Se il tuo obiettivo è usare un profiler, usa uno di quelli suggeriti.
Tuttavia, se sei di fretta e puoi interrompere manualmente il tuo programma sotto il debugger mentre è soggettivamente lento, c'è un modo semplice per trovare problemi di prestazioni.
Basta fermarlo più volte e ogni volta guardare lo stack delle chiamate. Se c'è un codice che sta perdendo una certa percentuale del tempo, il 20% o il 50% o qualsiasi altra cosa, questa è la probabilità che lo coglierai sul fatto in ogni campione. Quindi, questa è all'incirca 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 lo smentirà.
Potresti avere più problemi di prestazioni di dimensioni diverse. Se ne elimini uno, i restanti prenderanno una percentuale maggiore e saranno più facili da individuare nei passaggi successivi. Questo effetto di ingrandimento , se combinati su più problemi, possono portare a fattori di accelerazione davvero enormi.
Avvertimento :I programmatori tendono ad essere scettici su questa tecnica a meno che non l'abbiano usata loro stessi. Diranno che i profiler ti danno queste informazioni, ma questo è vero solo se campionano l'intero stack di chiamate e poi ti permettono di esaminare un insieme casuale di campioni. (I riepiloghi sono dove l'intuizione è persa.) I grafici delle chiamate non ti danno le stesse informazioni, perché
- Non riassumono a livello di istruzioni e
- 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 questo è vero solo se vedi qualcosa una volta . Se vedi un problema su più di un campione, è reale.
P.S. 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 in Java.
P.P.S In generale, più livelli di astrazione hai nel tuo software, più è probabile che scoprirai che questa è la causa dei problemi di prestazioni (e l'opportunità di aumentare la velocità).
Aggiunto :Potrebbe non essere ovvio, ma la tecnica di campionamento dello stack funziona altrettanto bene in presenza di ricorsione. Il motivo è che il tempo che verrebbe risparmiato rimuovendo un'istruzione è approssimato dalla frazione di campioni che la contengono, indipendentemente dal numero di volte in cui può verificarsi all'interno di un campione.
Un'altra obiezione che sento spesso è:"Si fermerà in un punto casuale e mancherà il vero problema ".Questo deriva dall'avere un'idea precedente 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 è l'incredulità. Questo è naturale, ma puoi assicurati che se trova un problema è reale, e viceversa.
Aggiunto :Lasciatemi fare una spiegazione bayesiana di come funziona. Supponiamo che ci sia qualche istruzione I
(call o altro) che si trova nello stack delle chiamate una frazione f
del tempo (e quindi costa così tanto). Per semplicità, supponiamo di non sapere cosa f
è, ma supponiamo che sia 0,1, 0,2, 0,3, ... 0,9, 1,0 e che la probabilità a priori di ciascuna di queste possibilità sia 0,1, quindi tutti questi costi sono ugualmente probabili a priori.
Quindi supponiamo di prendere solo 2 campioni di stack e di vedere l'istruzione I
su entrambi i campioni, osservazione designata o=2/2
. Questo ci fornisce nuove stime della frequenza f
di 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 è 92%, rispetto all'ipotesi precedente del 60%.
Supponiamo che le ipotesi precedenti siano diverse. Supponiamo di assumere P(f=0.1)
è .991 (quasi certo) e tutte le altre possibilità sono quasi impossibili (0.001). In altre parole, la nostra prima 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 P(f >= 0.5)
è del 26%, in aumento rispetto all'ipotesi 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 con precisione quale sia il costo, ma solo che è abbastanza grande da valere la pena correggerla.
Ancora un altro modo di vederlo è chiamato la regola della successione. Se lanci una moneta 2 volte ed esce testa entrambe le volte, cosa ti dice sulla probabile ponderazione della moneta? Il modo rispettato per rispondere è diciamo che è una distribuzione Beta, con valore medio (number of hits + 1) / (number of tries + 2) = (2+1)/(2+2) = 75%
.
(La chiave è che vediamo I
più di una volta. Se lo vediamo solo una volta, non ci dice molto tranne quel f
> 0.)
Quindi, anche un numero molto piccolo di campioni può dirci molto sul costo delle istruzioni che vede. (E li vedrà con una frequenza, in media, proporzionale al loro costo. Se n
vengono prelevati campioni e f
è il costo, quindi I
apparirà su nf+/-sqrt(nf(1-f))
campioni. Esempio, n=10
, f=0.3
, ovvero 3+/-1.4
campioni.)
Aggiunto :Per dare un'idea intuitiva della differenza tra la misurazione e il campionamento casuale dello stack:
Ora ci sono profiler che campionano lo stack, anche all'ora esatta, ma quello che viene fuori è misurazioni (o percorso caldo, o punto caldo, da cui può facilmente nascondersi un "collo di bottiglia"). Quello che non ti mostrano (e potrebbero facilmente) sono i campioni stessi. E se il tuo obiettivo è trovare il collo di bottiglia, il numero che devi vedere è in media , 2 diviso per la frazione di tempo impiegata. Quindi, se impiega il 30% del tempo, 2/.3 =6,7 campioni, in media, lo mostreranno, e la possibilità che 20 campioni lo mostrino è del 99,2%.
Ecco un'illustrazione improvvisata della differenza tra l'esame delle misurazioni e l'esame dei campioni di stack. Il collo di bottiglia potrebbe essere un grosso blob come questo o numerosi piccoli, non fa differenza.
La misura è orizzontale; ti dice quale frazione di tempo impiegano 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'intero motivo del tempo speso, non solo quanto.
Puoi usare Valgrind con le seguenti opzioni
valgrind --tool=callgrind ./(Your binary)
Genererà un file chiamato callgrind.out.x
. Puoi quindi utilizzare kcachegrind
strumento per leggere questo file. Ti darà un'analisi grafica delle cose con risultati come quali linee costano quanto.
Presumo tu stia usando GCC. La soluzione standard sarebbe profilare con gprof.
Assicurati di aggiungere -pg
alla compilazione prima della profilazione:
cc -o myprog myprog.c utils.c -g -pg
Non l'ho ancora provato ma ho sentito parlare bene di google-perftools. Vale sicuramente la pena provare.
Domanda correlata qui.
Alcune altre parole d'ordine sono gprof
non fa il lavoro per te:Valgrind, Intel VTune, Sun DTrace.