unix performance - Wie kann ich C++-Code profilieren, der unter Linux läuft?




profiling gprof (9)

Verwenden Sie Valgrind, Callgrind und Kcachegrind:

valgrind --tool=callgrind ./(Your binary)

erzeugt callgrind.out.x. Lesen Sie es mit kcachegrind.

Verwende gprof (add -pg):

cc -o myprog myprog.c utils.c -g -pg 

(nicht so gut für Multithreads, Funktionszeiger)

Verwenden Sie google-perftools:

Verwendet Zeit-Sampling, zeigt I / O und CPU-Engpässe werden aufgedeckt.

Intel VTune ist das Beste (kostenlos für Bildungszwecke).

Andere: AMD Codeanalyst, OProfile, 'perf' Werkzeuge (apt-get install linux-tools)

Ich habe eine C ++ - Anwendung, die auf Linux läuft und die ich gerade optimiere. Wie kann ich feststellen, welche Bereiche meines Codes langsam ausgeführt werden?


Für single-threaded Programme können Sie igprof , den Ignominous Profiler verwenden: https://igprof.org/ .

Es ist ein Sampling-Profiler, ähnlich wie bei ... long ... Antwort von Mike Dunlavey, der die Ergebnisse in einen durchsuchbaren Call-Stack-Baum einpackt, der mit der Zeit oder dem Speicher für jede Funktion, entweder kumulativ oder pro Funktion.


Neuere Kernel (zB die neuesten Ubuntu-Kernel) werden mit den neuen "perf" perf_events ( apt-get install linux-tools ) AKA perf_events .

Diese kommen mit klassischen Sampling-Profilern ( man-page ) sowie dem tollen timechart !

Wichtig ist, dass diese Tools Systemprofilerstellung und nicht nur Prozessprofilierung sein können - sie können die Interaktion zwischen Threads, Prozessen und dem Kernel zeigen und Ihnen die Planung und E / A-Abhängigkeiten zwischen Prozessen verständlich machen.


Wenn Sie einen Profiler verwenden möchten, verwenden Sie einen der vorgeschlagenen.

Wenn Sie jedoch in Eile sind und Sie Ihr Programm unter dem Debugger manuell unterbrechen können, während es subjektiv langsam ist, gibt es eine einfache Möglichkeit, Leistungsprobleme zu finden.

Halte es einfach mehrmals an und schaue jedes Mal auf den Call-Stack. Wenn es einen Code gibt, der einen gewissen Prozentsatz der Zeit verschwendet, 20% oder 50% oder so, ist das die Wahrscheinlichkeit, dass Sie ihn bei jeder Stichprobe auffangen. Das ist ungefähr der Prozentsatz der Proben, auf denen Sie es sehen werden. Es ist kein geschultes Rätselraten erforderlich. Wenn Sie eine Vermutung haben, was das Problem ist, wird dies beweisen oder widerlegen.

Sie haben möglicherweise mehrere Leistungsprobleme unterschiedlicher Größe. Wenn du einen von ihnen ausräumst, werden die verbleibenden einen größeren Prozentsatz nehmen und bei nachfolgenden Durchgängen leichter zu erkennen sein. Dieser Vergrößerungseffekt kann , wenn er über mehrere Probleme hinweggeht, zu wirklich massiven Beschleunigungsfaktoren führen.

Vorbehalt: Programmierer sind skeptisch gegenüber dieser Technik, wenn sie sie nicht selbst verwenden. Sie werden sagen, dass Profiler Ihnen diese Informationen geben, aber das ist nur wahr, wenn sie den gesamten Aufruf-Stack abtasten und Sie dann einen zufälligen Satz von Samples untersuchen lassen. (Die Zusammenfassungen sind, wo die Einsicht verloren geht.) Call-Graphen geben Ihnen nicht die gleichen Informationen, weil

  1. sie fassen nicht auf der Anweisungsebene zusammen, und
  2. sie geben verwirrende Zusammenfassungen in Gegenwart von Rekursion.

Sie werden auch sagen, dass es nur an Spielzeugprogrammen funktioniert, wenn es tatsächlich an jedem Programm funktioniert, und es scheint bei größeren Programmen besser zu funktionieren, weil sie eher Probleme haben, zu finden. Sie werden sagen, dass es manchmal Dinge findet, die keine Probleme sind, aber das ist nur wahr, wenn Sie einmal etwas sehen. Wenn Sie ein Problem bei mehr als einem Beispiel sehen, ist es real.

PS Dies kann auch in Multi-Thread-Programmen durchgeführt werden, wenn Call-Stack-Samples des Thread-Pools zu einem Zeitpunkt gesammelt werden können, wie es in Java der Fall ist.

PPS Grob gesagt, je mehr Abstraktionsschichten Sie in Ihrer Software haben, desto wahrscheinlicher ist es, dass Sie feststellen, dass dies die Ursache von Leistungsproblemen ist (und die Möglichkeit, schneller zu werden).

Hinzugefügt: Es ist vielleicht nicht offensichtlich, aber die Stack-Sampling-Technik funktioniert genauso gut bei Rekursion. Der Grund dafür ist, dass die Zeit, die durch das Entfernen eines Befehls gespart werden würde, durch den Anteil der Samples, die ihn enthalten, angenähert wird, unabhängig davon, wie oft er in einem Sample auftritt.

Ein weiterer Einwand, den ich oft höre, ist: " Es wird willkürlich aufhören, und es wird das wirkliche Problem vermissen ". Dies ergibt sich aus einem früheren Konzept dessen, was das eigentliche Problem ist. Eine Schlüsseleigenschaft von Leistungsproblemen ist, dass sie die Erwartungen übertreffen. Sampling sagt Ihnen, dass etwas ein Problem ist, und Ihre erste Reaktion ist Unglaube. Das ist natürlich, aber Sie können sicher sein, wenn es ein Problem findet, ist es real und umgekehrt.

HINZUGEFÜGT: Lass mich eine Bayes'sche Erklärung machen, wie es funktioniert. Angenommen, es gibt eine Anweisung I (Aufruf oder anderes), die sich auf dem Aufrufstapel befindet, einen Bruchteil f der Zeit (und kostet so viel). Nehmen wir zur Vereinfachung an, dass wir nicht wissen, was f ist, aber nehmen wir an, es ist entweder 0,1, 0,2, 0,3, ... 0,9, 1,0, und die vorherige Wahrscheinlichkeit jeder dieser Möglichkeiten ist 0,1, also sind alle diese Kosten gleich wahrscheinlich a priori.

Dann nehmen wir an, wir nehmen nur zwei Stapelproben, und wir sehen die Anweisung I an beiden Proben, die angegebene Beobachtung o=2/2 . Dies gibt uns neue Schätzungen der Häufigkeit f von I , entsprechend:

Prior                                    
P(f=x) x  P(o=2/2|f=x) P(o=2/2&&f=x)  P(o=2/2&&f >= x)  P(f >= x)

0.1    1     1             0.1          0.1            0.25974026
0.1    0.9   0.81          0.081        0.181          0.47012987
0.1    0.8   0.64          0.064        0.245          0.636363636
0.1    0.7   0.49          0.049        0.294          0.763636364
0.1    0.6   0.36          0.036        0.33           0.857142857
0.1    0.5   0.25          0.025        0.355          0.922077922
0.1    0.4   0.16          0.016        0.371          0.963636364
0.1    0.3   0.09          0.009        0.38           0.987012987
0.1    0.2   0.04          0.004        0.384          0.997402597
0.1    0.1   0.01          0.001        0.385          1

                  P(o=2/2) 0.385                

Die letzte Spalte besagt, dass zum Beispiel die Wahrscheinlichkeit, dass f > = 0,5 ist, 92% beträgt, gegenüber der vorherigen Annahme von 60%.

Angenommen, die vorherigen Annahmen sind unterschiedlich. Angenommen, P (f = 0.1) ist .991 (fast sicher), und alle anderen Möglichkeiten sind fast unmöglich (0.001). Mit anderen Worten, unsere vorherige Gewissheit ist, dass I billig I . Dann bekommen wir:

Prior                                    
P(f=x) x  P(o=2/2|f=x) P(o=2/2&& f=x)  P(o=2/2&&f >= x)  P(f >= x)

0.001  1    1              0.001        0.001          0.072727273
0.001  0.9  0.81           0.00081      0.00181        0.131636364
0.001  0.8  0.64           0.00064      0.00245        0.178181818
0.001  0.7  0.49           0.00049      0.00294        0.213818182
0.001  0.6  0.36           0.00036      0.0033         0.24
0.001  0.5  0.25           0.00025      0.00355        0.258181818
0.001  0.4  0.16           0.00016      0.00371        0.269818182
0.001  0.3  0.09           0.00009      0.0038         0.276363636
0.001  0.2  0.04           0.00004      0.00384        0.279272727
0.991  0.1  0.01           0.00991      0.01375        1

                  P(o=2/2) 0.01375                

Jetzt heißt es P (f> = 0,5) ist 26%, gegenüber der vorherigen Annahme von 0,6%. Mit Bayes können wir also unsere Schätzung der voraussichtlichen Kosten von I aktualisieren. Wenn die Datenmenge klein ist, sagt sie uns nicht genau, was die Kosten sind, nur dass es groß genug ist, um es zu reparieren.

Noch eine andere Art, es zu betrachten, heißt die Regel der Nachfolge . Wenn du eine Münze 2 Mal umdrehst und sie beide Male auftaucht, was sagt dir das über die wahrscheinliche Gewichtung der Münze? Der angesehene Weg zu antworten ist zu sagen, dass es eine Beta-Verteilung ist, mit Durchschnittswert (Anzahl der Treffer + 1) / (Anzahl der Versuche + 2) = (2 + 1) / (2 + 2) = 75%.

(Der Schlüssel ist, dass wir es mehr als einmal sehen. Wenn wir es nur einmal sehen, sagt uns das nicht viel, außer dass f > 0 ist.)

So kann uns sogar eine sehr kleine Anzahl von Samples viel über die Kosten von Anweisungen erzählen. (Und es wird sie mit einer Häufigkeit sehen, die im Durchschnitt proportional zu ihren Kosten ist. Wenn n Proben genommen werden und f die Kosten sind, dann werde I auf nf+/-sqrt(nf(1-f)) Proben erscheinen , n=10 , f=0.3 , das sind 3+/-1.4 Proben.)

HINZUGEFÜGT, um ein intuitives Gefühl für den Unterschied zwischen Messung und zufälliger Stapelabtastung zu geben:
Es gibt jetzt Profiler, die den Stack testen, sogar zur Wanduhrzeit, aber was herauskommt, sind Messungen (oder Hot-Path oder Hot-Spot, aus dem sich ein "Flaschenhals" leicht verstecken kann). Was sie dir nicht zeigen (und das konnten sie leicht), sind die eigentlichen Samples. Und wenn Ihr Ziel darin besteht, den Engpass zu finden , müssen Sie im Durchschnitt 2 davon durch den Bruchteil der benötigten Zeit dividieren. Wenn es also 30% der Zeit dauert, zeigt 2 / .3 = 6,7 Proben im Durchschnitt, und die Wahrscheinlichkeit, dass 20 Proben zeigen, ist 99,2%.

Hier sehen Sie den Unterschied zwischen der Untersuchung von Messungen und der Untersuchung von Stapelproben. Der Engpass könnte ein großer Fleck wie dieser sein, oder viele kleine, es macht keinen Unterschied.

Die Messung ist horizontal; es zeigt Ihnen, welchen Bruchteil der Zeit bestimmte Routinen benötigen. Sampling ist vertikal. Wenn es irgendeinen Weg gibt, zu vermeiden, was das ganze Programm in diesem Moment macht, und wenn Sie es bei einer zweiten Probe sehen , haben Sie den Engpass gefunden. Das macht den Unterschied - den ganzen Grund für die aufgewendete Zeit zu sehen, nicht nur wie viel.


Die Antwort auf valgrind --tool=callgrind ist nicht vollständig ohne einige Optionen. Normalerweise möchten wir 10 Minuten langsame Startzeit unter Valgrind nicht profilieren und wollen unser Programm profilieren, wenn es eine Aufgabe erledigt.

Also das ist, was ich empfehle. Programm zuerst ausführen:

valgrind --tool=callgrind --dump-instr=yes -v --instr-atstart=no ./binary > tmp

Wenn es nun funktioniert und wir mit dem Profiling beginnen wollen, sollten wir in einem anderen Fenster laufen:

callgrind_control -i on

Dadurch wird das Profiling aktiviert. Um es auszuschalten und die ganze Aufgabe zu stoppen, könnten wir verwenden:

callgrind_control -k

Jetzt haben wir einige Dateien namens callgrind.out. * Im aktuellen Verzeichnis. Um Profilergebnisse zu sehen, verwenden Sie:

kcachegrind callgrind.out.*

Ich empfehle im nächsten Fenster, auf die Spaltenüberschrift "Self" zu klicken, sonst zeigt es, dass "main ()" die zeitaufwendigste Aufgabe ist. "Self" zeigt an, wie viel jede Funktion selbst Zeit braucht, nicht zusammen mit Abhängigen.


Ich würde Valgrind und Callgrind als Basis für meine Profiling Tool Suite verwenden. Es ist wichtig zu wissen, dass Valgrind im Grunde eine virtuelle Maschine ist:

(wikipedia) Valgrind ist im Wesentlichen eine virtuelle Maschine, die Just-in-Time (JIT) -Kompilierungstechniken einschließlich dynamischer Neukompilierung verwendet. Nichts aus dem ursprünglichen Programm wird jemals direkt auf dem Host-Prozessor ausgeführt. Stattdessen übersetzt Valgrind das Programm zunächst in eine temporäre, einfachere Form namens Intermediate Representation (IR), bei der es sich um eine prozessor-neutrale SSA-basierte Form handelt. Nach der Konvertierung kann ein Werkzeug (siehe unten) beliebige Transformationen im IR vornehmen, bevor Valgrind das IR zurück in den Maschinencode übersetzt und den Hostprozessor laufen lässt.

Callgrind ist ein Profiler darauf aufgebaut. Der Hauptvorteil ist, dass Sie Ihre Anwendung nicht stundenlang ausführen müssen, um zuverlässige Ergebnisse zu erhalten. Schon eine Sekunde Laufzeit reicht aus, um solide, zuverlässige Ergebnisse zu erhalten, da Callgrind ein nicht-untersuchender Profiler ist.

Ein weiteres Werkzeug, das auf Valgrind aufbaut, ist Massif. Ich verwende es, um die Speicherbelegung des Heapspeichers zu profilieren. Es funktioniert großartig. Was es tut, ist, dass es Ihnen Schnappschüsse der Speichernutzung gibt - detaillierte Informationen WAS hält, WAS Prozentsatz des Gedächtnisses, und WHO hatte es dort hingelegt. Solche Informationen sind zu verschiedenen Zeitpunkten des Anwendungslaufs verfügbar.


Ich nehme an, Sie verwenden GCC. Die Standardlösung wäre ein Profil mit gprof .

-pg Sie darauf, dass -pg vor der Profilerstellung -pg zur Kompilierung hinzufügen:

cc -o myprog myprog.c utils.c -g -pg

Ich habe es noch nicht ausprobiert, aber ich habe gute Dinge über google-perftools . Es ist definitiv einen Versuch wert.

Verwandte Frage here .

Ein paar andere Schlagworte, wenn gprof nicht die Arbeit für Sie VTune : Valgrind , Intel VTune , Sun DTrace .


Dies ist eine Antwort auf Nazgobs Gprof-Antwort .

Ich habe Gprof in den letzten Tagen verwendet und habe bereits drei signifikante Einschränkungen gefunden, von denen ich (noch) nirgendwo anders dokumentiert habe:

  1. Es funktioniert nicht ordnungsgemäß auf Multithread-Code, es sei denn, Sie verwenden eine workaround

  2. Das Aufrufdiagramm wird durch Funktionszeiger verwirrt. Beispiel: Ich habe eine Funktion namens multithread (), die es mir ermöglicht, eine bestimmte Funktion über ein spezifiziertes Array (beide als Argumente übergeben) zu migrieren. Gprof betrachtet jedoch alle Aufrufe von Multithread () als äquivalent für die Rechenzeit, die für Kinder aufgewendet wird. Da einige Funktionen, die ich an Multithread () übergebe, viel länger dauern als andere, sind meine Aufrufgraphen meistens nutzlos. (Für diejenigen, die sich fragen, ob Threading das Problem hier ist: Nein, multithread () kann optional und tat in diesem Fall alles sequenziell nur auf dem aufrufenden Thread).

  3. Es heißt here dass "... die Anzahl der Rufnummern durch Zählen, nicht durch Abtasten abgeleitet wird. Sie sind vollkommen genau ...". Aber ich finde mein Anrufdiagramm, das mir 5345859132 + 784984078 als Anrufstatistik zu meiner am meisten genannten Funktion gibt, wo die erste Nummer direkte Anrufe sein sollen, und die zweiten rekursiven Anrufe (die alle von selbst sind). Da dies bedeutete, dass ich einen Fehler hatte, steckte ich lange (64-Bit-) Zähler in den Code und führte denselben Lauf erneut aus. Meine zählt: 5345859132 direkt und 78094395406 selbstrekursive Aufrufe. Es gibt viele Stellen dort, also werde ich darauf hinweisen, dass die rekursiven Aufrufe, die ich messe, 78bn sind, gegenüber 784m von Gprof: ein Faktor von 100 verschieden. Beide Läufe waren single-threaded und nicht optimierter Code, einer kompiliert -g und der andere -pg.

Das war GNU Gprof (GNU Binutils für Debian) 2.18.0.20080103 läuft unter 64-Bit Debian Lenny, wenn das irgendjemandem hilft.


Ich verwende httpie

$ pip install httpie

Und du kannst es so benutzen

 $ http PUT localhost:8001/api/v1/ports/my 
 HTTP/1.1 200 OK
 Connection: keep-alive
 Content-Length: 93
 Content-Type: application/json
 Date: Fri, 06 Mar 2015 02:46:41 GMT
 Server: nginx/1.4.6 (Ubuntu)
 X-Powered-By: HHVM/3.5.1

 {
     "data": [], 
     "message": "Failed to manage ports in 'my'. Request body is empty", 
     "success": false
 }




c++ unix profiling