169 votes

Alternatives à gprof

Quels autres programmes font la même chose que gprof ?

201voto

Mike Dunlavey Points 25419

gprof (lire l'article) existe pour des raisons historiques. Si vous pensez qu'il vous aidera à trouver des problèmes de performance, il n'a jamais été annoncé comme tel. Voici ce que dit le journal :

Ce profile peut être utilisé pour comparer et évaluer les coûts de diverses mises en œuvre.

Il ne dit pas qu'il peut être utilisé pour identifier les différentes mises en œuvre à évaluer, bien qu'il ne sous-entend qu'il pourrait, dans des circonstances particulières :

surtout si l'on constate que de petites parties du programme dominent son temps d'exécution.

Qu'en est-il des problèmes qui ne sont pas aussi localisés ? Ne sont-ils pas importants ? Ne placez pas d'attentes sur gprof qui n'ont jamais été réclamés pour elle. Il s'agit uniquement un outil de mesure, et uniquement des opérations liées au CPU.

Essayez plutôt ceci.
Voici un exemple d'une accélération de 44x.
Voici une accélération de 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 observation simple à propos des programmes. Dans une exécution donnée, chaque instruction est responsable d'une certaine fraction du temps total (en particulier, le temps d'exécution de l'instruction). call instructions), dans le sens où si elle n'était pas là, le temps ne serait pas dépensé. Pendant ce temps, l'instruction est sur la pile **. Quand on a compris cela, on peut voir que -

gprof incarne certains mythes sur la performance, tels que :

  1. que l'échantillonnage du compteur de programme est utile.
    Elle n'est utile que si vous avez un goulot d'étranglement inutile, comme un tri à bulles d'un grand tableau de valeurs scalaires. Dès que vous le transformez, par exemple, en un tri utilisant la comparaison de chaînes de caractères, il reste un goulot d'étranglement, mais l'échantillonnage du compteur de programme ne le verra pas parce que le point chaud est maintenant dans la comparaison de chaînes de caractères. D'un autre côté, si l'on échantillonne le compteur de programme de étendu Le compteur du programme (la pile d'appels), l'endroit où le comparateur de chaînes est appelé, la boucle de tri, sont clairement affichés. En fait, gprof était une tentative de remédier aux limites de l'échantillonnage pc-only.

  2. que la synchronisation des fonctions est plus importante que la saisie des lignes de code qui prennent du temps.
    La raison de ce mythe est que gprof n'a pas été 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, il faut encore chercher à l'intérieur de celle-ci les lignes qui sont responsables du temps. S'il y avait des échantillons de pile, vous n'auriez pas besoin de regarder, ces lignes seraient sur les échantillons. (Une fonction typique peut comporter 100 à 1000 instructions. Une fonction appelez est 1 instruction, donc quelque chose qui localise les appels coûteux est 2-3 ordres de grandeur plus précis).

  3. que le graphique d'appel est important.
    Ce que vous devez savoir à propos d'un programme n'est pas il passe son temps, mais pourquoi . Lorsqu'il passe du temps dans une fonction, chaque ligne de code sur la pile constitue un maillon de la chaîne de raisonnement qui explique pourquoi il se trouve là. Si vous ne pouvez voir qu'une partie de la pile, vous ne pouvez voir qu'une partie du raisonnement, et vous ne pouvez donc pas savoir 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, cette ligne a peut-être une mauvaise raison. En général, il faut voir plusieurs lignes simultanées pour trouver une mauvaise raison, si elle existe. Si A appelle B à plusieurs endroits, cela vous en dit encore moins.

  4. que la récursion est une question délicate et confuse.
    C'est seulement parce que gprof et d'autres profileurs perçoivent la nécessité de générer un graphe d'appels, puis d'attribuer des temps aux nœuds. Si l'on dispose d'é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 des échantillons sur laquelle elle se trouve. S'il y a récursion, alors une ligne donnée peut apparaître plus d'une 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 (de manière isolée ou non). Si l'on peut faire en sorte que cette ligne ne prenne pas de temps (par exemple en la supprimant ou en la contournant), alors ces échantillons seraient disparaître et le temps serait réduit de F%.

  5. que la précision de la mesure du temps (et donc un grand nombre d'échantillons) est importante.
    Pensez-y une seconde. Si une ligne de code se trouve sur 3 échantillons sur 5, alors si vous pouviez l'éliminer comme une ampoule, c'est environ 60% de temps en moins qui serait utilisé. Maintenant, vous savez que si vous aviez pris 5 échantillons différents, vous n'auriez pu la voir que 2 fois, ou même 4. Donc, cette mesure de 60% est plutôt une fourchette générale de 40% à 80%. Si elle n'était que de 40 %, diriez-vous que le problème ne vaut pas la peine d'être résolu ? Quel est donc l'intérêt de la précision temporelle, alors que ce que vous voulez vraiment, c'est trouver les problèmes ? 500 ou 5000 échantillons auraient mesuré le problème avec plus de précision, mais ne l'auraient pas trouvé avec plus d'exactitude.

  6. que le comptage des invocations d'instructions ou de fonctions est utile.
    Supposons que vous sachiez qu'une fonction a été appelée 1000 fois. Pouvez-vous en déduire la fraction de temps qu'elle coûte ? Vous devez également connaître le temps d'exécution moyen de la fonction, le multiplier par le nombre et le diviser par le temps total. Le temps d'invocation moyen peut varier de quelques nanosecondes à quelques secondes, donc le compte seul n'est pas très révélateur. S'il y a des échantillons de pile, le coût d'une routine ou de toute instruction est juste la fraction d'échantillons sur laquelle elle se trouve. Cette fraction de temps est ce qui pourrait en principe être économisé globalement si la routine ou l'instruction ne prenait pas de temps, c'est donc ce qui a la relation la plus directe avec les performances.

  7. que les échantillons ne doivent pas être prélevés en cas de blocage
    Les raisons de ce mythe sont doubles : 1) l'échantillonnage du PC n'a aucun sens lorsque le programme attend, et 2) le souci de la précision du timing. Cependant, pour (1), le programme peut très bien être en train d'attendre quelque chose qu'il a demandé, comme l'entrée/sortie d'un fichier, ce que vous ne pouvez pas faire. besoin de savoir et que les échantillons de piles révèlent. (Pour (2), si le programme attend simplement en raison de la concurrence avec d'autres processus, cela se produit probablement de manière assez aléatoire pendant l'exécution. Ainsi, bien que le programme puisse prendre plus de temps, cela n'aura pas un grand effet sur la statistique qui importe, le pourcentage de temps pendant lequel les instructions sont sur la pile.

  8. que le "temps libre" est important
    Le temps personnel n'a de sens que si vous mesurez au niveau de la fonction, et non de la ligne, et que vous pensez avoir besoin d'aide pour discerner si le temps de la fonction est consacré à des calculs purement locaux ou à des routines appelées. Si l'on résume au niveau de la ligne, une ligne représente le temps personnel si elle est à la fin de la pile, sinon elle représente le temps inclus. Dans tous les cas, ce qu'elle coûte est le pourcentage d'échantillons de pile sur lequel elle se trouve, ce qui vous permet de la localiser dans les deux cas.

  9. que les échantillons doivent être prélevés à haute fréquence
    Cela vient de l'idée qu'un problème de performance peut agir rapidement, et que les échantillons doivent être fréquents pour l'atteindre. Mais, si le problème coûte, disons 20%, sur une durée totale de 10 secondes (ou autre), alors chaque échantillon dans cette durée totale aura 20% de chance de l'atteindre, peu importe si le problème survient dans un seul morceau comme ceci
    .....XXXXXXXX...........................
    .^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^ (20 échantillons, 4 résultats)
    ou en plusieurs petits morceaux comme ceci
    X...X...X.X..X.........X.....X....X.....
    .^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^ (20 échantillons, 3 résultats)
    Dans tous les cas, le nombre d'occurrences sera en moyenne de 1 sur 5, quel que soit le nombre d'échantillons prélevés, ou leur nombre réduit. (Moyenne = 20 * 0,2 = 4. Écart-type = +/- sqrt(20 * 0,2 * 0,8) = 1,8.)

  10. que vous essayez de trouver le site goulot d'étranglement
    comme s'il n'y en avait qu'un seul. Considérons la chronologie d'exécution suivante : vxvWvzvWvxvWvYvWvxvWv.vWvxvWvYvW
    Il s'agit d'un véritable travail utile, représenté par . . Il y a des problèmes de performance vWxYz prenant respectivement 1/2, 1/4, 1/8, 1/16, 1/32 du temps. L'échantillonnage trouve v facilement. Il est retiré, laissant
    xWzWxWYWxW.WxWYW
    Maintenant le programme prend moitié moins de temps à s'exécuter, et maintenant W prend la moitié du temps, et se trouve facilement. Il est retiré, laissant
    xzxYx.xY
    Ce processus se poursuit, en éliminant chaque fois le problème de performance le plus important, en pourcentage, jusqu'à ce que l'on ne trouve plus rien à éliminer. Maintenant, la seule chose exécutée est . qui s'exécute en 1/32 du temps utilisé par le programme original. C'est le effet de grossissement L'élimination d'un problème rend le reste plus grand, en pourcentage, parce que le dénominateur est réduit.
    Un autre point crucial est que chaque problème doit être trouvé - Il ne manque aucun des 5. Tout problème non trouvé et corrigé réduit sévèrement le rapport final d'accélération. En trouver quelques-uns, mais pas tous, n'est pas "suffisant".

AJOUTÉ : Je voudrais juste souligner une raison pour laquelle gprof est populaire - elle est enseignée, probablement parce que c'est gratuit, facile à enseigner et qu'il existe depuis longtemps. Une recherche rapide sur Google permet de trouver quelques institutions académiques qui l'enseignent (ou semblent l'enseigner) :

berkeley bu clemson colorado duke earlham fsu indiana mit msu ncsa.illinois ncsu nyu ou princeton psu stanford ucsd umd umich utah utexas utk wustl

** A l'exception d'autres moyens de demander un travail à faire, qui ne laissent pas de trace en disant pourquoi comme l'envoi de messages.

77voto

Norman Ramsey Points 115730

Valgrind dispose d'un profileur de comptage d'instructions avec un très beau visualiseur appelé KCacheGrind . Comme Mike Dunlavey le recommande, Valgrind compte la fraction d'instructions pour lesquelles une procédure est vivante sur la pile, bien que je sois désolé de dire qu'il semble devenir confus en présence de récursion mutuelle. Mais le visualisateur est très agréable et à des années lumière d'avance sur gprof .

66voto

Puisque je n'ai rien vu ici au sujet de perf qui est un outil relativement nouveau pour profiler le noyau et les applications utilisateur sous Linux, j'ai décidé d'ajouter cette information.

Tout d'abord - il s'agit d'un tutoriel sur Profilage Linux avec perf

Vous pouvez utiliser perf si votre noyau Linux est supérieur à la version 2.6.32 ou oprofile si elle est plus ancienne. Les deux programmes n'exigent pas de vous d'instrumenter votre programme (comme gprof exige). Cependant, afin d'obtenir le graphique d'appel correctement dans perf vous devez construire 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

Vous pouvez également enregistrer les données de performance d'une application en cours d'exécution et les analyser ensuite :

1) 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 le graphique d'appel ()

perf record -g -p `pidof a.out` 

2) 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

Vous pouvez également enregistrer les données de performance d'une application et les analyser par la suite en lançant l'application de cette manière et en attendant qu'elle se termine :

perf record ./a.out

Voici un exemple de profilage d'un programme de test

Le programme de test se trouve dans le fichier main.cpp (je mettrai main.cpp au bas du message) :

Je le compile de cette façon :

g++ -m64 -fno-omit-frame-pointer -g main.cpp -L.  -ltcmalloc_minimal -o my_test

J'utilise libmalloc_minimial.so puisqu'il est compilé avec -fno-omit-frame-pointer alors que libc malloc semble être compilé sans cette option. J'exécute ensuite 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 :

perf report --stdio -g none --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 :

perf report --stdio -g none -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'appel sont analysées :

perf report --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 ...

À ce stade, vous savez donc où votre programme passe du temps.

Et voici le fichier 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;
}

24voto

Anycorn Points 20521

Essayez OProfile . C'est un bien meilleur outil pour profiler votre code. Je suggère également Intel VTune .

Les deux outils ci-dessus permettent de réduire le temps passé dans une ligne de code particulière, d'annoter votre code, de montrer l'assemblage et la durée d'une instruction particulière. En plus de la mesure du temps, vous pouvez également interroger des compteurs spécifiques, par exemple les hits de cache, etc.

Contrairement à gprof, vous pouvez profiler tout processus/binaire s'exécutant sur votre système en utilisant l'un ou l'autre.

18voto

Sandeep Points 503

Outils de performance Google inclure un profileur simple à utiliser. Un profileur de CPU ainsi qu'un profileur de tas sont disponibles.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X