Quels autres programmes font la même chose que gprof?
Quels autres programmes font la même chose que gprof?
Réponses:
Valgrind a un profileur de comptage d'instructions avec un très bon visualiseur appelé KCacheGrind . Comme le recommande Mike Dunlavey, Valgrind compte la fraction d'instructions pour laquelle une procédure est active sur la pile, même si je suis désolé de dire qu'elle semble devenir confuse en présence d'une récursivité mutuelle. Mais le visualiseur est très agréable et à des années-lumière d'avance gprof
.
gprof (lire l'article) existe pour des raisons historiques. Si vous pensez que cela vous aidera à trouver des problèmes de performances, cela n'a jamais été annoncé comme tel. Voici ce que dit le journal:
Le profil peut être utilisé pour comparer et évaluer les coûts de diverses implémentations.
Il ne dit pas qu'il peut être utilisé pour identifier les différentes implémentations à évaluer, bien que cela implique qu'il pourrait, dans des circonstances particulières:
surtout si de petites portions du programme dominent son temps d'exécution.
Qu'en est-il des problèmes qui ne sont pas si localisés? Cela n'a-t-il pas d'importance? Ne placez pas d'attentes sur gprof qui n'ont jamais été réclamées pour cela. Ce n'est qu'un outil de mesure et uniquement des opérations liées au processeur.
Essayez plutôt ceci.
Voici un exemple d'accélération 44x.
Voici une accélération 730x.
Voici une démonstration vidéo de 8 minutes.
Voici une explication des statistiques.
Voici une réponse aux critiques.
Il y a une simple observation sur les programmes. Dans une exécution donnée, chaque instruction est responsable d'une fraction du temps global (en particulier les call
instructions), en ce sens que si elle n'était pas là, le temps ne serait pas dépensé. Pendant ce temps, l'instruction est sur la pile **. Quand cela est compris, vous pouvez voir que -
gprof incarne certains mythes sur la performance, tels que:
cet échantillonnage du compteur de programme est utile.
Cela n'est utile que si vous avez un goulot d'étranglement inutile, tel qu'une sorte de bulle d'un grand tableau de valeurs scalaires. Dès que vous, par exemple, le changez en un tri utilisant la comparaison de chaînes, il s'agit toujours d'un goulot d'étranglement, mais l'échantillonnage du compteur de programme ne le verra pas car maintenant le hotspot est en comparaison de chaînes. D'autre part, s'il devait échantillonner le compteur de programme étendu (la pile d'appels), le point auquel la comparaison de chaînes est appelée, la boucle de tri, est clairement affiché. En fait, gprof était une tentative de remédier aux limites de l'échantillonnage sur ordinateur uniquement.
que les fonctions de chronométrage sont plus importantes que la capture de lignes de code chronophages.
La raison de ce mythe est que gprof n'était pas capable de capturer des échantillons de pile, donc à la place, il chronomètre les fonctions, compte leurs invocations et essaie de capturer le graphe d'appel. Cependant, une fois qu'une fonction coûteuse est identifiée, vous devez toujours rechercher à l'intérieur les lignes qui sont responsables du temps. S'il y avait des échantillons de pile que vous n'auriez pas besoin de regarder, ces lignes seraient sur les échantillons. (Une fonction typique peut avoir 100 à 1000 instructions. Un appel de fonction est une instruction, donc quelque chose qui localise les appels coûteux est de 2 à 3 ordres de grandeur plus précis.)
que le graphe d'appel est important.
Ce que vous devez savoir sur un programme n'est pas l' endroit où il passe son temps, mais pourquoi. Lorsqu'il passe du temps dans une fonction, chaque ligne de code de la pile donne un lien dans la chaîne de raisonnement expliquant pourquoi elle est là. Si vous ne pouvez voir qu'une partie de la pile, vous ne pouvez voir qu'une partie de la raison, vous ne pouvez donc pas dire avec certitude si ce temps est réellement nécessaire. Que vous dit le graphe d'appel? Chaque arc vous indique qu'une fonction A était en train d'appeler une fonction B pendant une certaine fraction du temps. Même si A n'a qu'une seule ligne de code appelant B, cette ligne ne donne qu'une petite partie de la raison. Si vous êtes assez chanceux, peut-être que cette ligne a une mauvaise raison. Habituellement, vous devez voir plusieurs lignes simultanées pour trouver une mauvaise raison si elle est là. Si A appelle B à plus d'un endroit, il vous en dit encore moins.
cette récursivité est un problème délicat et déroutant.
C'est uniquement parce que gprof et d'autres profileurs perçoivent le besoin de générer un graphe d'appel, puis d'attribuer des heures aux nœuds. Si l'on a des échantillons de la pile, le coût en temps de chaque ligne de code qui apparaît sur les échantillons est un nombre très simple - la fraction d'échantillons sur laquelle elle se trouve. S'il y a récursivité, une ligne donnée peut apparaître plusieurs fois sur un échantillon.
Peu importe. Supposons que des échantillons soient prélevés toutes les N ms et que la ligne apparaisse sur F% d'entre eux (seuls ou non). Si cette ligne peut prendre peu de temps (par exemple en la supprimant ou en la branchant autour d'elle), alors ces échantillons disparaîtraient et le temps serait réduit de F%.
cette précision de la mesure du temps (et donc d'un grand nombre d'échantillons) est importante.
Réfléchis-y une seconde. Si une ligne de code est sur 3 échantillons sur cinq, alors si vous pouviez la tirer comme une ampoule, c'est environ 60% de temps en moins qui serait utilisé. Maintenant, vous savez que si vous aviez prélevé 5 échantillons différents, vous ne l'auriez peut-être vu que 2 fois, ou jusqu'à 4. Ainsi, la mesure à 60% ressemble plus à une plage générale de 40% à 80%. Si ce n'était que de 40%, diriez-vous que le problème ne vaut pas la peine d'être réglé? Alors, quel est le point de précision du temps, quand ce que vous voulez vraiment est de trouver les problèmes ? 500 ou 5000 échantillons auraient mesuré le problème avec une plus grande précision, mais ne l'auraient pas trouvé avec plus de précision.
que le comptage des appels d'instructions ou de fonctions est utile.
Supposons que vous sachiez qu'une fonction a été appelée 1000 fois. Pouvez-vous dire à partir de là quelle fraction de temps cela coûte? Vous devez également savoir combien de temps il faut pour s'exécuter, en moyenne, multipliez-le par le nombre et divisez par le temps total. Le temps moyen d'appel peut varier de nanosecondes à secondes, de sorte que le décompte à lui seul ne dit pas grand-chose. S'il y a des échantillons de pile, le coût d'une routine ou d'une instruction n'est que la fraction d'échantillons sur laquelle elle se trouve. Cette fraction de temps est ce qui pourrait en principe être globalement économisé si la routine ou la déclaration pouvait être faite pour ne pas prendre de temps, c'est donc ce qui a la relation la plus directe avec la performance.
que les échantillons n'ont pas besoin d'être prélevés lorsqu'ils sont bloqués
Les raisons de ce mythe sont doubles: 1) que l'échantillonnage PC n'a pas de sens lorsque le programme est en attente, et 2) la préoccupation de l'exactitude du timing. Cependant, pour (1), le programme peut très bien attendre quelque chose qu'il a demandé, comme des E / S de fichier, que vous devez connaître et quels échantillons de pile révèlent. (De toute évidence, vous voulez exclure les échantillons en attendant l'entrée de l'utilisateur.) Pour (2) si le programme attend simplement à cause de la concurrence avec d'autres processus, cela se produit probablement de manière assez aléatoire pendant son exécution. Ainsi, bien que le programme prenne plus de temps, cela n'aura pas un effet important sur la statistique qui compte, le pourcentage de temps pendant lequel les instructions sont sur la pile.
que le "temps propre" compte Le
temps propre n'a de sens que si vous mesurez au niveau de la fonction, pas au niveau de la ligne, et vous pensez avoir besoin d'aide pour discerner si le temps de la fonction entre dans le calcul purement local par rapport aux routines appelées. Si vous récapitulez au niveau de la ligne, une ligne représente le temps propre si elle se trouve à la fin de la pile, sinon elle représente le temps inclusif. Quoi qu'il en soit, ce qu'il en coûte, c'est le pourcentage d'échantillons de pile sur lesquels il se trouve, ce qui le localise pour vous dans les deux cas.
que les échantillons doivent être prélevés à haute fréquence
Cela vient de l'idée qu'un problème de performance peut être rapide et que les échantillons doivent être fréquents pour y parvenir. Mais, si le problème coûte 20%, par exemple, sur un temps de fonctionnement total de 10 secondes (ou autre), alors chaque échantillon de ce temps total aura 20% de chances de le toucher, peu importe si le problème se produit. dans un seul morceau comme celui-ci
.....XXXXXXXX...........................
.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^
(20 échantillons, 4 hits)
ou dans de nombreux petits morceaux comme celui-ci
X...X...X.X..X.........X.....X....X.....
.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^
(20 échantillons, 3 hits) Dans tous les
cas, le nombre de hits sera en moyenne d'environ 1 sur 5, quel que soit le nombre d'échantillons prélevés, ou combien peu. (Moyenne = 20 * 0,2 = 4. Écart type = +/- sqrt (20 * 0,2 * 0,8) = 1,8.)
que vous essayez de trouver le goulot
d' étranglement comme s'il n'y en avait qu'un. Considérez le calendrier d'exécution suivant: vxvWvzvWvxvWvYvWvxvWv.vWvxvWvYvW
Il consiste en un véritable travail utile, représenté par .
. Il y a des problèmes de performances vWxYz
prenant respectivement 1/2, 1/4, 1/8, 1/16, 1/32 du temps. L'échantillonnage trouve v
facilement. Il est supprimé, laissant
xWzWxWYWxW.WxWYW
maintenant le programme prend deux fois moins de temps à exécuter, et W
prend maintenant la moitié du temps, et se trouve facilement. Il est supprimé, laissant
xzxYx.xY
Ce processus se poursuit, chaque fois en supprimant le plus gros problème de performances, en pourcentage, jusqu'à ce que rien à supprimer ne puisse être trouvé. Maintenant, la seule chose exécutée est .
, qui s'exécute dans 1/32 du temps utilisé par le programme d'origine. C'est l' effet de grossissement, par lequel la suppression de tout problème augmente le reste, en pourcentage, car le dénominateur est réduit.
Un autre point crucial est que chaque problème doit être trouvé - ne manque aucun des 5. Tout problème non trouvé et corrigé réduit considérablement le taux d'accélération final. Le simple fait d'en trouver, mais pas tous, n'est pas «suffisant».
AJOUTÉ: Je voudrais juste souligner une des raisons pour lesquelles gprof est populaire - il est enseigné, probablement parce qu'il est gratuit, facile à enseigner et qu'il existe depuis longtemps. Une recherche rapide sur Google localise certaines institutions académiques qui l'enseignent (ou semblent le faire):
berkeley bu clemson colorado duc earlham fsu indiana mit msu ncsa.illinois ncsu nyu ou princeton psu stanford ucsd umd umich utah utexas utk wustl
** À l'exception des autres façons de demander du travail à faire, qui ne laissent aucune trace indiquant pourquoi , comme par l'envoi de messages.
Comme je n'ai rien vu ici sur perf
un outil relativement nouveau de profilage du noyau et des applications utilisateur sous Linux, j'ai décidé d'ajouter ces informations.
Tout d'abord - ceci est un tutoriel sur le profilage Linux avecperf
Vous pouvez utiliser perf
si votre noyau Linux est supérieur à 2.6.32 ou oprofile
s'il est plus ancien. Les deux programmes ne nécessitent pas de votre part d'instrumenter votre programme (comme le gprof
requiert). Cependant, pour obtenir correctement le graphe d'appel, perf
vous devez créer votre programme avec -fno-omit-frame-pointer
. Par exemple: g++ -fno-omit-frame-pointer -O2 main.cpp
.
Vous pouvez voir l'analyse "en direct" de votre application avec perf top
:
sudo perf top -p `pidof a.out` -K
Ou vous pouvez enregistrer les données de performance d'une application en cours d'exécution et les analyser après cela:
1) Pour enregistrer les données de performance:
perf record -p `pidof a.out`
ou pour enregistrer pendant 10 secondes:
perf record -p `pidof a.out` sleep 10
ou pour enregistrer avec call graph ()
perf record -g -p `pidof a.out`
2) Pour analyser les données enregistrées
perf report --stdio
perf report --stdio --sort=dso -g none
perf report --stdio -g none
perf report --stdio -g
Ou vous pouvez enregistrer les données de performance d'une application et les analyser ensuite simplement en lançant l'application de cette manière et en attendant qu'elle se termine:
perf record ./a.out
Ceci est un exemple de profilage d'un programme de test
Le programme de test est dans le fichier main.cpp (je mettrai main.cpp en bas du message):
Je le compile de cette manière:
g++ -m64 -fno-omit-frame-pointer -g main.cpp -L. -ltcmalloc_minimal -o my_test
J'utilise libmalloc_minimial.so
car il est compilé avec -fno-omit-frame-pointer
tandis que la libc malloc semble être compilée sans cette option. Ensuite, je lance mon programme de test
./my_test 100000000
Ensuite, j'enregistre les données de performance d'un processus en cours:
perf record -g -p `pidof my_test` -o ./my_test.perf.data sleep 30
Ensuite, j'analyse la charge par module:
rapport de perf --stdio -g aucun --sort comm, dso -i ./my_test.perf.data
# Overhead Command Shared Object
# ........ ....... ............................
#
70.06% my_test my_test
28.33% my_test libtcmalloc_minimal.so.0.1.0
1.61% my_test [kernel.kallsyms]
Ensuite, la charge par fonction est analysée:
rapport de perf --stdio -g aucun -i ./my_test.perf.data | c ++ filt
# Overhead Command Shared Object Symbol
# ........ ....... ............................ ...........................
#
29.30% my_test my_test [.] f2(long)
29.14% my_test my_test [.] f1(long)
15.17% my_test libtcmalloc_minimal.so.0.1.0 [.] operator new(unsigned long)
13.16% my_test libtcmalloc_minimal.so.0.1.0 [.] operator delete(void*)
9.44% my_test my_test [.] process_request(long)
1.01% my_test my_test [.] operator delete(void*)@plt
0.97% my_test my_test [.] operator new(unsigned long)@plt
0.20% my_test my_test [.] main
0.19% my_test [kernel.kallsyms] [k] apic_timer_interrupt
0.16% my_test [kernel.kallsyms] [k] _spin_lock
0.13% my_test [kernel.kallsyms] [k] native_write_msr_safe
and so on ...
Ensuite, les chaînes d'appels sont analysées:
rapport perf --stdio -g graph -i ./my_test.perf.data | c ++ filt
# Overhead Command Shared Object Symbol
# ........ ....... ............................ ...........................
#
29.30% my_test my_test [.] f2(long)
|
--- f2(long)
|
--29.01%-- process_request(long)
main
__libc_start_main
29.14% my_test my_test [.] f1(long)
|
--- f1(long)
|
|--15.05%-- process_request(long)
| main
| __libc_start_main
|
--13.79%-- f2(long)
process_request(long)
main
__libc_start_main
15.17% my_test libtcmalloc_minimal.so.0.1.0 [.] operator new(unsigned long)
|
--- operator new(unsigned long)
|
|--11.44%-- f1(long)
| |
| |--5.75%-- process_request(long)
| | main
| | __libc_start_main
| |
| --5.69%-- f2(long)
| process_request(long)
| main
| __libc_start_main
|
--3.01%-- process_request(long)
main
__libc_start_main
13.16% my_test libtcmalloc_minimal.so.0.1.0 [.] operator delete(void*)
|
--- operator delete(void*)
|
|--9.13%-- f1(long)
| |
| |--4.63%-- f2(long)
| | process_request(long)
| | main
| | __libc_start_main
| |
| --4.51%-- process_request(long)
| main
| __libc_start_main
|
|--3.05%-- process_request(long)
| main
| __libc_start_main
|
--0.80%-- f2(long)
process_request(long)
main
__libc_start_main
9.44% my_test my_test [.] process_request(long)
|
--- process_request(long)
|
--9.39%-- main
__libc_start_main
1.01% my_test my_test [.] operator delete(void*)@plt
|
--- operator delete(void*)@plt
0.97% my_test my_test [.] operator new(unsigned long)@plt
|
--- operator new(unsigned long)@plt
0.20% my_test my_test [.] main
0.19% my_test [kernel.kallsyms] [k] apic_timer_interrupt
0.16% my_test [kernel.kallsyms] [k] _spin_lock
and so on ...
Donc, à ce stade, vous savez où votre programme passe du temps.
Et voici main.cpp pour le test:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
time_t f1(time_t time_value)
{
for (int j =0; j < 10; ++j) {
++time_value;
if (j%5 == 0) {
double *p = new double;
delete p;
}
}
return time_value;
}
time_t f2(time_t time_value)
{
for (int j =0; j < 40; ++j) {
++time_value;
}
time_value=f1(time_value);
return time_value;
}
time_t process_request(time_t time_value)
{
for (int j =0; j < 10; ++j) {
int *p = new int;
delete p;
for (int m =0; m < 10; ++m) {
++time_value;
}
}
for (int i =0; i < 10; ++i) {
time_value=f1(time_value);
time_value=f2(time_value);
}
return time_value;
}
int main(int argc, char* argv2[])
{
int number_loops = argc > 1 ? atoi(argv2[1]) : 1;
time_t time_value = time(0);
printf("number loops %d\n", number_loops);
printf("time_value: %d\n", time_value );
for (int i =0; i < number_loops; ++i) {
time_value = process_request(time_value);
}
printf("time_value: %ld\n", time_value );
return 0;
}
f1
appelait delete
. 40% (environ) du temps process_request
appelait delete
. Une bonne partie du reste a été dépensée new
. Les mesures sont approximatives, mais les points chauds sont localisés.
As in my answer, you run it under a debugger and hit ^C at a random time and capture the stack trace
. 1) Je pense que votre technique n'est pas utile lorsque vous devez analyser des problèmes de performances pour un programme fonctionnant sur le serveur de votre client. 2) Je ne sais pas comment vous appliquez cette technique pour obtenir des informations sur un programme comportant de nombreux threads qui traitent différentes requêtes. Je veux dire quand l'image générale est assez compliquée.
the problem is outside your code
, n'est-ce pas? Puisque vous pourriez avoir besoin de quelques informations pour soutenir votre point. Dans cette situation, vous devrez peut-être à un moment donné profiler votre application. Vous ne pouvez pas simplement demander à votre client de démarrer gdb et d'appuyer sur ^ C et d'obtenir des piles d'appels. C'était mon point. Ceci est un exemple spielwiese.fontein.de/2012/01/22/… . J'ai eu ce problème et le profilage m'a beaucoup aidé.
Essayez OProfile . C'est un bien meilleur outil pour profiler votre code. Je suggérerais également Intel VTune .
Les deux outils ci-dessus peuvent réduire le temps passé dans une ligne de code particulière, annoter votre code, afficher l'assemblage et la quantité d'instruction particulière nécessaire. En plus de la métrique de temps, vous pouvez également interroger des compteurs spécifiques, c'est-à-dire des hits de cache, etc.
Contrairement à gprof, vous pouvez profiler n'importe quel processus / binaire en cours d'exécution sur votre système en utilisant l'un des deux.
Les outils de performance de Google incluent un profileur simple à utiliser. Le processeur ainsi que le profileur de tas sont disponibles.
Jetez un œil à Sysprof .
Votre distribution l'a peut-être déjà.
http://lttng.org/ si vous voulez un traceur haute performance