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


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?


1498
2017-12-17 20:29


Ursprung


Antworten:


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. Dies Vergrößerungseffektkann, wenn es über mehrere Probleme zusammengesetzt ist, 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 etwas sehen Einmal. Wenn Sie ein Problem bei mehr als einem Beispiel sehen, ist es real.

P.S. Dies kann auch in Multithread-Programmen durchgeführt werden, wenn es eine Möglichkeit gibt, Call-Stack-Beispiele des Thread-Pools zu einem bestimmten Zeitpunkt zu sammeln, wie es in Java der Fall ist.

P.P.S Grob gesagt, je mehr Abstraktionsebenen 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 anderer Einwand, den ich oft höre, ist:Es wird an einem zufälligen Ort aufhören und es wird das eigentliche 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 Anweisungen I (Anruf oder auf andere Weise), die auf dem Aufrufstapel einen Bruchteil hat f der Zeit (und kostet so viel). Nehmen Sie zur Vereinfachung an, wir wissen nicht was f ist, aber angenommen, es ist entweder 0,1, 0,2, 0,3, ... 0,9, 1,0, und die vorherige Wahrscheinlichkeit jeder dieser Möglichkeiten ist 0,1, so dass alle diese Kosten a priori gleich wahrscheinlich sind.

Nehmen wir an, wir nehmen nur zwei Stack-Samples und sehen Anweisungen I an beiden Proben, benannte Beobachtung o=2/2. Dies gibt uns neue Schätzungen der Häufigkeit f von I, demzufolge:

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 sagt, dass zum Beispiel die Wahrscheinlichkeit, dass f > = 0,5 ist 92%, 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 das I ist günstig. 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%. Bayes erlaubt uns, unsere Schätzung der voraussichtlichen Kosten von I. 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 sehen I mehr als einmal. Wenn wir es nur einmal sehen, sagt uns das nur wenig f > 0.)

So kann uns sogar eine sehr kleine Anzahl von Samples viel über die Kosten von Anweisungen erzählen. (Und sie wird sie im Durchschnitt proportional zu ihren Kosten sehen. Wenn n Proben werden genommen, und f ist die Kosten, dann I erscheint auf nf+/-sqrt(nf(1-f)) Proben. Beispiel, n=10, f=0.3, das ist 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 samplen, sogar zur Wanduhrzeit, aber was kommt heraus ist Messungen (oder Hot-Path, oder Hot-Spot, von denen sich ein "Flaschenhals" leicht verstecken kann). Was sie dir nicht zeigen (und das konnten sie leicht), sind die eigentlichen Samples. Und wenn dein Ziel ist finden der Engpass, die Anzahl von ihnen, die Sie sehen müssen, ist, im Durchschnitt, 2 geteilt durch den Bruchteil der Zeit, die es braucht. 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.

enter image description here

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 tut, und wenn Sie es bei einer zweiten Probe sehenDu hast den Flaschenhals gefunden. Das macht den Unterschied - den ganzen Grund für die aufgewendete Zeit zu sehen, nicht nur wie viel.


1192
2018-04-21 04:09



Sie können verwenden Valgrind mit den folgenden Optionen

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

Es erzeugt eine Datei namens callgrind.out.x. Sie können dann verwenden kcachegrind Werkzeug, um diese Datei zu lesen. Es wird Ihnen eine grafische Analyse von Dingen mit Ergebnissen geben, wie welche Zeilen wie viel kosten.


472
2017-12-17 20:34



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

Achten Sie darauf, hinzuzufügen -pg zur Kompilierung vor dem Profiling:

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

Ich habe es noch nicht ausprobiert, aber ich habe gute Dinge gehört google-puttools. Es ist definitiv einen Versuch wert.

Verwandte Frage Hier.

Ein paar andere Schlagworte, wenn gprof macht die Arbeit für dich nicht: Valgrind, Intel VTune, Sonne DTrace.


296
2017-08-17 11:48



Neuere Kernel (z. B. die neuesten Ubuntu-Kernel) enthalten die neuen "Perf" -Tools (apt-get install linux-tools) AKA Perf_Ereignisse.

Diese kommen mit klassischen Sampling Profilern (Man-Seite) genauso wie das geniale Zeitdiagramm!

Das Wichtigste ist, dass diese Werkzeuge sein können Systemprofilierung und nicht nur die Profilerstellung - 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.

Alt text


216
2018-05-22 21:44



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 mit Just-In-Time (JIT)   Kompilierungstechniken, einschließlich   dynamische Neukompilierung. Nichts von   Das ursprüngliche Programm wird jemals ausgeführt   direkt auf dem Host-Prozessor.   Stattdessen übersetzt Valgrind zuerst die   Programm in eine temporäre, einfachere Form   Zwischenrepräsentation genannt   (IR), die eine prozessorneutrale ist,   SSA-basierte Form. Nach der Konvertierung   Ein Werkzeug (siehe unten) ist frei zu tun   welche Transformationen es auch haben mag   im IR, bevor Valgrind übersetzt   das IR zurück in Maschinencode und lässt   der Host-Prozessor führt es aus.

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, verlässliche Ergebnisse zu erzielen, denn Callgrind ist ein nicht probierend Profiler.

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.


65
2018-06-30 19:30



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. Bei Multi-Thread-Code funktioniert es nicht richtig, es sei denn, Sie verwenden a Problemumgehung

  2. Das Aufrufdiagramm wird durch Funktionszeiger verwirrt. Beispiel: Ich habe eine Funktion namens multithread (), die es mir ermöglicht, eine angegebene 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 sequentiell nur auf dem aufrufenden Thread).

  3. Es sagt Hier 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.


49
2018-06-08 08:01



Die Antwort zum laufen valgrind --tool=callgrind ist nicht ganz ohne einige Optionen abgeschlossen. 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.


47
2018-02-23 21:28



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)


8
2018-03-17 12:20



Für Singlethread-Programme, die Sie verwenden können igprof, Der Ignorante Profiler: 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 in jeder Funktion, entweder kumulativ oder pro Funktion.


4
2017-11-28 18:21



Dies sind die beiden Methoden, mit denen ich meinen Code beschleunigen kann:

Für CPU-gebundene Anwendungen:

  1. Verwenden Sie einen Profiler im DEBUG-Modus, um fragwürdige Teile Ihres Codes zu identifizieren
  2. Wechseln Sie dann in den RELEASE-Modus und kommentieren Sie die fragwürdigen Abschnitte Ihres Codes (stub it nothing), bis Sie Änderungen in der Leistung sehen.

Für E / A-gebundene Anwendungen:

  1. Verwenden Sie einen Profiler im RELEASE-Modus, um fragwürdige Teile Ihres Codes zu identifizieren.

N.B.

Wenn Sie keinen Profiler haben, verwenden Sie den Profiler des armen Mannes. Hit Pause beim Debuggen Ihrer Anwendung. Die meisten Entwickler-Suites werden mit den kommentierten Zeilennummern in die Assembly einsteigen. Sie landen statistisch wahrscheinlich in einer Region, in der die meisten CPU-Zyklen verbraucht werden.

Für CPU, der Grund für das Profiling in DEBUGGEN Modus ist, weil wenn Sie versuchten, Profiling in FREISETZUNG Modus, der Compiler wird Mathe zu reduzieren, Schleifen und Inline-Funktionen zu vektorisieren, die dazu neigt, Ihren Code in eine nicht abbildbare Unordnung zu globalisieren, wenn es zusammengebaut wird. Ein nicht abbildbares Chaos bedeutet, dass Ihr Profiler nicht in der Lage ist, eindeutig zu identifizieren, was so lange dauert, da die Assembly möglicherweise nicht dem Quellcode entspricht, der unter Optimierung steht. Wenn Sie die Leistung (z. B. Timing-empfindlich) von benötigen FREISETZUNG deaktivieren Sie die Debugger-Funktionen nach Bedarf, um eine brauchbare Leistung zu erhalten.

Bei E / A-Bindungen kann der Profiler weiterhin E / A-Vorgänge in definieren FREISETZUNG Modus, da E / A-Operationen entweder extern mit einer gemeinsam genutzten Bibliothek verbunden sind (meistens) oder im schlimmsten Fall zu einem sys-Aufruf-Interrupt-Vektor führt (der auch vom Profiler leicht zu erkennen ist).


2