Quali altri programmi fanno la stessa cosa di gprof?
Quali altri programmi fanno la stessa cosa di gprof?
Risposte:
Valgrind ha un profiler per il conteggio delle istruzioni con un visualizzatore molto carino chiamato KCacheGrind . Come Mike Dunlavey raccomanda, Valgrind conta la frazione di istruzioni per le quali è in corso una procedura in pila, anche se mi dispiace dire che sembra confusa in presenza di una ricorsione reciproca. Ma il visualizzatore è molto bello e anni luce avanti gprof
.
gprof (leggi l'articolo) esiste per ragioni storiche. Se pensi che ti aiuterà a trovare problemi di prestazioni, non è mai stato pubblicizzato come tale. Ecco cosa dice il documento:
Il pro fi lo può essere utilizzato per confrontare e valutare i costi di varie implementazioni.
Non dice che può essere usato per identificare le varie implementazioni da valutare, anche se implica che potrebbe, in circostanze speciali:
specialmente se si scopre che piccole parti del programma dominano il suo tempo di esecuzione.
Che dire dei problemi che non sono così localizzati? Quelli non contano? Non gettare aspettative su gprof che non sono mai state rivendicate per questo. È solo uno strumento di misurazione e solo delle operazioni associate alla CPU.
Prova questo invece.
Ecco un esempio di uno speedup 44x.
Ecco uno speedup 730x.
Ecco una dimostrazione video di 8 minuti.
Ecco una spiegazione delle statistiche.
Ecco una risposta alle critiche.
C'è una semplice osservazione sui programmi. In una data esecuzione, ogni istruzione è responsabile di una frazione del tempo complessivo (in particolare le call
istruzioni), nel senso che se non fosse presente, il tempo non sarebbe trascorso. Durante quel periodo, l'istruzione è in pila **. Quando questo è compreso, puoi vedere che -
gprof incarna alcuni miti sulle prestazioni, come:
quel campionamento del contatore di programmi è utile.
È utile solo se si dispone di un collo di bottiglia dell'hotspot non necessario come una sorta di bolla di una vasta gamma di valori scalari. Non appena, ad esempio, lo si cambia in un ordinamento usando string-compare, rimane comunque un collo di bottiglia, ma il campionamento del contatore del programma non lo vedrà perché ora l'hotspot è in string-compare. D'altra parte, se si dovesse campionare il contatore del programma esteso (lo stack di chiamate), il punto in cui viene chiamato il confronto di stringhe, il ciclo di ordinamento, viene visualizzato chiaramente. In effetti, gprof è stato un tentativo di porre rimedio ai limiti del campionamento solo su PC.
che le funzioni di temporizzazione sono più importanti della cattura di lunghe righe di codice.
La ragione di questo mito è che gprof non è stato in grado di catturare campioni di stack, quindi cronometra le funzioni, conta le loro invocazioni e cerca di catturare il grafico delle chiamate. Tuttavia, una volta identificata una funzione costosa, è comunque necessario cercare al suo interno le linee responsabili del tempo. Se ci fossero campioni di stack che non avresti bisogno di guardare, quelle linee sarebbero sui campioni. (Una funzione tipica potrebbe avere 100 - 1000 istruzioni. Una chiamata di funzione è 1 istruzione, quindi qualcosa che individua chiamate costose è 2-3 ordini di grandezza più precisi.)
che il grafico della chiamata è importante.
Quello che devi sapere su un programma non è dove trascorre il suo tempo, ma perché. Quando trascorre del tempo in una funzione, ogni riga di codice nello stack fornisce un collegamento nella catena di ragionamento del perché è lì. Se riesci a vedere solo parte dello stack, puoi vedere solo una parte del motivo, quindi non puoi dire con certezza se quel tempo è effettivamente necessario. Cosa ti dice il grafico delle chiamate? Ogni arco indica che alcune funzioni A erano in procinto di chiamare alcune funzioni B per una frazione del tempo. Anche se A ha solo una di queste righe di codice che chiama B, quella riga fornisce solo una piccola parte del motivo. Se sei abbastanza fortunato, forse quella linea ha un motivo scarso. Di solito, è necessario vedere più righe simultanee per trovare una ragione scadente se è lì. Se A chiama B in più di un posto, allora ti dice anche meno.
quella ricorsione è un problema complicato e confuso.
Questo solo perché gprof e altri profiler percepiscono la necessità di generare un grafico di chiamata e quindi attribuire i tempi ai nodi. Se uno ha campioni dello stack, il costo nel tempo di ogni riga di codice che appare sui campioni è un numero molto semplice: la frazione di campioni su cui si trova. In caso di ricorsione, una determinata riga può apparire più di una volta su un campione.
Non importa. Supponiamo che i campioni vengano prelevati ogni N ms e che la linea appaia su F% di essi (singolarmente o no). Se si può fare in modo che quella linea non richieda tempo (ad esempio cancellandola o ramificandola), quei campioni sparirebbero e il tempo verrebbe ridotto di F%.
che l'accuratezza della misurazione del tempo (e quindi un gran numero di campioni) è importante.
Pensaci per un secondo. Se una riga di codice è su 3 campioni su cinque, quindi se si potesse sparare come una lampadina, sarebbe circa il 60% in meno di tempo che verrebbe utilizzato. Ora, sai che se avessi preso 5 diversi campioni, avresti potuto vederlo solo 2 volte, o fino a 4. Quindi la misurazione del 60% è più simile a un intervallo generale dal 40% all'80%. Se fosse solo il 40%, diresti che non vale la pena risolvere il problema? Quindi qual è l'accuratezza del momento preciso, quando quello che vuoi davvero è trovare i problemi ? 500 o 5000 campioni avrebbero misurato il problema con maggiore precisione, ma non lo avrebbero trovato più accuratamente.
che il conteggio delle invocazioni di istruzioni o funzioni è utile.
Supponiamo che tu sappia che una funzione è stata chiamata 1000 volte. Puoi dire da ciò che frazione di tempo costa? Devi anche sapere quanto tempo ci vuole per correre, in media, moltiplicarlo per il conteggio e dividere per il tempo totale. Il tempo medio di chiamata può variare da nanosecondi a secondi, quindi il conteggio da solo non dice molto. Se ci sono campioni di stack, il costo di una routine o di qualsiasi istruzione è solo la frazione di campioni su cui si trova. Quella frazione di tempo è ciò che in linea di principio potrebbe essere risparmiato se la routine o l'affermazione non si realizzasse in pochissimo tempo, quindi questa è la relazione più diretta con la performance.
che i campioni non devono essere prelevati quando bloccati
Le ragioni di questo mito sono duplici: 1) che il campionamento su PC non ha senso quando il programma è in attesa, e 2) la preoccupazione per l'accuratezza dei tempi. Tuttavia, per (1) il programma potrebbe essere in attesa di qualcosa che ha richiesto, come I / O di file, che è necessario conoscere e quali campioni dello stack rivelano. (Ovviamente si desidera escludere campioni in attesa dell'input dell'utente.) Per (2) se il programma è in attesa semplicemente a causa della concorrenza con altri processi, ciò presumibilmente accade in modo abbastanza casuale mentre è in esecuzione. Quindi, mentre il programma potrebbe richiedere più tempo, ciò non avrà un grande effetto sulla statistica che conta, la percentuale di tempo in cui le istruzioni sono in pila.
che il "tempo di sé" è importante Il
tempo di sé ha senso solo se si sta misurando a livello di funzione, non a livello di linea, e si ritiene di aver bisogno di aiuto per discernere se il tempo di funzione va in un calcolo puramente locale rispetto alle routine chiamate. Se riepilogando a livello di linea, una linea rappresenta il tempo autonomo se si trova alla fine della pila, altrimenti rappresenta il tempo inclusivo. In entrambi i casi, ciò che costa è la percentuale di campioni dello stack su cui si trova, in modo che lo localizzi in entrambi i casi.
che i campioni devono essere prelevati ad alta frequenza
Ciò deriva dall'idea che un problema di prestazioni potrebbe essere ad azione rapida e che i campioni devono essere frequenti per colpirlo. Ma, se il problema costa, 20%, ad esempio, su un tempo di esecuzione totale di 10 sec (o qualsiasi altra cosa), ogni campione in quel tempo totale avrà una probabilità del 20% di colpirlo, non importa se si verifica il problema in un singolo pezzo come questo
.....XXXXXXXX...........................
.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^
(20 campioni, 4 risultati)
o in molti piccoli pezzi come questo
X...X...X.X..X.........X.....X....X.....
.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^
(20 campioni, 3 risultati)
Ad ogni modo, il numero di risultati sarà in media circa 1 su 5, indipendentemente dal numero di campioni prelevati, oppure quanto pochi. (Media = 20 * 0,2 = 4. Deviazione standard = +/- sqrt (20 * 0,2 * 0,8) = 1,8.)
che stai cercando di trovare il collo
di bottiglia come se ce ne fosse solo uno. Considera la seguente sequenza temporale di esecuzione: vxvWvzvWvxvWvYvWvxvWv.vWvxvWvYvW
consiste in un vero lavoro utile, rappresentato da .
. Ci sono problemi di prestazioni che vWxYz
richiedono rispettivamente 1/2, 1/4, 1/8, 1/16, 1/32 del tempo. Il campionamento lo trova v
facilmente. Viene rimosso, lasciando
xWzWxWYWxW.WxWYW
Ora il programma impiega la metà del tempo per essere eseguito, e ora W
impiega la metà del tempo, e si trova facilmente. Viene rimosso, lasciando
xzxYx.xY
Questo processo continua, rimuovendo ogni volta il più grande, in percentuale, problema di prestazioni, fino a quando non si trova nulla da rimuovere. Ora l'unica cosa eseguita è .
, che viene eseguita in 1/32 del tempo utilizzato dal programma originale. Questo è l' effetto di ingrandimento, per cui la rimozione di qualsiasi problema rende il resto più grande, in percentuale, perché il denominatore è ridotto.
Un altro punto cruciale è che ogni singolo problema deve essere trovato - mancando nessuno dei 5. Qualsiasi problema non trovato e risolto riduce notevolmente il rapporto di accelerazione finale. Solo trovarne alcuni, ma non tutti, non è "abbastanza buono".
AGGIUNTO: Vorrei solo sottolineare un motivo per cui gprof è popolare: viene insegnato, presumibilmente perché è gratuito, facile da insegnare ed è in circolazione da molto tempo. Una rapida ricerca su Google individua alcune istituzioni accademiche che insegnano (o sembrano) a:
berkeley bu clemson colorado duca Earlham fsu indiana mit msu ncsa.illinois ncsu nyu ou princeton psu stanford ucsd umd umich utah utexas utk wustl
** Con l'eccezione di altri modi per richiedere il lavoro da svolgere, ciò non lascia traccia del perché , ad esempio tramite la pubblicazione di messaggi.
Dal momento che non ho visto nulla su perf
quale sia uno strumento relativamente nuovo per la creazione del profilo del kernel e delle applicazioni utente su Linux, ho deciso di aggiungere queste informazioni.
Prima di tutto, questo è un tutorial sulla profilazione di Linux conperf
Puoi usare perf
se il tuo kernel Linux è maggiore di 2.6.32 o oprofile
se è più vecchio. Entrambi i programmi non richiedono da te per strumentare il tuo programma (come gprof
richiede). Tuttavia, al fine di ottenere correttamente il grafico delle chiamate, perf
è necessario creare il programma con -fno-omit-frame-pointer
. Ad esempio: g++ -fno-omit-frame-pointer -O2 main.cpp
.
Puoi vedere l'analisi "live" della tua applicazione con perf top
:
sudo perf top -p `pidof a.out` -K
Oppure puoi registrare i dati sulle prestazioni di un'applicazione in esecuzione e analizzarli successivamente:
1) Per registrare i dati sulle prestazioni:
perf record -p `pidof a.out`
o per registrare per 10 secondi:
perf record -p `pidof a.out` sleep 10
o per registrare con il grafico delle chiamate ()
perf record -g -p `pidof a.out`
2) Analizzare i dati registrati
perf report --stdio
perf report --stdio --sort=dso -g none
perf report --stdio -g none
perf report --stdio -g
Oppure puoi registrare i dati di performance di un'applicazione e analizzarli dopo semplicemente avviando l'applicazione in questo modo e aspettando che esca:
perf record ./a.out
Questo è un esempio di profilazione di un programma di test
Il programma di test è nel file main.cpp (metterò main.cpp nella parte inferiore del messaggio):
Lo compilo in questo modo:
g++ -m64 -fno-omit-frame-pointer -g main.cpp -L. -ltcmalloc_minimal -o my_test
Uso libmalloc_minimial.so
poiché è compilato -fno-omit-frame-pointer
mentre libc malloc sembra essere compilato senza questa opzione. Quindi eseguo il mio programma di test
./my_test 100000000
Quindi registro i dati sulle prestazioni di un processo in esecuzione:
perf record -g -p `pidof my_test` -o ./my_test.perf.data sleep 30
Quindi analizzo il carico per modulo:
perf report --stdio -g none --sort comm, dso -i ./my_test.perf.data
# Overhead Command Shared Object
# ........ ....... ............................
#
70.06% my_test my_test
28.33% my_test libtcmalloc_minimal.so.0.1.0
1.61% my_test [kernel.kallsyms]
Quindi viene analizzato il carico per funzione:
perf report --stdio -g none -i ./my_test.perf.data | c ++ filt
# Overhead Command Shared Object Symbol
# ........ ....... ............................ ...........................
#
29.30% my_test my_test [.] f2(long)
29.14% my_test my_test [.] f1(long)
15.17% my_test libtcmalloc_minimal.so.0.1.0 [.] operator new(unsigned long)
13.16% my_test libtcmalloc_minimal.so.0.1.0 [.] operator delete(void*)
9.44% my_test my_test [.] process_request(long)
1.01% my_test my_test [.] operator delete(void*)@plt
0.97% my_test my_test [.] operator new(unsigned long)@plt
0.20% my_test my_test [.] main
0.19% my_test [kernel.kallsyms] [k] apic_timer_interrupt
0.16% my_test [kernel.kallsyms] [k] _spin_lock
0.13% my_test [kernel.kallsyms] [k] native_write_msr_safe
and so on ...
Quindi vengono analizzate le catene di chiamata:
perf report --stdio -g graph -i ./my_test.perf.data | c ++ filt
# Overhead Command Shared Object Symbol
# ........ ....... ............................ ...........................
#
29.30% my_test my_test [.] f2(long)
|
--- f2(long)
|
--29.01%-- process_request(long)
main
__libc_start_main
29.14% my_test my_test [.] f1(long)
|
--- f1(long)
|
|--15.05%-- process_request(long)
| main
| __libc_start_main
|
--13.79%-- f2(long)
process_request(long)
main
__libc_start_main
15.17% my_test libtcmalloc_minimal.so.0.1.0 [.] operator new(unsigned long)
|
--- operator new(unsigned long)
|
|--11.44%-- f1(long)
| |
| |--5.75%-- process_request(long)
| | main
| | __libc_start_main
| |
| --5.69%-- f2(long)
| process_request(long)
| main
| __libc_start_main
|
--3.01%-- process_request(long)
main
__libc_start_main
13.16% my_test libtcmalloc_minimal.so.0.1.0 [.] operator delete(void*)
|
--- operator delete(void*)
|
|--9.13%-- f1(long)
| |
| |--4.63%-- f2(long)
| | process_request(long)
| | main
| | __libc_start_main
| |
| --4.51%-- process_request(long)
| main
| __libc_start_main
|
|--3.05%-- process_request(long)
| main
| __libc_start_main
|
--0.80%-- f2(long)
process_request(long)
main
__libc_start_main
9.44% my_test my_test [.] process_request(long)
|
--- process_request(long)
|
--9.39%-- main
__libc_start_main
1.01% my_test my_test [.] operator delete(void*)@plt
|
--- operator delete(void*)@plt
0.97% my_test my_test [.] operator new(unsigned long)@plt
|
--- operator new(unsigned long)@plt
0.20% my_test my_test [.] main
0.19% my_test [kernel.kallsyms] [k] apic_timer_interrupt
0.16% my_test [kernel.kallsyms] [k] _spin_lock
and so on ...
Quindi a questo punto sai dove il tuo programma passa il tempo.
E questo è main.cpp per il test:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
time_t f1(time_t time_value)
{
for (int j =0; j < 10; ++j) {
++time_value;
if (j%5 == 0) {
double *p = new double;
delete p;
}
}
return time_value;
}
time_t f2(time_t time_value)
{
for (int j =0; j < 40; ++j) {
++time_value;
}
time_value=f1(time_value);
return time_value;
}
time_t process_request(time_t time_value)
{
for (int j =0; j < 10; ++j) {
int *p = new int;
delete p;
for (int m =0; m < 10; ++m) {
++time_value;
}
}
for (int i =0; i < 10; ++i) {
time_value=f1(time_value);
time_value=f2(time_value);
}
return time_value;
}
int main(int argc, char* argv2[])
{
int number_loops = argc > 1 ? atoi(argv2[1]) : 1;
time_t time_value = time(0);
printf("number loops %d\n", number_loops);
printf("time_value: %d\n", time_value );
for (int i =0; i < number_loops; ++i) {
time_value = process_request(time_value);
}
printf("time_value: %ld\n", time_value );
return 0;
}
f1
chiamava delete
. Il 40% (all'incirca) del tempo process_request
chiamava delete
. Una buona parte del resto è stata spesa new
. Le misurazioni sono approssimative, ma gli hotspot sono individuati con precisione.
As in my answer, you run it under a debugger and hit ^C at a random time and capture the stack trace
. 1) Penso che la tua tecnica non sia utile quando devi analizzare i problemi di prestazioni per un programma in esecuzione sul server del tuo cliente. 2) Non sono sicuro di come applicare questa tecnica per ottenere informazioni per un programma con molti thread che gestiscono richieste diverse. Voglio dire quando il quadro generale è piuttosto complicato.
the problem is outside your code
, vero? Dal momento che potresti aver bisogno di alcune informazioni per supportare il tuo punto. In questa situazione a un certo punto potrebbe essere necessario creare un profilo dell'applicazione. Non puoi semplicemente chiedere al tuo cliente di avviare gdb e premere ^ C e ottenere stack di chiamate. Questo era il mio punto. Questo è un esempio di spielwiese.fontein.de/2012/2012/22/… . Ho avuto questo problema e la profilazione mi ha aiutato molto.
Prova OProfile . È uno strumento molto migliore per la profilazione del codice. Vorrei anche suggerire Intel VTune .
I due strumenti di cui sopra possono ridurre il tempo trascorso in una particolare riga di codice, annotare il codice, mostrare l'assemblaggio e la quantità di istruzioni specifiche. Oltre alla metrica temporale, puoi anche eseguire query su contatori specifici, ad esempio hit della cache, ecc.
A differenza di gprof, puoi profilare qualsiasi processo / binario in esecuzione sul tuo sistema usando uno dei due.
Gli strumenti per le prestazioni di Google includono un profiler di semplice utilizzo. Sono disponibili CPU e profiler heap.
Dai un'occhiata a Sysprof .
La tua distribuzione potrebbe già averlo.
http://lttng.org/ se vuoi un tracciante ad alte prestazioni