OK, vous êtes à la définition du problème à l'endroit où il semble il n'y a pas beaucoup de place pour l'amélioration. C'est assez rare, dans mon expérience. J'ai essayé de l'expliquer dans un Dr Dobbs article en novembre 93, en partant d'une conventionnellement bien conçu non-trivial programme avec pas évident de déchets et en la prenant à travers une série d'optimisations jusqu'à ce que son horloge murale a été réduit de 48 secondes à 1,1 secondes, et le code source de la taille a été réduite par un facteur de 4. Mon outil de diagnostic a été cette. La séquence de changements:
Le premier problème est l'utilisation de la liste des clusters (maintenant appelé "itérateurs" et "les classes conteneur") qui représente plus de la moitié du temps. Ceux qui ont été remplacés par de code assez simple, amenant le temps jusqu'à 20 secondes.
Maintenant le plus grand temps-taker est plus de création de liste. En pourcentage, il n'était pas si grand, mais maintenant c'est parce que le plus gros problème a été supprimé. J'ai trouver un moyen de l'accélérer, et le temps de chute à 17 sec.
Maintenant, il est plus difficile de trouver évident coupables, mais il y a un peu plus petits que je peux faire quelque chose, et le temps de chute à 13 sec.
Maintenant j'ai l'impression d'avoir frappé un mur. Les échantillons sont de me dire exactement ce qu'il fait, mais je n'arrive pas à trouver quelque chose que je peux améliorer. Puis je réfléchis sur la base de la conception du programme, sur une opération pilotée par la structure, et de se demander si toute la liste de recherche qu'il est en train de faire est réellement obligatoire par les exigences du problème.
Puis j'ai frappé à une re-conception, où le code du programme est réellement produite (via les macros du préprocesseur) à partir d'un petit ensemble de la source, et dans lequel le programme n'est pas constamment essayer de trouver des choses que le programmeur sait sont assez prévisibles. En d'autres termes, ne pas "interpréter" la séquence de choses à faire, "compiler".
- Cette refonte est fait, la réduction du code source par un facteur de 4, et le temps est réduit à 10 secondes.
Maintenant, parce que c'est trop rapide, il est difficile de l'échantillon, donc, je lui donne 10 fois plus de travail à faire, mais le suivant, basé sur l'original de la charge de travail.
Plus le diagnostic révèle que c'est le temps passé dans la file d'attente-gestion. En doublure de ces réduit le temps de 7 secondes.
Maintenant un grand temps-taker est le diagnostic de l'impression que j'avais fait. Couleur que - 4 secondes.
Maintenant, le plus gros temps-preneurs sont les appels à malloc et free. Recycler objets - 2,6 secondes.
En continuant d'exemple, je trouve encore des opérations qui ne sont pas strictement nécessaires - 1.1 secondes.
Total de l'accélération de facteur: 43.6
Maintenant pas de deux programmes sont identiques, mais dans la non-jouet de logiciel je l'ai toujours vu une progression de ce genre. Vous recevez d'abord les choses faciles, et puis le plus difficile, jusqu'à ce que vous arrivez à un point de rendements décroissants. Puis, les informations de gain peut ainsi conduire à une refonte, le démarrage d'un nouveau cycle de la vitesse, jusqu'à ce que vous atteint des rendements décroissants. Maintenant c'est le moment à partir duquel il pourrait être judicieux de s'interroger sur l' ++i
ou i++
ou for(;;)
ou while(1)
sont plus rapides: les types de questions que je vois souvent sur.
P. S. Il peut être demandé pourquoi je ne l'ai pas utiliser un profiler. La réponse est que presque tous ces "problèmes" était un appel de fonction du site, qui mettent des échantillons pinpoint. Les profileurs, même aujourd'hui, sont à peine à se rallier à l'idée que les déclarations et les instructions d'appel sont plus importants pour les localiser, et plus faciles à résoudre, que l'ensemble des fonctions.
J'en ai construit un profiler pour le faire, mais pour un vrai bas-et-sale intimité avec ce que fait le code, il n'y a pas de substitut pour l'obtention de vos doigts à droite. C'est pas une question que le nombre d'échantillons est faible, car aucun des problèmes trouvés sont si minuscules qu'ils sont faciles à manquer.
AJOUTÉ: jerryjvl demandées quelques exemples. Ici, c'est le premier problème. Il se compose d'un petit nombre de lignes de code, ensemble, à prendre plus de la moitié du temps:
/* IF ALL TASKS DONE, SEND ITC_ACKOP, AND DELETE OP */
if (ptop->current_task >= ILST_LENGTH(ptop->tasklist){
. . .
/* FOR EACH OPERATION REQUEST */
for ( ptop = ILST_FIRST(oplist); ptop != NULL; ptop = ILST_NEXT(oplist, ptop)){
. . .
/* GET CURRENT TASK */
ptask = ILST_NTH(ptop->tasklist, ptop->current_task)
Elles ont été à l'aide de la liste de cluster ILST (similaire à une liste de classe). Ils sont mis en œuvre dans la manière habituelle, avec "se cacher de l'information", qui signifie que les utilisateurs de la classe n'étaient pas censés avoir de soins de la façon dont ils ont été mises en œuvre. Lorsque ces lignes ont été écrites (sur environ 800 lignes de code) la pensée n'a pas été donnée à l'idée que ces pourrait être un "goulot d'étranglement" (je déteste ce mot). Ils sont tout simplement la méthode recommandée pour faire des choses. C'est facile à dire avec le recul, que ceux-ci devraient avoir été évité, mais dans mon expérience de tous les problèmes de performance sont comme ça. En général, il est bon d'essayer d'éviter de créer des problèmes de performances. Il est encore mieux de trouver et de corriger celles qui sont créées, même si elles "aurait pu être évité" (avec le recul). J'espère que donne un peu de la saveur.
Voici le deuxième problème, en deux lignes distinctes:
/* ADD TASK TO TASK LIST */
ILST_APPEND(ptop->tasklist, ptask)
. . .
/* ADD TRANSACTION TO TRANSACTION QUEUE */
ILST_APPEND(trnque, ptrn)
Il s'agit des listes en ajoutant des éléments à leurs extrémités. (La solution était de recueillir les éléments de tableaux, et de construire les listes de tous à la fois). La chose intéressante est que ces déclarations ne coût (c'est à dire ont été sur la pile d'appel) 3/48 de l'heure d'origine, de sorte qu'ils n'étaient pas en fait un gros problème au début. Cependant, après avoir enlevé le premier problème, ils coûtent 3/20 de l'époque et étaient donc maintenant un "gros poisson". En général, c'est comment il va.
Je pourrais ajouter que ce projet a été distillé à partir d'un projet réel, je l'ai aidé. Dans ce projet, les problèmes de performance ont été beaucoup plus dramatique (comme l'ont été les accélérations), comme l'appel d'une base de données-routine de l'accès à l'intérieur d'une boucle interne pour voir si une tâche a été terminée.
RÉFÉRENCE AJOUTÉE:
Le code source, à la fois original et repensé, peut être trouvé dans www.ddj.compour 1993, dans le fichier 9311.zip, fichiers slug.asc et slug.zip .
EDIT 2011/11/26:
Il y a maintenant un projet sur sourceforge contenant le code source en Visual C++ et d'un coup-par-coup de description de la façon dont il a été à l'écoute. Il ne va que jusqu'à la première moitié du scénario décrit ci-dessus, et il ne suit pas exactement la même séquence, mais obtient toujours un 2-3 ordre de grandeur de l'accélération.