Come posso profilare il codice C ++ in esecuzione su Linux?


1816

Ho un'applicazione C ++, in esecuzione su Linux, che sto ottimizzando. Come posso individuare quali aree del mio codice sono in esecuzione lentamente?


27
Se fornirai più dati sul tuo stack di sviluppo, potresti ottenere risposte migliori. Ci sono profiler di Intel e Sun ma devi usare i loro compilatori. È un'opzione?
Nazgob,

2
Si è già risposto al seguente link: stackoverflow.com/questions/2497211/…
Kapil Gupta

4
La maggior parte delle risposte sono codeprofiler. Tuttavia, l'inversione di priorità, l'aliasing della cache, la contesa di risorse, ecc. Possono tutti essere fattori di ottimizzazione e prestazioni. Penso che le persone leggano le informazioni nel mio codice lento . Le domande frequenti fanno riferimento a questa discussione.
rumore senza arti


3
Usavo pstack in modo casuale, la maggior parte delle volte stampava lo stack più tipico in cui il programma è il più delle volte, indicando quindi il collo di bottiglia.
Jose Manuel Gomez Alvarez,

Risposte:


1406

Se il tuo obiettivo è usare un profiler, usa uno di quelli suggeriti.

Tuttavia, se hai fretta e puoi interrompere manualmente il programma sotto il debugger mentre è soggettivamente lento, c'è un modo semplice per trovare problemi di prestazioni.

Basta fermarlo più volte e ogni volta guarda lo stack di chiamate. Se esiste un codice che sta sprecando una percentuale del tempo, 20% o 50% o altro, questa è la probabilità che lo catturi nell'atto su ciascun campione. Quindi, questa è approssimativamente la percentuale di campioni su cui lo vedrai. Non sono necessarie congetture istruite. Se hai un'ipotesi su quale sia il problema, questo lo dimostrerà o smentirà.

Potresti avere più problemi di prestazioni di dimensioni diverse. Se ne ripulisci uno, quelli rimanenti avranno una percentuale maggiore e saranno più facili da individuare, nei passaggi successivi. Questo effetto di ingrandimento , se combinato con più problemi, può portare a fattori di accelerazione davvero enormi.

Avvertenza : i programmatori tendono ad essere scettici su questa tecnica se non l'hanno usata da soli. Diranno che i profiler ti forniscono queste informazioni, ma ciò è vero solo se campionano l'intero stack di chiamate e quindi ti permettono di esaminare un set casuale di campioni. (I riepiloghi sono dove si perde la visione.) I grafici delle chiamate non ti danno le stesse informazioni, perché

  1. Non riassumono a livello di istruzione e
  2. Danno riassunti confusi in presenza di ricorsione.

Diranno anche che funziona solo su programmi giocattolo, quando in realtà funziona su qualsiasi programma e sembra funzionare meglio su programmi più grandi, perché tendono ad avere più problemi da trovare. Diranno che a volte trova cose che non sono problemi, ma è vero solo se vedi qualcosa una volta . Se vedi un problema su più di un campione, è reale.

PS Questo può essere fatto anche su programmi multi-thread se esiste un modo per raccogliere campioni di stack di chiamate del pool di thread in un determinato momento, come avviene in Java.

PPS In generale, più strati di astrazione hai nel tuo software, più è probabile che tu possa scoprire che questa è la causa di problemi di prestazioni (e l'opportunità di accelerare).

aggiunto : Potrebbe non essere ovvio, ma la tecnica di campionamento dello stack funziona ugualmente bene in presenza di ricorsione. Il motivo è che il tempo che verrebbe risparmiato rimuovendo un'istruzione è approssimato dalla frazione di campioni che lo contengono, indipendentemente dal numero di volte che può verificarsi all'interno di un campione.

Un'altra obiezione che sento spesso è: " Si fermerà da qualche parte a caso e mancherà il vero problema ". Ciò deriva dall'avere un precedente concetto di quale sia il vero problema. Una proprietà chiave dei problemi di prestazione è che sfidano le aspettative. Il campionamento ti dice che qualcosa è un problema e la tua prima reazione è incredulità. È naturale, ma puoi essere sicuro che se trova un problema è reale, e viceversa.

Aggiunto : lasciami fare una spiegazione bayesiana di come funziona. Supponiamo che ci siano alcune istruzioni I(call o di altro tipo) che sono nello stack di chiamate una frazione fdel tempo (e quindi costano così tanto). Per semplicità, supponiamo di non sapere cosa fsia, ma supponiamo che sia 0,1, 0,2, 0,3, ... 0,9, 1,0 e che la probabilità precedente di ciascuna di queste possibilità sia 0,1, quindi tutti questi costi sono ugualmente probabili a priori.

Supponiamo quindi di prelevare solo 2 campioni di stack e di vedere istruzioni Isu entrambi i campioni, osservazione designata o=2/2. Questo ci dà nuove stime della frequenza fdi I, secondo questo:

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 | o=2/2)

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                

L'ultima colonna indica che, ad esempio, la probabilità che f> = 0,5 sia del 92%, in aumento rispetto al presupposto precedente del 60%.

Supponiamo che le ipotesi precedenti siano diverse. Supponiamo di assumere P(f=0.1)0,91 (quasi certo) e tutte le altre possibilità sono quasi impossibili (0,001). In altre parole, la nostra precedente certezza è che Iè economico. Quindi otteniamo:

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 | o=2/2)

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                

Ora dice che P(f >= 0.5)è del 26%, rispetto al presupposto precedente dello 0,6%. Quindi Bayes ci consente di aggiornare la nostra stima del probabile costo di I. Se la quantità di dati è piccola, non ci dice esattamente quale sia il costo, ma solo che è abbastanza grande da valere la pena di essere risolto.

Ancora un altro modo di vederlo è chiamato la regola della successione . Se lanci una moneta 2 volte, e esce testa entrambe le volte, cosa ti dice del probabile peso della moneta? Il modo rispettato di rispondere è quello di dire che è una distribuzione Beta, con un valore medio (number of hits + 1) / (number of tries + 2) = (2+1)/(2+2) = 75%.

(La chiave è che vediamo Ipiù di una volta. Se lo vediamo solo una volta, questo non ci dice molto tranne che f> 0).

Quindi, anche un numero molto limitato di campioni può dirci molto sul costo delle istruzioni che vede. (E sarà vederli con una frequenza, in media, proporzionale al loro costo. Se nvengono prelevati campioni, ed fè il costo, allora Iappariranno su nf+/-sqrt(nf(1-f))campioni. Esempio, n=10, f=0.3, cioè 3+/-1.4campioni.)


Aggiunto : per dare un'idea intuitiva della differenza tra la misurazione e il campionamento casuale dello stack:
ora ci sono profilatori che campionano lo stack, anche in tempo di clock, ma ciò che ne risulta sono le misurazioni (o hot path o hot spot, da cui un "collo di bottiglia" può facilmente nascondersi). Quello che non ti mostrano (e che potrebbero facilmente) sono gli stessi campioni stessi. E se il tuo obiettivo è trovare il collo di bottiglia, il numero di quelli che devi vedere è, in media , 2 diviso per la frazione di tempo necessaria. Quindi, se impiega il 30% del tempo, 2 / .3 = 6,7 campioni, in media, lo mostreranno e la probabilità che 20 campioni lo mostrino è del 99,2%.

Ecco un'illustrazione immediata della differenza tra l'esame delle misurazioni e l'esame dei campioni dello stack. Il collo di bottiglia potrebbe essere un grosso blob come questo, o numerosi piccoli blocchetti, non fa differenza.

inserisci qui la descrizione dell'immagine

La misurazione è orizzontale; ti dice quale frazione di tempo richiede routine specifiche. Il campionamento è verticale. Se c'è un modo per evitare ciò che l'intero programma sta facendo in quel momento, e se lo vedi su un secondo campione , hai trovato il collo di bottiglia. Questo è ciò che fa la differenza: vedere l'intera ragione del tempo trascorso, non solo quanto.


292
Questo è fondamentalmente un profiler di campionamento di un uomo povero, il che è fantastico, ma corri il rischio di una dimensione del campione troppo piccola che ti darà probabilmente risultati completamente falsi.
Crashworks,

100
@ Crash: non discuterò della parte "povero" :-) È vero che la precisione delle misurazioni statistiche richiede molti campioni, ma ci sono due obiettivi contrastanti: misurazione e localizzazione del problema. Mi sto concentrando su quest'ultimo, per il quale è necessaria la precisione della posizione, non la precisione della misura. Quindi, ad esempio, può esserci, a metà pila, una singola chiamata di funzione A (); che rappresenta il 50% del tempo, ma può trovarsi in un'altra grande funzione B, insieme a molte altre chiamate ad A () che non sono costose. Riepiloghi precisi dei tempi delle funzioni possono essere un indizio, ma ogni altro campione dello stack individuerà il problema.
Mike Dunlavey,

41
... il mondo sembra pensare che un grafico delle chiamate, annotato con conteggi delle chiamate e / o tempistica media, sia abbastanza buono. Non è. E la parte triste è, per coloro che campionano lo stack di chiamate, le informazioni più utili sono proprio di fronte a loro, ma le gettano via, nell'interesse delle "statistiche".
Mike Dunlavey,

30
Non intendo essere in disaccordo con la tua tecnica. Chiaramente mi affido molto ai profilatori di campionamento che camminano in pila. Sto solo sottolineando che ci sono alcuni strumenti che lo fanno in modo automatizzato ora, il che è importante quando hai superato il punto di ottenere una funzione dal 25% al ​​15% e devi abbatterla dall'1,2% al 0,6%.
Crashworks,

13
-1: Idea chiara, ma se vieni pagato per lavorare anche in un ambiente moderatamente orientato alle prestazioni, questo è uno spreco di tempo per tutti. Usa un vero profiler in modo che non dobbiamo venire dietro di te e risolvere i problemi reali.
Sam Harwell,

583

Puoi usare Valgrind con le seguenti opzioni

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

Genererà un file chiamato callgrind.out.x. È quindi possibile utilizzare lo kcachegrindstrumento per leggere questo file. Ti fornirà un'analisi grafica delle cose con risultati come quali linee costano quanto.


51
valgrind è fantastico, ma
tieni presente che

30
Dai un'occhiata anche a Gprof2Dot per un fantastico modo alternativo di visualizzare l'output. ./gprof2dot.py -f callgrind callgrind.out.x | dot -Tsvg -o output.svg
Sebastian,

2
@neves Sì Valgrind non è molto utile in termini di velocità per il profiling delle applicazioni "gstreamer" e "opencv" in tempo reale.
enthusiasticgeek

1
stackoverflow.com/questions/375913/... è soluton parziale per emissione velocità.
Tõnu Samuel,

3
@Sebastian: gprof2dotè ora qui: github.com/jrfonseca/gprof2dot
John Zwinck

348

Presumo che tu stia usando GCC. La soluzione standard sarebbe profilare con gprof .

Assicurati di aggiungere -pgalla compilation prima della profilazione:

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

Non l'ho ancora provato ma ho sentito cose positive su google-perftools . Vale sicuramente la pena provare.

Domanda correlata qui .

Qualche altra parola d'ordine se gprofnon fa il lavoro per te: Valgrind , Intel VTune , Sun DTrace .


3
Sono d'accordo che gprof è lo standard attuale. Solo una nota, tuttavia, Valgrind viene utilizzato per profilare perdite di memoria e altri aspetti relativi alla memoria dei programmi, non per l'ottimizzazione della velocità.
Bill the Lizard,

68
Bill, nella suite vaglrind puoi trovare callgrind e massiccio. Entrambi sono abbastanza utili per profilare le app
dario minonne il

7
@ Bill-the-lucertola: Alcuni commenti su gprof : stackoverflow.com/questions/1777556/alternatives-to-gprof/...
Mike Dunlavey

6
gprof -pg è solo un'approssimazione della profilazione del callstack. Inserisce chiamate mcount per tracciare quali funzioni stanno chiamando quali altre funzioni. Usa il campionamento basato sul tempo standard per, uh, il tempo. Quindi ripartisce i tempi campionati in una funzione foo () di nuovo ai chiamanti di foo (), in base al numero di chiamate. Quindi non distingue tra chiamate di costi diversi.
Krazy Glew,

1
Con clang / clang ++, si potrebbe prendere in considerazione l'uso del profiler della CPU di gperftools . Avvertenza: non l'ho fatto da solo.
einpoklum,

257

I kernel più recenti (ad esempio i kernel Ubuntu più recenti) vengono forniti con i nuovi strumenti "perf" ( apt-get install linux-tools) AKA perf_events .

Questi sono dotati di classici profili di campionamento ( man-page ) e di un fantastico diagramma temporale !

La cosa importante è che questi strumenti possono essere la profilazione del sistema e non solo la profilazione dei processi, ma possono mostrare l'interazione tra thread, processi e kernel e farti comprendere la pianificazione e le dipendenze I / O tra i processi.

Testo alternativo


12
Ottimo strumento! C'è comunque un modo per ottenere una tipica vista a "farfalla" che inizia dallo stile "main-> func1-> fun2"? Non riesco a capirlo ... perf reportsembra darmi i nomi delle funzioni con i genitori di chiamata ... (quindi è una specie di vista a farfalla invertita)
kizzx2

Will, può perf mostrare il diagramma temporale dell'attività del thread; con le informazioni sul numero di CPU aggiunte? Voglio vedere quando e quale thread era in esecuzione su ogni CPU.
Osgx,

2
@ kizzx2 - puoi usare gprof2dote perf script. Strumento molto bello!
dashesy,

2
Anche i kernel più recenti come 4.13 hanno eBPF per la profilazione. Vedi brendangregg.com/blog/2015-05-15/ebpf-one-small-step.html e brendangregg.com/ebpf.html
Andrew Stern,

Un'altra bella introduzione perfesiste su archive.li/9r927#selection-767.126-767.271 (Perché gli dei SO hanno deciso di eliminare quella pagina dalla knowledge base SO è oltre me ....)
ragerdl,

75

Vorrei utilizzare Valgrind e Callgrind come base per la mia suite di strumenti di profilazione. Ciò che è importante sapere è che Valgrind è fondamentalmente una macchina virtuale:

(wikipedia) Valgrind è in sostanza una macchina virtuale che utilizza tecniche di compilazione just-in-time (JIT), inclusa la ricompilazione dinamica. Nulla del programma originale viene mai eseguito direttamente sul processore host. Invece, Valgrind prima traduce il programma in una forma temporanea e più semplice chiamata Intermediate Representation (IR), che è una forma basata su SSA neutra dal processore. Dopo la conversione, uno strumento (vedi sotto) è libero di fare qualsiasi trasformazione vorrebbe su IR, prima che Valgrind traduca nuovamente l'IR in codice macchina e consenta al processore host di eseguirlo.

Callgrind è un profiler basato su questo. Il vantaggio principale è che non è necessario eseguire la domanda per ore per ottenere risultati affidabili. Anche un secondo giro è sufficiente per ottenere risultati affidabili, perché Callgrind è un profiler senza sondaggio .

Un altro strumento costruito su Valgrind è Massif. Lo uso per profilare l'utilizzo della memoria heap. Funziona benissimo. Quello che fa è che ti dà istantanee dell'uso della memoria - informazioni dettagliate COSA contiene QUALE percentuale di memoria e CHI l'ha messo lì. Tali informazioni sono disponibili in diversi momenti dell'esecuzione dell'applicazione.


70

La risposta da eseguire valgrind --tool=callgrindnon è del tutto completa senza alcune opzioni. Di solito non vogliamo profilare 10 minuti di tempo di avvio lento in Valgrind e vogliamo profilare il nostro programma quando sta eseguendo alcune attività.

Quindi questo è quello che raccomando. Esegui prima il programma:

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

Ora, quando funziona e vogliamo iniziare la profilazione, dovremmo eseguire un'altra finestra:

callgrind_control -i on

Questo attiva la profilazione. Per disattivarlo e interrompere l'intero compito, potremmo usare:

callgrind_control -k

Ora abbiamo alcuni file chiamati callgrind.out. * Nella directory corrente. Per vedere i risultati della profilazione utilizzare:

kcachegrind callgrind.out.*

Raccomando nella finestra successiva di fare clic sull'intestazione della colonna "Auto", altrimenti mostra che "main ()" richiede molto tempo. "Sé" mostra quanto ogni funzione stessa ha impiegato del tempo, non insieme ai dipendenti.


9
Ora per qualche motivo i file callgrind.out. * Erano sempre vuoti. L'esecuzione di callgrind_control -d è stata utile per forzare il dump dei dati su disco.
Tõnu Samuel,

3
Non posso. I miei soliti contesti sono qualcosa come MySQL o PHP o qualcosa di simile. Spesso non so nemmeno cosa voglio separare all'inizio.
Tõnu Samuel,

2
O nel mio caso il mio programma in realtà carica un sacco di dati in una cache LRU e non voglio profilarlo. Quindi carico forzatamente un sottoinsieme della cache all'avvio e profilo il codice usando solo quei dati (lasciando che OS + CPU gestiscano l'uso della memoria nella mia cache). Funziona, ma il caricamento di quella cache è lento e richiede molta CPU nel codice che sto cercando di profilare in un contesto diverso, quindi callgrind produce risultati gravemente inquinati.
Codice Abominator

2
è inoltre CALLGRIND_TOGGLE_COLLECTnecessario abilitare / disabilitare la raccolta a livello di codice; vedi stackoverflow.com/a/13700817/288875
Andre Holzner,

1
Wow, non sapevo che esistesse, grazie!
Vincent Fourmond,

59

Questa è una risposta alla risposta Gprof di Nazgob .

Ho usato Gprof negli ultimi due giorni e ho già trovato tre limiti significativi, uno dei quali non ho visto documentato altrove (ancora):

  1. Non funziona correttamente su codice multi-thread, a meno che non si utilizzi una soluzione alternativa

  2. Il grafico delle chiamate viene confuso dai puntatori a funzione. Esempio: ho una funzione chiamata multithread()che mi consente di eseguire il multithreading di una funzione specificata su un array specificato (entrambi passati come argomenti). Gprof, tuttavia, considera tutte le chiamate multithread()equivalenti ai fini del calcolo del tempo trascorso nei bambini. Poiché alcune funzioni che passo multithread()impiegano molto più tempo di altre, i grafici delle mie chiamate sono per lo più inutili. (A chi si chiede se il threading sia il problema qui: no, multithread()può facoltativamente, e in questo caso ha eseguito tutto in sequenza solo sul thread chiamante).

  3. Qui dice che "... le cifre del numero di chiamate derivano dal conteggio, non dal campionamento. Sono completamente accurate ...". Eppure trovo che il mio grafico delle chiamate mi dia 5345859132 + 784984078 come statistiche di chiamata alla mia funzione più chiamata, in cui il primo numero dovrebbe essere diretto e le seconde chiamate ricorsive (che sono tutte da sé). Dato che ciò implicava che avevo un bug, inserivo nel codice contatori lunghi (a 64 bit) e eseguivo di nuovo la stessa esecuzione. I miei conteggi: 5345859132 chiamate dirette e 78094395406 auto-ricorsive. Ci sono molte cifre lì, quindi sottolineo che le chiamate ricorsive che misuro sono 78 miliardi, contro i 784 m di Gprof: un fattore 100 diverso. Entrambe le esecuzioni erano codice a thread singolo e non ottimizzato, uno compilato -ge l'altro -pg.

Questo era GNU Gprof (GNU Binutils per Debian) 2.18.0.20080103 in esecuzione con Debian Lenny a 64 bit, se questo aiuta qualcuno.


Sì, esegue il campionamento, ma non per le cifre relative al numero di chiamate. È interessante notare che, seguendo il tuo link, alla fine mi ha portato a una versione aggiornata della pagina di manuale che ho collegato nel mio post, nuovo URL: sourceware.org/binutils/docs/gprof/… Questo ripete la citazione nella parte (iii) della mia risposta, ma dice anche "Nelle applicazioni multi-thread o nelle applicazioni single thread che si collegano con librerie multi-thread, i conteggi sono deterministici solo se la funzione di conteggio è thread-safe. (Nota: attenzione che la funzione di conteggio mcount in glibc non è thread -sicuro)."
Rob_before_edits il

Non mi è chiaro se questo spiega il mio risultato in (iii). Il mio codice era collegato -lpthread -lm e dichiarava sia una variabile statica "pthread_t * thr" che una variabile "pthread_mutex_t nextLock = PTHREAD_MUTEX_INITIALIZER" anche quando era in esecuzione a thread singolo. Normalmente presumo che "collegamento con librerie multi-thread" significhi effettivamente usare quelle librerie, e in misura maggiore di questa, ma potrei sbagliarmi!
Rob_before_edits il

23

Usa Valgrind, callgrind e kcachegrind:

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

genera callgrind.out.x. Leggilo usando kcachegrind.

Usa gprof (aggiungi -pg):

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

(non così buono per multi-thread, puntatori a funzioni)

Usa google-perftools:

Vengono rivelati i colli di bottiglia relativi al campionamento temporale, agli I / O e alla CPU.

Intel VTune è il migliore (gratuito per scopi didattici).

Altri: AMD Codeanalyst (dal momento che sostituito con AMD CodeXL), OProfile, strumenti 'perf' (apt-get install linux-tools)


11

Rilievo di tecniche di profilazione C ++

In questa risposta, userò diversi strumenti per analizzare alcuni programmi di test molto semplici, al fine di confrontare concretamente il funzionamento di tali strumenti.

Il seguente programma di test è molto semplice e procede come segue:

  • mainchiamate faste maybe_slow3 volte, una delle maybe_slowchiamate è lenta

    La chiamata lenta di maybe_slowè 10 volte più lunga e domina il runtime se consideriamo le chiamate alla funzione figlio common. Idealmente, lo strumento di profilazione sarà in grado di indicarci la specifica chiamata lenta.

  • entrambi faste maybe_slowcall common, che rappresenta la maggior parte dell'esecuzione del programma

  • L'interfaccia del programma è:

    ./main.out [n [seed]]

    e il programma esegue O(n^2)loop in totale. seedè solo per ottenere output diversi senza influire sul runtime.

main.c

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>

uint64_t __attribute__ ((noinline)) common(uint64_t n, uint64_t seed) {
    for (uint64_t i = 0; i < n; ++i) {
        seed = (seed * seed) - (3 * seed) + 1;
    }
    return seed;
}

uint64_t __attribute__ ((noinline)) fast(uint64_t n, uint64_t seed) {
    uint64_t max = (n / 10) + 1;
    for (uint64_t i = 0; i < max; ++i) {
        seed = common(n, (seed * seed) - (3 * seed) + 1);
    }
    return seed;
}

uint64_t __attribute__ ((noinline)) maybe_slow(uint64_t n, uint64_t seed, int is_slow) {
    uint64_t max = n;
    if (is_slow) {
        max *= 10;
    }
    for (uint64_t i = 0; i < max; ++i) {
        seed = common(n, (seed * seed) - (3 * seed) + 1);
    }
    return seed;
}

int main(int argc, char **argv) {
    uint64_t n, seed;
    if (argc > 1) {
        n = strtoll(argv[1], NULL, 0);
    } else {
        n = 1;
    }
    if (argc > 2) {
        seed = strtoll(argv[2], NULL, 0);
    } else {
        seed = 0;
    }
    seed += maybe_slow(n, seed, 0);
    seed += fast(n, seed);
    seed += maybe_slow(n, seed, 1);
    seed += fast(n, seed);
    seed += maybe_slow(n, seed, 0);
    seed += fast(n, seed);
    printf("%" PRIX64 "\n", seed);
    return EXIT_SUCCESS;
}

gprof

gprof richiede la ricompilazione del software con la strumentazione e utilizza anche un approccio di campionamento insieme a quella strumentazione. Pertanto, trova un equilibrio tra accuratezza (il campionamento non è sempre del tutto accurato e può saltare le funzioni) e il rallentamento dell'esecuzione (la strumentazione e il campionamento sono tecniche relativamente veloci che non rallentano molto l'esecuzione).

gprof è integrato in GCC / binutils, quindi tutto ciò che dobbiamo fare è compilare con l' -pgopzione per abilitare gprof. Quindi eseguiamo normalmente il programma con un parametro CLI size che produce una durata ragionevole di alcuni secondi ( 10000):

gcc -pg -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
time ./main.out 10000

Per motivi educativi, eseguiremo anche una corsa senza ottimizzazioni abilitate. Si noti che questo è inutile in pratica, poiché normalmente ti interessa solo ottimizzare le prestazioni del programma ottimizzato:

gcc -pg -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out 10000

Innanzitutto, timeci dice che i tempi di esecuzione con e senza -pgerano gli stessi, il che è fantastico: nessun rallentamento! Ho comunque visto account di rallentamenti 2x - 3x su software complessi, ad esempio come mostrato in questo ticket .

Poiché abbiamo compilato -pg, l'esecuzione del programma produce un file gmon.outfile contenente i dati di profilatura.

Possiamo osservare graficamente quel file con gprof2dotla domanda: È possibile ottenere una rappresentazione grafica dei risultati di gprof?

sudo apt install graphviz
python3 -m pip install --user gprof2dot
gprof main.out > main.gprof
gprof2dot < main.gprof | dot -Tsvg -o output.svg

Qui, lo gprofstrumento legge le gmon.outinformazioni di traccia e genera un rapporto leggibile dall'uomo main.gprof, che gprof2dotquindi legge per generare un grafico.

La fonte per gprof2dot è a: https://github.com/jrfonseca/gprof2dot

Osserviamo quanto segue per la -O0corsa:

inserisci qui la descrizione dell'immagine

e per la -O3corsa:

inserisci qui la descrizione dell'immagine

L' -O0output è praticamente autoesplicativo. Ad esempio, mostra che le 3 maybe_slowchiamate e le loro chiamate figlio occupano il 97,56% del tempo di esecuzione totale, sebbene l'esecuzione di maybe_slowse stessa senza figli rappresenti lo 0,00% del tempo di esecuzione totale, ovvero quasi tutto il tempo trascorso in quella funzione è stato impiegato bambino chiama.

TODO: perché mainmanca l' -O3output, anche se riesco a vederlo su un btin GDB? Funzione mancante dall'output GProf Penso che sia perché gprof è basato anche sul campionamento oltre alla sua strumentazione compilata, ed -O3 mainè semplicemente troppo veloce e non ha ottenuto campioni.

Ho scelto l'output SVG anziché PNG perché l'SVG è ricercabile con Ctrl + F e la dimensione del file può essere circa 10 volte più piccola. Inoltre, la larghezza e l'altezza dell'immagine generata possono essere humoungous con decine di migliaia di pixel per software complessi, e GNOME eog3.28.1 in quel caso bug fuori per PNG, mentre gli SVG vengono aperti automaticamente dal mio browser. gimp 2.8 ha funzionato bene, vedi anche:

ma anche allora, trascinerai l'immagine molto per trovare quello che vuoi, vedi ad esempio questa immagine da un "vero" esempio di software preso da questo ticket :

inserisci qui la descrizione dell'immagine

Riesci a trovare facilmente lo stack di chiamate più critico con tutte quelle piccole linee di spaghetti non ordinate che si sovrappongono? Potrebbero esserci dotopzioni migliori , ma non voglio andarci adesso. Ciò di cui abbiamo davvero bisogno è un visualizzatore dedicato adeguato, ma non ne ho ancora trovato uno:

Puoi tuttavia utilizzare la mappa dei colori per mitigare un po 'questi problemi. Ad esempio, nella precedente enorme immagine, sono finalmente riuscito a trovare il percorso critico sulla sinistra quando ho fatto la brillante deduzione che il verde viene dopo il rosso, seguito infine dal blu sempre più scuro.

In alternativa, possiamo anche osservare l'output di testo dello gprofstrumento binutils incorporato che in precedenza abbiamo salvato in:

cat main.gprof

Per impostazione predefinita, questo produce un output estremamente dettagliato che spiega cosa significano i dati di output. Dal momento che non posso spiegare meglio di così, ti lascerò leggerlo da solo.

Una volta compreso il formato di output dei dati, è possibile ridurre la verbosità per mostrare solo i dati senza il tutorial con l' -bopzione:

gprof -b main.out

Nel nostro esempio, gli output sono stati per -O0:

Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls   s/call   s/call  name    
100.35      3.67     3.67   123003     0.00     0.00  common
  0.00      3.67     0.00        3     0.00     0.03  fast
  0.00      3.67     0.00        3     0.00     1.19  maybe_slow

            Call graph


granularity: each sample hit covers 2 byte(s) for 0.27% of 3.67 seconds

index % time    self  children    called     name
                0.09    0.00    3003/123003      fast [4]
                3.58    0.00  120000/123003      maybe_slow [3]
[1]    100.0    3.67    0.00  123003         common [1]
-----------------------------------------------
                                                 <spontaneous>
[2]    100.0    0.00    3.67                 main [2]
                0.00    3.58       3/3           maybe_slow [3]
                0.00    0.09       3/3           fast [4]
-----------------------------------------------
                0.00    3.58       3/3           main [2]
[3]     97.6    0.00    3.58       3         maybe_slow [3]
                3.58    0.00  120000/123003      common [1]
-----------------------------------------------
                0.00    0.09       3/3           main [2]
[4]      2.4    0.00    0.09       3         fast [4]
                0.09    0.00    3003/123003      common [1]
-----------------------------------------------

Index by function name

   [1] common                  [4] fast                    [3] maybe_slow

e per -O3:

Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  us/call  us/call  name    
100.52      1.84     1.84   123003    14.96    14.96  common

            Call graph


granularity: each sample hit covers 2 byte(s) for 0.54% of 1.84 seconds

index % time    self  children    called     name
                0.04    0.00    3003/123003      fast [3]
                1.79    0.00  120000/123003      maybe_slow [2]
[1]    100.0    1.84    0.00  123003         common [1]
-----------------------------------------------
                                                 <spontaneous>
[2]     97.6    0.00    1.79                 maybe_slow [2]
                1.79    0.00  120000/123003      common [1]
-----------------------------------------------
                                                 <spontaneous>
[3]      2.4    0.00    0.04                 fast [3]
                0.04    0.00    3003/123003      common [1]
-----------------------------------------------

Index by function name

   [1] common

Come una sintesi molto rapida per ogni sezione, ad esempio:

                0.00    3.58       3/3           main [2]
[3]     97.6    0.00    3.58       3         maybe_slow [3]
                3.58    0.00  120000/123003      common [1]

ruota attorno alla funzione che viene lasciata rientrata ( maybe_flow). [3]è l'ID di quella funzione. Sopra la funzione, ci sono i suoi chiamanti, e sotto di essa le chiamate.

Per -O3, vedi qui come nell'output grafico che maybe_slowe fastnon hanno un genitore noto, che è ciò che la documentazione dice che <spontaneous>significa.

Non sono sicuro che esista un buon modo per eseguire il profiling riga per riga con gprof: il tempo `gprof` trascorso in particolari righe di codice

valgrind callgrind

valgrind esegue il programma attraverso la macchina virtuale valgrind. Ciò rende la profilazione molto accurata, ma produce anche un notevole rallentamento del programma. Ho già menzionato kcachegrind in precedenza in: Strumenti per ottenere un grafico di chiamata di funzione pittorica di codice

callgrind è lo strumento di valgrind per profilare il codice e kcachegrind è un programma KDE in grado di visualizzare l'output di cachegrind.

Per prima cosa dobbiamo rimuovere il -pgflag per tornare alla compilazione normale, altrimenti la corsa in realtà fallisce Profiling timer expirede sì, questo è così comune che l'ho fatto e c'era una domanda Stack Overflow per questo.

Quindi compiliamo ed eseguiamo come:

sudo apt install kcachegrind valgrind
gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
time valgrind --tool=callgrind valgrind --dump-instr=yes \
  --collect-jumps=yes ./main.out 10000

Abilito --dump-instr=yes --collect-jumps=yesperché questo scarica anche le informazioni che ci consentono di visualizzare una suddivisione delle prestazioni per linea di assemblaggio, a un costo aggiuntivo aggiunto relativamente piccolo.

Detto questo, timeci dice che l'esecuzione del programma ha richiesto 29,5 secondi, quindi in questo esempio abbiamo avuto un rallentamento di circa 15x. Chiaramente, questo rallentamento rappresenterà una seria limitazione per carichi di lavoro più grandi. Nell'esempio di "software reale" menzionato qui , ho osservato un rallentamento di 80x.

La corsa genera un file di dati del profilo chiamato callgrind.out.<pid>ad esempio callgrind.out.8554nel mio caso. Visualizziamo quel file con:

kcachegrind callgrind.out.8554

che mostra una GUI che contiene dati simili all'output testuale di gprof:

inserisci qui la descrizione dell'immagine

Inoltre, se andiamo nella scheda "Call Graph" in basso a destra, vediamo un grafico di chiamata che possiamo esportare facendo clic con il tasto destro del mouse per ottenere la seguente immagine con quantità irragionevoli di bordo bianco :-)

inserisci qui la descrizione dell'immagine

Penso che fastnon sia mostrato su quel grafico perché kcachegrind deve aver semplificato la visualizzazione perché quella chiamata impiega troppo poco tempo, questo sarà probabilmente il comportamento desiderato su un vero programma. Il menu di scelta rapida ha alcune impostazioni per controllare quando eliminare tali nodi, ma non sono riuscito a mostrare una chiamata così breve dopo un rapido tentativo. Se clicco sulla fastfinestra di sinistra, mostra un grafico di chiamata con fast, quindi lo stack è stato effettivamente catturato. Nessuno aveva ancora trovato un modo per mostrare il grafico completo delle chiamate del grafico: Rendi callgrind mostra tutte le chiamate di funzione nel callgraph di kcachegrind

TODO su software C ++ complesso, vedo alcune voci di tipo <cycle N>, ad esempio <cycle 11>dove mi aspetto i nomi delle funzioni, cosa significa? Ho notato che c'è un pulsante "Cycle Detection" per attivarlo e disattivarlo, ma cosa significa?

perf a partire dal linux-tools

perfsembra usare esclusivamente meccanismi di campionamento del kernel Linux. Questo rende molto semplice l'installazione, ma anche non completamente accurato.

sudo apt install linux-tools
time perf record -g ./main.out 10000

Questo ha aggiunto 0,2 secondi all'esecuzione, quindi stiamo bene per quanto riguarda il tempo, ma non vedo ancora molto interesse, dopo aver espanso il commonnodo con la freccia destra della tastiera:

Samples: 7K of event 'cycles:uppp', Event count (approx.): 6228527608     
  Children      Self  Command   Shared Object     Symbol                  
-   99.98%    99.88%  main.out  main.out          [.] common              
     common                                                               
     0.11%     0.11%  main.out  [kernel]          [k] 0xffffffff8a6009e7  
     0.01%     0.01%  main.out  [kernel]          [k] 0xffffffff8a600158  
     0.01%     0.00%  main.out  [unknown]         [k] 0x0000000000000040  
     0.01%     0.00%  main.out  ld-2.27.so        [.] _dl_sysdep_start    
     0.01%     0.00%  main.out  ld-2.27.so        [.] dl_main             
     0.01%     0.00%  main.out  ld-2.27.so        [.] mprotect            
     0.01%     0.00%  main.out  ld-2.27.so        [.] _dl_map_object      
     0.01%     0.00%  main.out  ld-2.27.so        [.] _xstat              
     0.00%     0.00%  main.out  ld-2.27.so        [.] __GI___tunables_init
     0.00%     0.00%  main.out  [unknown]         [.] 0x2f3d4f4944555453  
     0.00%     0.00%  main.out  [unknown]         [.] 0x00007fff3cfc57ac  
     0.00%     0.00%  main.out  ld-2.27.so        [.] _start              

Quindi provo a confrontare il -O0programma per vedere se mostra qualcosa e solo ora finalmente vedo un grafico di chiamata:

Samples: 15K of event 'cycles:uppp', Event count (approx.): 12438962281   
  Children      Self  Command   Shared Object     Symbol                  
+   99.99%     0.00%  main.out  [unknown]         [.] 0x04be258d4c544155  
+   99.99%     0.00%  main.out  libc-2.27.so      [.] __libc_start_main   
-   99.99%     0.00%  main.out  main.out          [.] main                
   - main                                                                 
      - 97.54% maybe_slow                                                 
           common                                                         
      - 2.45% fast                                                        
           common                                                         
+   99.96%    99.85%  main.out  main.out          [.] common              
+   97.54%     0.03%  main.out  main.out          [.] maybe_slow          
+    2.45%     0.00%  main.out  main.out          [.] fast                
     0.11%     0.11%  main.out  [kernel]          [k] 0xffffffff8a6009e7  
     0.00%     0.00%  main.out  [unknown]         [k] 0x0000000000000040  
     0.00%     0.00%  main.out  ld-2.27.so        [.] _dl_sysdep_start    
     0.00%     0.00%  main.out  ld-2.27.so        [.] dl_main             
     0.00%     0.00%  main.out  ld-2.27.so        [.] _dl_lookup_symbol_x 
     0.00%     0.00%  main.out  [kernel]          [k] 0xffffffff8a600158  
     0.00%     0.00%  main.out  ld-2.27.so        [.] mmap64              
     0.00%     0.00%  main.out  ld-2.27.so        [.] _dl_map_object      
     0.00%     0.00%  main.out  ld-2.27.so        [.] __GI___tunables_init
     0.00%     0.00%  main.out  [unknown]         [.] 0x552e53555f6e653d  
     0.00%     0.00%  main.out  [unknown]         [.] 0x00007ffe1cf20fdb  
     0.00%     0.00%  main.out  ld-2.27.so        [.] _start              

TODO: cosa è successo -O3nell'esecuzione? È semplicemente così maybe_slowed fastera troppo veloce e non ha ottenuto alcun campione? Funziona bene con -O3programmi più grandi che richiedono più tempo per l'esecuzione? Ho perso qualche opzione CLI? Ho scoperto il -Fcontrollo della frequenza di campionamento in Hertz, ma l'ho alzato al massimo consentito di default -F 39500(potrebbe essere aumentato con sudo) e non vedo ancora chiamate chiare.

Una cosa interessante perfè lo strumento FlameGraph di Brendan Gregg che mostra i tempi dello stack delle chiamate in un modo molto ordinato che ti consente di vedere rapidamente le grandi chiamate. Lo strumento è disponibile all'indirizzo: https://github.com/brendangregg/FlameGraph ed è menzionato anche il suo perf tutorial: http://www.brendangregg.com/perf.html#FlameGraphs Quando ho eseguito perfsenza sudoho avuto ERROR: No stack counts foundmodo di ora lo farò con sudo:

git clone https://github.com/brendangregg/FlameGraph
sudo perf record -F 99 -g -o perf_with_stack.data ./main.out 10000
sudo perf script -i perf_with_stack.data | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > flamegraph.svg

ma in un programma così semplice l'output non è molto facile da capire, dal momento che non possiamo facilmente vedere maybe_slowfastsu quel grafico:

inserisci qui la descrizione dell'immagine

Sull'esempio più complesso diventa chiaro cosa significa il grafico:

inserisci qui la descrizione dell'immagine

TODO ci sono un registro di [unknown]funzioni in quell'esempio, perché?

Altre interfacce GUI perf che potrebbero valerne la pena includono:

  • Plug-in Eclipse Trace Compass: https://www.eclipse.org/tracecompass/

    Ma questo ha il rovescio della medaglia che devi prima convertire i dati nel Common Trace Format, che può essere fatto con perf data --to-ctf, ma deve essere abilitato al momento della compilazione / avere perfabbastanza nuovo, nessuno dei due non è il caso del perf in Ubuntu 18.04

  • https://github.com/KDAB/hotspot

    Il rovescio della medaglia è che non sembra esserci alcun pacchetto Ubuntu, e la sua costruzione richiede Qt 5.10 mentre Ubuntu 18.04 è a Qt 5.9.

gperftools

Precedentemente chiamato "Google Performance Tools", fonte: https://github.com/gperftools/gperftools basato su campioni.

Prima installa gperftools con:

sudo apt install google-perftools

Quindi, possiamo abilitare il profiler CPU gperftools in due modi: in fase di esecuzione o in fase di compilazione.

In fase di esecuzione, dobbiamo passare impostare il LD_PRELOADpunto to libprofiler.so, che puoi trovare con locate libprofiler.so, ad esempio sul mio sistema:

gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libprofiler.so \
  CPUPROFILE=prof.out ./main.out 10000

In alternativa, possiamo creare la libreria al momento del collegamento, dispensando il passaggio LD_PRELOADin fase di esecuzione:

gcc -Wl,--no-as-needed,-lprofiler,--as-needed -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
CPUPROFILE=prof.out ./main.out 10000

Vedi anche: gperftools - file di profilo non scaricato

Il modo migliore per visualizzare questi dati che ho trovato finora è rendere l'output di pprof lo stesso formato che kcachegrind utilizza come input (sì, lo strumento Valgrind-project-viewer) e usare kcachegrind per visualizzare che:

google-pprof --callgrind main.out prof.out  > callgrind.out
kcachegrind callgrind.out

Dopo l'esecuzione con uno di questi metodi, otteniamo un prof.outfile di dati del profilo come output. Possiamo visualizzare graficamente quel file come SVG con:

google-pprof --web main.out prof.out

inserisci qui la descrizione dell'immagine

che fornisce come un grafico di chiamata familiare come altri strumenti, ma con l'unità goffa di numero di campioni anziché secondi.

In alternativa, possiamo anche ottenere alcuni dati testuali con:

google-pprof --text main.out prof.out

che dà:

Using local file main.out.
Using local file prof.out.
Total: 187 samples
     187 100.0% 100.0%      187 100.0% common
       0   0.0% 100.0%      187 100.0% __libc_start_main
       0   0.0% 100.0%      187 100.0% _start
       0   0.0% 100.0%        4   2.1% fast
       0   0.0% 100.0%      187 100.0% main
       0   0.0% 100.0%      183  97.9% maybe_slow

Vedi anche: Come utilizzare google perf tools

Testato in Ubuntu 18.04, gprof2dot 2019.11.30, valgrind 3.13.0, perf 4.15.18, kernel Linux 4.15.0, FLameGraph 1a0dc6985aad06e76857cf2a354bd5ba0c9ce96b, gperftools 2.5-2.


2
Per impostazione predefinita, il record perf utilizza il registro del puntatore al frame. I compilatori moderni non registrano l'indirizzo del frame e usano invece il registro come scopo generale. L'alternativa è compilare con -fno-omit-frame-pointerflag o utilizzare un'alternativa diversa: registrare con --call-graph "dwarf"o in --call-graph "lbr"base al proprio scenario.
Jorge Bellon,

5

Per i programmi a thread singolo è possibile utilizzare igprof , The Ignominous Profiler: https://igprof.org/ .

È un profiler di campionamento, sulla falsa riga della ... lunga ... risposta di Mike Dunlavey, che racchiuderà i risultati in un albero di stack di chiamate sfogliabile, annotato con il tempo o la memoria spesi in ciascuna funzione, cumulativa o per-funzione.


Sembra interessante, ma non riesce a compilare con GCC 9.2. (Debian / Sid) Ho fatto un problema su Github.
Basile Starynkevitch l'

5

Vale anche la pena ricordare

  1. HPCToolkit ( http://hpctoolkit.org/ ) - Open-source, funziona per programmi paralleli e ha una GUI con cui guardare i risultati in diversi modi
  2. Intel VTune ( https://software.intel.com/en-us/vtune ) - Se hai compilatori Intel questo è molto buono
  3. TAU ( http://www.cs.uoregon.edu/research/tau/home.php )

Ho usato HPCToolkit e VTune e sono molto efficaci nel trovare il palo lungo nella tenda e non ho bisogno di ricompilare il tuo codice (tranne per il fatto che devi usare -g -O o il tipo RelWithDebInfo compilato in CMake per ottenere un risultato significativo) . Ho sentito che TAU è simile nelle capacità.


4

Questi sono i due metodi che utilizzo per velocizzare il mio codice:

Per applicazioni associate alla CPU:

  1. Utilizzare un profiler in modalità DEBUG per identificare parti discutibili del codice
  2. Quindi passa alla modalità RELEASE e commenta le sezioni discutibili del tuo codice (stub con nulla) fino a quando non vedi cambiamenti nelle prestazioni.

Per applicazioni associate a I / O:

  1. Utilizzare un profiler in modalità RELEASE per identificare parti discutibili del codice.

NB

Se non hai un profiler, usa il profiler del povero. Fai una pausa mentre esegui il debug della tua applicazione. La maggior parte delle suite di sviluppatori si romperà in assembly con i numeri di riga commentati. È statisticamente probabile che atterri in una regione che sta consumando la maggior parte dei cicli della CPU.

Per la CPU, il motivo della profilazione in modalità DEBUG è perché se si è provata la creazione di profili in modalità RELEASE , il compilatore ridurrà la matematica, vettorializza i cicli e le funzioni incorporate che tendono a mettere in circolo il codice in un pasticcio non mappabile quando viene assemblato. Un pasticcio non mappabile significa che il tuo profiler non sarà in grado di identificare chiaramente ciò che sta impiegando così tanto tempo perché l'assembly potrebbe non corrispondere al codice sorgente in fase di ottimizzazione . Se sono necessarie le prestazioni (ad es. Tempi sensibili) della modalità RELEASE , disabilitare le funzionalità di debugger in base alle necessità per mantenere prestazioni utilizzabili.

Per I / O-bound, il profiler può ancora identificare le operazioni di I / O in modalità RELEASE perché le operazioni di I / O sono collegate esternamente a una libreria condivisa (la maggior parte delle volte) o, nel caso peggiore, si tradurrà in un sys- chiama il vettore di interrupt (che è anche facilmente identificabile dal profiler).


2
+1 Il metodo del povero funziona altrettanto bene per il limite I / O che per il limite CPU, e consiglio di eseguire tutte le ottimizzazioni delle prestazioni in modalità DEBUG. Al termine della sintonizzazione, attiva RELEASE. Migliorerà se il programma è associato alla CPU nel codice. Ecco un breve ma breve video del processo.
Mike Dunlavey,

3
Non userei build DEBUG per la profilazione delle prestazioni. Ho visto spesso che le parti critiche per le prestazioni in modalità DEBUG sono completamente ottimizzate in modalità di rilascio. Un altro problema è l'uso di assert nel codice di debug che aggiungono rumore alle prestazioni.
gast128,

3
Hai letto il mio post? "Se hai bisogno delle prestazioni (es. Tempismo sensibile) della modalità RELEASE, disabilita le funzionalità di debugger come necessario per mantenere una prestazione utilizzabile", "Quindi passa alla modalità RELEASE e commenta le sezioni discutibili del tuo codice (Stub it with nothing) fino a quando non vedi cambiamenti nelle prestazioni ". Ho detto di controllare possibili aree problematiche in modalità debug e verificare quei problemi in modalità release per evitare la trappola che hai citato.
seo,


2

È possibile utilizzare un framework di registrazione come logurupoiché include i timestamp e il tempo di attività totale che possono essere utilizzati correttamente per la profilazione:


1

Al lavoro disponiamo di uno strumento davvero utile che ci aiuta a monitorare ciò che vogliamo in termini di pianificazione. Questo è stato utile numerose volte.

È in C ++ e deve essere personalizzato in base alle tue esigenze. Sfortunatamente non posso condividere il codice, solo concetti. Si utilizza un volatilebuffer "grande" contenente timestamp e ID evento che è possibile scaricare post mortem o dopo aver arrestato il sistema di registrazione (e scaricare questo in un file, ad esempio).

Si recupera il cosiddetto buffer di grandi dimensioni con tutti i dati e una piccola interfaccia lo analizza e mostra gli eventi con nome (su / giù + valore) come fa un oscilloscopio con i colori (configurati nel .hppfile).

Personalizzi la quantità di eventi generati per concentrarti esclusivamente su ciò che desideri. Ci ha aiutato molto per i problemi di pianificazione consumando la quantità di CPU che volevamo in base alla quantità di eventi registrati al secondo.

Hai bisogno di 3 file:

toolname.hpp // interface
toolname.cpp // code
tool_events_id.hpp // Events ID

Il concetto è di definire gli eventi in tool_events_id.hppquesto modo:

// EVENT_NAME                         ID      BEGIN_END BG_COLOR NAME
#define SOCK_PDU_RECV_D               0x0301  //@D00301 BGEEAAAA # TX_PDU_Recv
#define SOCK_PDU_RECV_F               0x0302  //@F00301 BGEEAAAA # TX_PDU_Recv

Puoi anche definire alcune funzioni in toolname.hpp:

#define LOG_LEVEL_ERROR 0
#define LOG_LEVEL_WARN 1
// ...

void init(void);
void probe(id,payload);
// etc

Ovunque nel tuo codice puoi usare:

toolname<LOG_LEVEL>::log(EVENT_NAME,VALUE);

La probefunzione utilizza alcune linee di assemblaggio per recuperare il timestamp dell'orologio il più presto possibile e quindi imposta una voce nel buffer. Abbiamo anche un incremento atomico per trovare in modo sicuro un indice in cui archiviare l'evento di registro. Naturalmente il buffer è circolare.

Spero che l'idea non sia offuscata dalla mancanza di codice di esempio.


1

In realtà un po 'sorpreso non molti citati su google / benchmark , mentre è un po' complicato bloccare l'area specifica del codice, specialmente se la base di codice è un po 'grande, tuttavia l'ho trovato molto utile quando usato in combinazione concallgrind

La chiave qui è l'IMHO che identifica il pezzo che sta causando il collo di bottiglia. Cercherei comunque di rispondere prima alle seguenti domande e scegliere uno strumento basato su quello

  1. il mio algoritmo è corretto?
  2. ci sono serrature che si stanno rivelando colli di bottiglia?
  3. esiste una specifica sezione di codice che si sta rivelando colpevole?
  4. che ne dici di IO, gestito e ottimizzato?

valgrindcon la combinazione di callrinde kcachegrinddovrebbe fornire una stima decente sui punti sopra e una volta stabilito che ci sono problemi con alcune sezioni di codice, suggerirei che fare un micro-punto di riferimento google benchmarksia un buon punto di partenza.


1

Utilizzare -pgflag durante la compilazione e il collegamento del codice ed eseguire il file eseguibile. Durante l'esecuzione di questo programma, i dati di profilazione vengono raccolti nel file a.out.
Esistono due diversi tipi di profilazione

1- Profilatura piatta:
eseguendo il comando gprog --flat-profile a.outsi ottengono i seguenti dati
- quale percentuale del tempo complessivo è stato impiegato per la funzione,
- quanti secondi sono stati spesi in una funzione — includendo ed escludendo le chiamate a sotto-funzioni,
- il numero di chiamate,
- il tempo medio per chiamata.

2-grafico che
ci profila il comando gprof --graph a.outper ottenere i seguenti dati per ciascuna funzione che include
- In ogni sezione, una funzione è contrassegnata da un numero indice.
- Sopra la funzione, c'è un elenco di funzioni che chiamano la funzione.
- Sotto la funzione, c'è un elenco di funzioni chiamate dalla funzione.

Per ottenere maggiori informazioni, puoi consultare https://sourceware.org/binutils/docs-2.32/gprof/


0

Come nessuno ha menzionato Arm MAP, lo aggiungerei come personalmente ho usato con successo Map per profilare un programma scientifico C ++.

Arm MAP è il profiler per i codici C, C ++, Fortran e F90 paralleli, multithread o a thread singolo. Fornisce analisi approfondite e individuazione dei colli di bottiglia nella linea di origine. A differenza della maggior parte dei profilatori, è progettato per essere in grado di profilare pthreads, OpenMP o MPI per codice parallelo e thread.

MAP è un software commerciale.


0

utilizzare un software di debug come identificare dove il codice viene eseguito lentamente?

pensa solo di avere un ostacolo mentre sei in movimento, quindi diminuirà la tua velocità

come il looping, gli overflow del buffer, la ricerca, le perdite di memoria ecc. della riallocazione indesiderata consumano più potenza di esecuzione e influiranno negativamente sulle prestazioni del codice, assicurati di aggiungere -pg alla compilazione prima della profilazione:

g++ your_prg.cpp -pgo cc my_program.cpp -g -pgsecondo il tuo compilatore

non l'ho ancora provato ma ho sentito cose positive su google-perftools. Vale sicuramente la pena provare.

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

Genererà un file chiamato gmon.out o callgrind.out.x. È quindi possibile utilizzare kcachegrind o lo strumento debugger per leggere questo file. Ti fornirà un'analisi grafica delle cose con risultati come quali linee costano quanto.

credo di si

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.