Vraag Hoe kan ik de C ++ -code profileren die op Linux wordt uitgevoerd?


Ik heb een C ++ -toepassing die op Linux draait en die ik momenteel aan het optimaliseren ben. Hoe kan ik bepalen welke gedeelten van mijn code langzaam worden weergegeven?


1498
2017-12-17 20:29


oorsprong


antwoorden:


Als je een profiler wilt gebruiken, gebruik dan een van de voorgestelde.

Als u echter haast hebt en u uw programma handmatig onder de debugger kunt onderbreken terwijl het subjectief langzaam is, is er een eenvoudige manier om prestatieproblemen te vinden.

Stop het een paar keer en stop elke keer naar de call-stack. Als er een code is die een bepaald percentage van de tijd, 20% of 50% of wat dan ook verspilt, is dat de kans dat je het zult vangen in de handeling op elk monster. Dus dat is ongeveer het percentage monsters waarop u het zult zien. Er is geen opgeleid giswerk vereist. Als je een gok hebt over wat het probleem is, zal dit het bewijs zijn of weerleggen.

U kunt meerdere prestatieproblemen van verschillende grootten hebben. Als je een van deze schoonmaakt, zullen de overblijvende een groter percentage nemen en gemakkelijker te herkennen zijn bij volgende passen. Deze vergrotingseffect, wanneer samengesteld over meerdere problemen, kan leiden tot echt enorme snelheidsfactoren.

Waarschuwing: programmeurs zijn vaak sceptisch over deze techniek, tenzij ze het zelf hebben gebruikt. Ze zullen zeggen dat profilers je deze informatie geven, maar dat is alleen het geval als ze de hele call stack samplen en je vervolgens een willekeurige reeks samples laten onderzoeken. (De samenvattingen zijn waar het inzicht verloren gaat.) Oproepgrafieken geven u niet dezelfde informatie, omdat

  1. zij vatten niet samen op instructieniveau, en
  2. ze geven verwarrende samenvattingen in de aanwezigheid van recursie.

Ze zullen ook zeggen dat het alleen werkt op speelgoedprogramma's, terwijl het eigenlijk werkt op elk programma en het lijkt beter te werken op grotere programma's, omdat ze de neiging hebben om meer problemen te vinden. Ze zullen zeggen dat het soms dingen vindt die geen problemen zijn, maar dat is alleen waar als je iets ziet een keer. Als u een probleem op meerdere monsters ziet, is het echt.

Postscriptum Dit kan ook worden gedaan op multi-thread-programma's als er een manier is om call-stack-samples van de thread pool te verzamelen op een tijdstip, zoals er in Java is.

P.P.S Hoe meer lagen abstractie je in je software hebt, hoe groter de kans dat je merkt dat dit de oorzaak is van prestatieproblemen (en de mogelijkheid om sneller te worden).

Toegevoegd: het is misschien niet voor de hand liggend, maar de stack-sampling-techniek werkt even goed in de aanwezigheid van recursie. De reden is dat de tijd die zou worden bespaard door het verwijderen van een instructie, wordt benaderd door de fractie van de monsters die het bevat, ongeacht het aantal keren dat het binnen een steekproef kan voorkomen.

Een ander bezwaar dat ik vaak hoor is: "Het zal ergens willekeurig stoppen, en het zal het echte probleem missen". Dit komt door het hebben van een voorafgaand concept van wat het echte probleem is. Een belangrijk kenmerk van prestatieproblemen is dat ze de verwachtingen tarten. Bemonstering vertelt je dat er iets aan de hand is, en je eerste reactie is ongeloof. Dat is normaal, maar je kunt er zeker van zijn dat als het een probleem vindt het echt is, en omgekeerd.

TOEGEVOEGD: Laat me een Bayesiaanse uitleg geven over hoe het werkt. Stel dat er wat instructie is I (oproep of anderszins) die op de oproepstapel een fractie bevat f van de tijd (en dus kosten zoveel). Voor de eenvoud, stel dat we niet weten wat f is, maar neem aan dat het 0,1, 0,2, 0,3, ... 0,9, 1,0 is en de eerdere waarschijnlijkheid van elk van deze mogelijkheden is 0,1, dus al deze kosten zijn a priori even waarschijnlijk.

Stel dat we maar twee stapelvoorbeelden nemen en we zien instructies I op beide monsters, aangewezen observatie o=2/2. Dit geeft ons nieuwe schattingen van de frequentie f van I, volgens dit:

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                

De laatste kolom zegt dat bijvoorbeeld de kans dat f > = 0,5 is 92%, hoger dan de eerdere veronderstelling van 60%.

Stel dat de eerdere aannames anders zijn. Stel dat we aannemen dat P (f = 0.1) .991 is (vrijwel zeker), en alle andere mogelijkheden zijn bijna onmogelijk (0.001). Met andere woorden, onze voorafgaande zekerheid is dat I is goedkoop. Dan krijgen we:

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                

Nu staat er dat P (f> = 0,5) 26% is, een stijging ten opzichte van de eerdere veronderstelling van 0,6%. Met Bayes kunnen we onze schatting van de waarschijnlijke kosten van bijwerken I. Als de hoeveelheid gegevens klein is, vertelt dit ons niet precies wat de kosten zijn, alleen dat het groot genoeg is om de moeite waard te zijn.

Nog een andere manier om ernaar te kijken is de Regel van opvolging. Als je een munt twee keer omdraait en beide keren omhoog gaat, wat zegt dat dan over de waarschijnlijke weging van de munt? De gerespecteerde manier om te antwoorden is om te zeggen dat het een bètadistributie is, met gemiddelde waarde (aantal hits + 1) / (aantal pogingen + 2) = (2 + 1) / (2 + 2) = 75%.

(De sleutel is dat we zien I meerdere keren. Als we het slechts één keer zien, zegt dat niet veel, behalve dat f > 0.)

Dus, zelfs een heel klein aantal voorbeelden kan ons veel vertellen over de kosten van instructies die het ziet. (En het zal hen met een frequentie zien die, gemiddeld genomen, evenredig is met hun kosten n er worden monsters genomen en f zijn de kosten dan I verschijnt op nf+/-sqrt(nf(1-f)) samples. Voorbeeld, n=10, f=0.3, dat is 3+/-1.4 samples.)


TOEGEVOEGD, om een ​​intuïtief gevoel te geven voor het verschil tussen meten en willekeurige stapelbemonstering:
Er zijn nu profilers die de stapel samplen, zelfs op de klok, maar wat komt eruit is metingen (of hot path, of hot spot, van waaruit een "knelpunt" gemakkelijk kan verbergen). Wat ze je niet laten zien (en dat zouden ze gemakkelijk kunnen) zijn de eigenlijke monsters zelf. En als je doel is om vind het knelpunt, het aantal dat je moet zien is, gemiddeld, 2 gedeeld door de fractie van tijd die het kost. Dus als het 30% van de tijd in beslag neemt, zullen gemiddeld 2 / .3 = 6.7 samples dit laten zien, en de kans dat 20 samples het zullen laten zien is 99.2%.

Hier ziet u een illustratie van het verschil tussen het onderzoeken van metingen en het onderzoeken van stapelvoorbeelden. De bottleneck kan een grote klodder zijn, of een paar kleine, het maakt geen verschil.

enter image description here

Meting is horizontaal; het vertelt je welk deel van de tijd specifieke routines nemen. Bemonstering is verticaal. Als er een manier is om te vermijden wat het hele programma op dat moment doet, en als je het op een tweede monster ziet, je hebt de bottleneck gevonden. Dat is wat het verschil maakt - de hele reden voor tijd zien besteed, niet alleen hoeveel.


1192
2018-04-21 04:09



Je kunt gebruiken Valgrind met de volgende opties

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

Het genereert een bestand met de naam callgrind.out.x. Je kunt dan gebruiken kcachegrind hulpmiddel om dit bestand te lezen. Het geeft je een grafische analyse van dingen met resultaten zoals welke lijnen hoeveel kosten.


472
2017-12-17 20:34



Ik neem aan dat je GCC gebruikt. De standaardoplossing zou zijn om te profileren met gprof.

Zorg ervoor dat u toevoegt -pg compilatie voor profilering:

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

Ik heb het nog niet geprobeerd maar ik heb goede dingen gehoord over google-perftools. Het is zeker het proberen waard.

Gerelateerde vraag hier.

Een paar andere buzzwords als gprof doet het werk niet voor u: Valgrind, Intel Vtune, Zon DTrace.


296
2017-08-17 11:48



Nieuwere kernels (bijvoorbeeld de nieuwste Ubuntu-kernels) worden geleverd met de nieuwe 'perf'-tools (apt-get install linux-tools) AKA perf_events.

Deze komen met klassieke steekproevenprofilers (man-page) evenals het geweldige Tijdschema!

Het belangrijkste is dat deze hulpmiddelen kunnen zijn systeemprofilering en niet alleen procesprofilering - ze kunnen de interactie tussen threads, processen en de kernel laten zien en je de planning en I / O-afhankelijkheden tussen processen laten begrijpen.

Alt text


216
2018-05-22 21:44



Ik zou Valgrind en Callgrind gebruiken als basis voor mijn toolset voor profilering. Wat belangrijk is om te weten is dat Valgrind in feite een virtuele machine is:

(wikipedia) Valgrind is in wezen een virtueel   machine met just-in-time (JIT)   compilatietechnieken, inclusief   dynamische hercompilatie. Niets van   het originele programma wordt ooit uitgevoerd   rechtstreeks op de hostprocessor.   In plaats daarvan vertaalt Valgrind eerst de   programmeer in een tijdelijke, eenvoudiger vorm   Intermediate Representation genoemd   (IR), dat een processorneutraal is,   SSA-gebaseerde vorm. Na de conversie,   een tool (zie hieronder) is gratis   welke transformaties het ook zou willen   op de IR, voordat Valgrind vertaalt   de IR terug in machinecode en laat   de hostprocessor voert het uit.

Callgrind is een profiler die daarop is voortgebouwd. Het belangrijkste voordeel is dat u uw aplicatie urenlang niet hoeft uit te voeren om een ​​betrouwbaar resultaat te krijgen. Zelfs een seconde run is voldoende om solide, betrouwbare resultaten te krijgen, want Callgrind is een niet-indringende profiler.

Een ander hulpmiddel dat op Valgrind is gebouwd, is Massif. Ik gebruik het om het gebruik van heap-geheugen te profileren. Het werkt geweldig. Wat het doet, is dat het snapshots geeft van het geheugengebruik - gedetailleerde informatie WAT in welk percentage van het geheugen zit en WIE het daar had neergelegd. Dergelijke informatie is beschikbaar op verschillende tijdstippen van applicatierun.


65
2018-06-30 19:30



Dit is een reactie op Nazgob's Gprof antwoord.

Ik gebruik Gprof de afgelopen dagen en heb al drie belangrijke beperkingen gevonden, waarvan ik er (nog) nergens anders (nog) niet eerder documentatie van heb gezien:

  1. Het werkt niet correct op code met meerdere threads, tenzij u een gebruikt tijdelijke oplossing

  2. De call-grafiek raakt in de war door functie-aanwijzers. Voorbeeld: ik heb een functie met de naam multithread () waarmee ik een gespecificeerde functie over een opgegeven array (beide doorgegeven als argumenten) multi-thread. Gprof beschouwt echter alle aanroepen van multithread () als gelijkwaardig voor het berekenen van de tijd die aan kinderen wordt besteed. Aangezien sommige functies die ik door geef aan multithread () veel langer duren dan andere, zijn mijn callgrafieken meestal nutteloos. (Voor degenen die zich afvragen of draadsnijden hier het probleem is: nee, multithread () kan optioneel, en deed in dit geval, alles sequentieel alleen op de aanroepende thread uitvoeren).

  3. Het zegt hier dat "... de cijfers voor het aantal oproepen worden afgeleid door te tellen, niet door steekproeven. Ze zijn volledig nauwkeurig ...". Toch vind ik mijn call-grafiek die me 5345859132 + 784984078 geeft als gespreksstatistieken voor mijn meest genoemde functie, waarbij het eerste nummer verondersteld wordt directe oproepen te zijn, en de tweede recursieve oproepen (die allemaal van zichzelf zijn). Omdat dit impliceerde dat ik een fout had, heb ik lange (64-bits) tellers in de code geplaatst en deed ik hetzelfde opnieuw. Mijn telt: 5345859132 directe en 78094395406 zelf recursieve oproepen. Er zijn daar veel cijfers, dus ik wil erop wijzen dat de recursieve oproepen die ik meet 78 miljard zijn, tegenover 784 meter van Gprof: een factor 100 anders. Beide runs waren single threaded en unoptimised code, de ene gecompileerd -g en de ander -pg.

Dit was GNU gprof (GNU Binutils voor Debian) 2.18.0.20080103 draait onder 64-bit Debian Lenny, als dat iemand helpt.


49
2018-06-08 08:01



Het antwoord om uit te voeren valgrind --tool=callgrind is niet helemaal compleet zonder een aantal opties. We willen meestal niet 10 minuten langzame opstarttijd onder Valgrind profileren en willen ons programma profileren wanneer het een bepaalde taak uitvoert.

Dus dit is wat ik aanbeveel. Voer eerst het programma uit:

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

Wanneer het nu werkt en we willen beginnen met profileren, zouden we in een ander venster moeten lopen:

callgrind_control -i on

Hiermee wordt profilering ingeschakeld. Om het uit te schakelen en de hele taak te stoppen, kunnen we gebruiken:

callgrind_control -k

Nu hebben we enkele bestanden met de naam callgrind.out. * In de huidige directory. Om profileringsresultaten te zien, gebruik:

kcachegrind callgrind.out.*

Ik raad aan om in het volgende venster op de kolomkop "Zelf" te klikken, anders wordt weergegeven dat "main ()" de meest tijdrovende taak is. "Zelf" laat zien hoeveel elke functie zelf tijd kostte, niet samen met personen ten laste.


47
2018-02-23 21:28