Quali sono alcune buone strategie per migliorare le prestazioni seriali del mio codice?


66

Lavoro nella scienza computazionale e, di conseguenza, passo una quantità non banale del mio tempo cercando di aumentare il rendimento scientifico di molti codici, oltre a comprendere l'efficienza di questi codici.

Supponiamo di aver valutato il rapporto tra prestazioni / leggibilità / riusabilità / manutenibilità del software su cui sto lavorando e ho deciso che è ora di andare per le prestazioni. Supponiamo anche che io sappia che non ho un algoritmo migliore per il mio problema (in termini di flop / se larghezza di banda della memoria). Puoi anche supporre che la mia base di codice sia in un linguaggio di basso livello come C, C ++ o Fortran. Infine, supponiamo che non ci sia parallelismo nel codice o che siamo interessati solo alle prestazioni su un singolo core.

Quali sono le cose più importanti da provare prima? Come faccio a sapere quante prestazioni posso ottenere?

Risposte:


66

Prima di tutto, come hanno sottolineato l' abilità e Dan , la profilazione è essenziale. Personalmente uso l'amplificatore VTune di Intel su Linux poiché mi offre una visione molto dettagliata di dove è stato impiegato il tempo a fare cosa.

Se non hai intenzione di cambiare l'algoritmo (ovvero se non ci saranno cambiamenti importanti che renderanno obsolete tutte le tue ottimizzazioni), ti suggerirei di cercare alcuni dettagli di implementazione comuni che possano fare la differenza:

  • Località di memoria : i dati letti / utilizzati insieme vengono memorizzati anche insieme o stai raccogliendo pezzi qua e là?

  • Allineamento di memoria : i vostri doppi sono effettivamente allineati a 4 byte? Come hai fatto le valigie structs? Per essere pedanti, usa posix_memaligninvece di malloc.

  • Efficienza della cache : la località si occupa della maggior parte dei problemi di efficienza della cache, ma se si dispone di alcune piccole strutture di dati che si leggono / scrivono spesso, è utile se si tratta di un multiplo intero o di una frazione di una riga della cache (in genere 64 byte). Aiuta anche se i tuoi dati sono allineati alle dimensioni di una linea di cache. Ciò può ridurre drasticamente il numero di letture necessarie per caricare un dato.

  • Vettorializzazione : No, non impazzire con l'assemblatore codificato a mano. gccoffre tipi vettoriali che vengono tradotti in SSE / AltiVec / qualunque istruzione automagicamente.

  • Parallelismo a livello di istruzione : il figlio bastardo della vettorializzazione. Se alcuni calcoli spesso ripetuti non vettorizzano bene, puoi provare ad accumulare valori di input e calcolare più valori contemporaneamente. È un po 'come svolgersi in loop. Quello che stai sfruttando qui è che la tua CPU di solito avrà più di un'unità a virgola mobile per core.

  • Precisione aritmetica : hai davvero bisogno dell'aritmetica a doppia precisione in tutto ciò che fai? Ad esempio, se stai calcolando una correzione in una iterazione di Newton, di solito non hai bisogno di tutte le cifre che stai calcolando. Per una discussione più approfondita, consulta questo documento.

Alcuni di questi trucchi vengono utilizzati in daxpy_cvec questo thread. Detto questo, se stai usando Fortran (non un linguaggio di basso livello nei miei libri), avrai pochissimo controllo sulla maggior parte di questi "trucchi".

Se si esegue un hardware dedicato, ad esempio un cluster che si utilizza per tutte le esecuzioni di produzione, è possibile che si desideri leggere le specifiche delle CPU utilizzate. Non che dovresti scrivere cose nell'assemblatore direttamente per quell'architettura, ma potrebbe ispirarti a trovare altre ottimizzazioni che potresti aver perso. Conoscere una funzione è un primo passo necessario per scrivere il codice che può sfruttarla.

Aggiornare

È da un po 'che non scrivo che non ho notato che è diventata una risposta così popolare. Per questo motivo, vorrei aggiungere un punto importante:

  • Parla con il tuo informatico locale : Non sarebbe bello se ci fosse una disciplina che si occupava esclusivamente di rendere gli algoritmi e / o i calcoli più efficienti / eleganti / paralleli, e tutti potremmo andare a chiedere loro un consiglio? Bene, buone notizie, esiste quella disciplina: Informatica. È probabile che il tuo istituto abbia persino un intero dipartimento ad esso dedicato. Parla con questi ragazzi.

Sono sicuro per un certo numero di scienziati non informatici che riporteranno ricordi di discussioni frustranti con detta disciplina che non ha portato a nulla o ricordi degli aneddoti di altre persone. Non scoraggiarti. La collaborazione interdisciplinare è una cosa complicata e richiede un po 'di lavoro, ma i premi possono essere enormi.

Nella mia esperienza, come informatico (CS), il trucco è quello di ottenere sia le aspettative che la comunicazione giusta.

Aspettativa : un CS ti aiuterà solo se pensa che il tuo problema sia interessante. Questo praticamente esclude il tentativo di ottimizzare / vettorializzare / parallelizzare un pezzo di codice che hai scritto, ma che non hai davvero commentato, per un problema che non capiscono. I CS sono generalmente più interessati al problema sottostante, ad esempio gli algoritmi utilizzati per risolverlo. Non dare loro la tua soluzione , dai loro il tuo problema .

Inoltre, preparatevi al CS per dire " questo problema è già stato risolto " e darvi solo un riferimento a un documento. Un consiglio: leggi quel documento e, se si applica davvero al tuo problema, implementa qualsiasi algoritmo che suggerisce. Questo non è un CS compiaciuto, è un CS che ti ha appena aiutato. Non essere offeso, ricorda: se il problema non è interessante dal punto di vista computazionale, ovvero è già stato risolto e la soluzione dimostrata ottimale, non funzioneranno su di esso, tanto meno codificherai per te.

In termini di comunicazione , ricorda che la maggior parte dei CS non sono esperti nel tuo settore e spiega il problema in termini di cosa stai facendo, al contrario di come e perché . Di solito non ci interessa davvero il perché e il come , beh, ciò che facciamo meglio.

Ad esempio, attualmente sto lavorando con un gruppo di cosmologi computazionali per scrivere una versione migliore del loro codice di simulazione, basato su SPH e Multipole . Ci sono voluti circa tre incontri per smettere di parlare in termini di materia oscura e aloni della galassia (eh?) E per approfondire il nucleo del calcolo, cioè che devono trovare tutti i vicini entro un determinato raggio di ogni particella, calcolare alcuni quantità su di essi, quindi ricopri nuovamente tutti i vicini e applica tale quantità in altri calcoli. Quindi sposta le particelle, o almeno alcune di esse, e fai di nuovo tutto. Vedi, mentre il primo può essere incredibilmente interessante (lo è!), Il secondo è quello che devo capire per iniziare a pensare agli algoritmi.

Ma sto divergendo dal punto principale: se sei davvero interessato a rendere veloce il tuo calcolo e non sei un informatico, vai a parlare con uno.


4
man mano che gli strumenti di profilazione vanno, non dimenticherei di valgrind .
GertVdE,

1
Sono d'accordo con te Pedro, quando il programma in fase di ottimizzazione è come una macchina da corsa F1, già quasi ottimale. I programmi che vedo in pratica, scientifici e non, spesso sono più simili a Cadillac Coupe DeVilles. Per ottenere prestazioni reali, è possibile tagliare tonnellate di grasso. Successivamente, la rasatura del ciclo inizia a fare progressi.
Mike Dunlavey,

1
@MikeDunlavey: completamente d'accordo. Ho aggiunto un aggiornamento alla mia risposta per risolvere problemi più algoritmicamente correlati.
Pedro,

1
@MikeDunlavey, I am CS folk :)
Pedro

2
L'ho dimostrato in un discorso alla U Mass. Lowell. Era una demo live, che mostrava tutte le fasi dello speedup 730x. Penso che un professore abbia capito il punto, su una mezza dozzina.
Mike Dunlavey,

38

Il software scientifico non è molto diverso dagli altri software, per quanto riguarda il modo di sapere cosa deve essere ottimizzato.

Il metodo che uso è una pausa casuale . Ecco alcuni degli speedups che ha trovato per me:

Se una grande parte del tempo viene impiegata in funzioni come loge exp, posso vedere quali sono gli argomenti di tali funzioni, in funzione dei punti da cui vengono chiamati. Spesso vengono chiamati più volte con lo stesso argomento. In tal caso, la memorizzazione nella memoria produce un fattore di accelerazione enorme.

Se sto usando le funzioni BLAS o LAPACK, potrei scoprire che una grande parte del tempo viene impiegata nelle routine per copiare array, moltiplicare matrici, trasformazioni di choleski, ecc.

  • La routine per copiare gli array non è lì per la velocità, è lì per comodità. Potresti scoprire che esiste un modo meno conveniente, ma più veloce, per farlo.

  • Le routine per moltiplicare o invertire le matrici, o prendere trasformazioni di choleski, tendono ad avere argomenti di carattere che specificano opzioni, come 'U' o 'L' per il triangolo superiore o inferiore. Ancora una volta, quelli sono lì per comodità. Quello che ho scoperto è stato che, poiché le mie matrici non erano molto grandi, le routine impiegavano più della metà del loro tempo a chiamare la subroutine per confrontare i personaggi solo per decifrare le opzioni. Scrivere versioni per scopi speciali delle routine matematiche più costose ha prodotto un enorme aumento di velocità.

Se posso solo espandere su quest'ultimo: la routine DGEMM moltiplica la matrice chiama LSAME per decodificare i suoi argomenti di carattere. Osservando il tempo percentuale inclusivo (l'unica statistica che vale la pena guardare) i profilatori considerati "buoni" potrebbero mostrare DGEMM usando una percentuale del tempo totale, come l'80%, e LSAME usando una percentuale del tempo totale, come il 50%. Guardando il primo, saresti tentato di dire "beh, deve essere fortemente ottimizzato, quindi non posso farci molto". Guardando quest'ultimo, saresti tentato di dire "Eh? Di che si tratta? È solo una piccola routine da teenager. Questo profiler deve essere sbagliato!"

Non è sbagliato, non ti sta solo dicendo ciò che devi sapere. Ciò che la pausa casuale ti mostra è che DGEMM è sull'80% dei campioni dello stack e LSAME è sul 50%. (Non hai bisogno di molti campioni per rilevarlo. Di solito è 10.) Inoltre, su molti di questi campioni, DGEMM sta chiamando LSAME da un paio di diverse righe di codice.

Quindi ora sai perché entrambe le routine stanno impiegando così tanto tempo inclusivo. Sai anche da dove vengono chiamati per passare tutto questo tempo nel tuo codice . Ecco perché uso una pausa casuale e prendo una visione itterica dei profiler, non importa quanto siano ben fatti. Sono più interessati a ottenere misurazioni che a dirti cosa sta succedendo.

È facile supporre che le routine di libreria matematica siano state ottimizzate all'ennesima potenza, ma in realtà sono state ottimizzate per essere utilizzabili per una vasta gamma di scopi. Devi vedere cosa sta realmente succedendo, non cosa è facile supporre.

AGGIUNTO: Quindi per rispondere alle ultime due domande:

Quali sono le cose più importanti da provare prima?

Prendi 10-20 stack stack e non riassumili, capisci cosa ti dicono ciascuno. Fallo per primo, ultimo e intermedio. (Non c'è "prova", giovane Skywalker.)

Come faccio a sapere quante prestazioni posso ottenere?

Xβ(S+1,(n-S)+1)Sn1/(1-X)n=10S=5X
inserisci qui la descrizione dell'immagine
XX

Come ti ho indicato prima, puoi ripetere l'intera procedura fino a quando non puoi più, e il rapporto di accelerazione composto può essere abbastanza grande.

(S+1)/(n+2)=3/22=13.6%.) La curva inferiore nel seguente grafico è la sua distribuzione:

inserisci qui la descrizione dell'immagine

Considera se abbiamo prelevato fino a 40 campioni (più di quanto io abbia mai fatto contemporaneamente) e abbiamo riscontrato un problema solo su due di essi. Il costo stimato (modalità) di quel problema è del 5%, come mostrato sulla curva più alta.

Che cos'è un "falso positivo"? È che se risolvi un problema ti rendi conto di un guadagno più piccolo del previsto, che rimpiangi di averlo risolto. Le curve mostrano (se il problema è "piccolo") che, mentre il guadagno potrebbe essere inferiore alla frazione dei campioni che lo mostrano, in media sarà più grande.

Esiste un rischio molto più grave: un "falso negativo". Questo è quando c'è un problema, ma non è stato trovato. (Contribuire a questo è "bias di conferma", in cui l'assenza di prove tende a essere trattata come prova di assenza).

Quello che ottieni con un profiler (buono) è che ottieni misurazioni molto più precise (quindi meno possibilità di falsi positivi), a scapito di informazioni molto meno precise su quale sia effettivamente il problema (quindi meno possibilità di trovarlo e ottenere qualsiasi guadagno). Ciò limita la velocità complessiva che può essere raggiunta.

Vorrei incoraggiare gli utenti dei profiler a segnalare i fattori di accelerazione che effettivamente mettono in pratica.


C'è un altro punto da chiarire. La domanda di Pedro sui falsi positivi.

Ha detto che potrebbe esserci una difficoltà quando si affrontano piccoli problemi in un codice altamente ottimizzato. (Per me, un piccolo problema è quello che rappresenta il 5% o meno del tempo totale.)

Dato che è del tutto possibile costruire un programma totalmente ottimale, tranne per il 5%, questo punto può essere affrontato solo empiricamente, come in questa risposta . Per generalizzare dall'esperienza empirica, va così:

Un programma, come scritto, in genere contiene diverse opportunità di ottimizzazione. (Possiamo chiamarli "problemi", ma spesso sono un codice perfettamente valido, semplicemente in grado di migliorare considerevolmente.) Questo diagramma illustra un programma artificiale che richiede un certo periodo di tempo (100s, diciamo) e contiene problemi A, B, C, ... che, una volta trovato e risolto, risparmia il 30%, il 21%, ecc. dei 100 originali.

inserisci qui la descrizione dell'immagine

Si noti che il problema F costa il 5% del tempo originale, quindi è "piccolo" e difficile da trovare senza 40 o più campioni.

Tuttavia, i primi 10 campioni trovano facilmente il problema A. ** Quando viene risolto, il programma impiega solo 70 secondi, per una velocità di 100/70 = 1,43x. Ciò non solo rende il programma più veloce, ma ingrandisce, in base a tale rapporto, le percentuali prese dai restanti problemi. Ad esempio, il problema B originariamente ha richiesto 21 secondi, che erano il 21% del totale, ma dopo aver rimosso A, B prende 21 secondi su 70 o 30%, quindi è più facile trovare quando l'intero processo viene ripetuto.

Una volta che il processo viene ripetuto cinque volte, ora il tempo di esecuzione è di 16,8 secondi, fuori dal quale il problema F è del 30%, non del 5%, quindi 10 campioni lo trovano facilmente.

Questo è il punto. Empiricamente, i programmi contengono una serie di problemi con una distribuzione delle dimensioni e qualsiasi problema rilevato e risolto facilita la ricerca di quelli rimanenti. Per raggiungere questo obiettivo, nessuno dei problemi può essere saltato perché, se lo sono, siedono lì prendendo tempo, limitando l'accelerazione totale e non riuscendo a ingrandire i problemi rimanenti. Ecco perché è molto importante trovare i problemi che si nascondono .

Se i problemi da A a F vengono rilevati e risolti, l'accelerazione è 100 / 11,8 = 8,5x. Se ne manca uno, ad esempio D, la velocità è solo 100 / (11,8 + 10,3) = 4,5x. Questo è il prezzo pagato per falsi negativi.

Quindi, quando il profiler dice "non sembra esserci alcun problema significativo qui" (vale a dire un buon programmatore, questo è praticamente un codice ottimale), forse è giusto, e forse non lo è. (Un falso negativo .) Non sai con certezza se ci sono più problemi da risolvere, per una maggiore velocità, a meno che tu non provi un altro metodo di profilazione e scopri che ci sono. Nella mia esperienza, il metodo di profilazione non ha bisogno di un gran numero di campioni, riassunti, ma di un piccolo numero di campioni, in cui ogni campione è compreso a fondo abbastanza da riconoscere ogni opportunità di ottimizzazione.

2/0.3=6.671 - pbinom(1, numberOfSamples, sizeOfProblem)1 - pbinom(1, 20, 0.3) = 0.9923627

Xβ(S+1,(n-S)+1)nSy1/(1-X)Xyy-1Distribuzione BetaPrime . L'ho simulato con 2 milioni di campioni, arrivando a questo comportamento:

         distribution of speedup
               ratio y

 s, n    5%-ile  95%-ile  mean
 2, 2    1.58    59.30   32.36
 2, 3    1.33    10.25    4.00
 2, 4    1.23     5.28    2.50
 2, 5    1.18     3.69    2.00
 2,10    1.09     1.89    1.37
 2,20    1.04     1.37    1.17
 2,40    1.02     1.17    1.08

 3, 3    1.90    78.34   42.94
 3, 4    1.52    13.10    5.00
 3, 5    1.37     6.53    3.00
 3,10    1.16     2.29    1.57
 3,20    1.07     1.49    1.24
 3,40    1.04     1.22    1.11

 4, 4    2.22    98.02   52.36
 4, 5    1.72    15.95    6.00
 4,10    1.25     2.86    1.83
 4,20    1.11     1.62    1.31
 4,40    1.05     1.26    1.14

 5, 5    2.54   117.27   64.29
 5,10    1.37     3.69    2.20
 5,20    1.15     1.78    1.40
 5,40    1.07     1.31    1.17

(n+1)/(n-S)S=ny

Questo è un diagramma della distribuzione dei fattori di accelerazione e dei loro mezzi per 2 hit su 5, 4, 3 e 2 campioni. Ad esempio, se vengono prelevati 3 campioni e 2 di essi sono risultati positivi di un problema e tale problema può essere rimosso, il fattore di velocità media sarebbe 4x. Se i 2 hit vengono visualizzati in soli 2 campioni, l'accelerazione media non è definita, concettualmente perché esistono programmi con loop infiniti con probabilità diversa da zero!

inserisci qui la descrizione dell'immagine


1
Uhm ... Non ottieni esattamente queste informazioni guardando i grafici delle chiamate del profiler o i riepiloghi di tipo "bottom-up" forniti da VTune?
Pedro,

2
@Pedro: se solo. In uno stack di esempio (e relative variabili) è codificato l'intero motivo per cui viene incrementato il tempo. Non puoi liberartene se non sai perché viene speso. Alcuni problemi possono essere trovati con informazioni limitate, ma non tutti . Se ne ottieni solo alcuni, ma non tutti, i problemi che non ottieni finiscono per bloccarti da ulteriori accelerazioni. Controlla qui e qui .
Mike Dunlavey,

Probabilmente stai confrontando il tuo metodo con una cattiva profilazione ... Potresti anche esaminare il profilo di ogni routine, indipendentemente dal suo contributo al tempo di esecuzione totale, e cercare miglioramenti, con lo stesso effetto. Ciò di cui sono preoccupato nel tuo approccio è il numero crescente di falsi positivi che finirai per rintracciare man mano che gli "hotspot" nel tuo codice diventano sempre più piccoli.
Pedro,

@Pedro: continua a prelevare campioni fino a quando non vedi qualcosa che puoi correggere su più di un campione. Il beta distr dice quanto può risparmiare, se ti interessa, ma se hai paura di ottenere meno speedup di quanto mostri, tieni presente che stai gettando via la possibilità che potrebbe anche essere più (ed è distorto a destra ). Il pericolo maggiore, con i profilatori riepilogativi, è costituito da falsi negativi . Può esserci un problema, ma stai solo sperando che il tuo intuito lo annusi quando il profiler non è molto specifico su dove potrebbe essere.
Mike Dunlavey,

@Pedro: l'unica debolezza che conosco è quando, guardando un'istantanea, non riesci a capire perché quel tempo viene speso, ad esempio se sta semplicemente elaborando eventi asincroni in cui si nasconde il richiedente o protocolli asincroni. Per un codice più "normale", mostrami un profiler "buono" e ti mostrerò un problema con cui ha problemi o che semplicemente non riesce a trovare (facendoti ricadere sulla tua intelligenza fallibile). Generalmente il modo per costruire un simile problema è assicurarsi che lo scopo che viene servito non possa essere decifrato localmente. E tali problemi abbondano nel software.
Mike Dunlavey,

23

Non solo devi avere una conoscenza intima del tuo compilatore , ma anche una conoscenza intima della tua architettura di destinazione e del tuo sistema operativo .

Cosa può influire sulle prestazioni?

Se vuoi spremere fino all'ultima oncia di prestazione, ogni volta che cambi l'architettura di destinazione, dovrai modificare e ri-ottimizzare il tuo codice. Qualcosa che era un'ottimizzazione con una CPU potrebbe diventare non ottimale nella revisione successiva di quella stessa CPU.

Un eccellente esempio di ciò sarebbe la cache della CPU. Sposta il tuo programma da una CPU con una piccola cache veloce a una con una cache leggermente più lenta, leggermente più grande e la tua profilazione potrebbe cambiare in modo significativo.

Anche se l'architettura di destinazione non cambia, anche le modifiche di basso livello a un sistema operativo possono influire sulle prestazioni. Le patch di mitigazione Spectre e Meltdown hanno avuto un impatto enorme in alcuni carichi di lavoro, quindi potrebbero forzare una rivalutazione delle ottimizzazioni.

Come posso mantenere il mio codice ottimizzato?

Quando si sviluppa un codice altamente ottimizzato, è necessario mantenerlo modulare e semplificare lo scambio di versioni diverse dello stesso algoritmo in entrata e in uscita, eventualmente anche selezionando la versione specifica utilizzata in fase di esecuzione, a seconda delle risorse disponibili e delle dimensioni / complessità di dati da elaborare.

Modularità significa anche essere in grado di utilizzare la stessa suite di test su tutti i versioni ottimizzate e unoptimised, che consente di verificare che tutti si comportano allo stesso e rapidamente il modo ciascuno in un like-for-like confronto. Vado un po 'più in dettaglio nella mia risposta a Come documentare e insegnare agli altri un codice computazionalmente "ottimizzato oltre il riconoscimento"? .

Ulteriori letture

Inoltre, consiglio caldamente di dare un'occhiata all'eccellente articolo di Ulrich Drepper What Every Programmer dovrebbe sapere sulla memoria , il cui titolo è un omaggio al altrettanto fantastico di David Goldberg che cosa ogni scienziato informatico dovrebbe sapere sull'aritmetica a virgola mobile .

Ricorda che ogni ottimizzazione ha il potenziale per diventare una futura anti-ottimizzazione , quindi dovrebbe essere considerata un possibile odore di codice, da ridurre al minimo. La mia risposta a La micro-ottimizzazione è importante durante la codifica? fornisce un esempio concreto di ciò per esperienza personale.


8

Penso che tu abbia formulato la domanda in modo troppo restrittivo. Dal mio punto di vista, un atteggiamento utile è quello di vivere supponendo che solo le modifiche alle strutture dati e agli algoritmi possano produrre significativi guadagni in termini di prestazioni su codici che superano le poche 100 righe e credo di dover ancora trovare un controesempio per questa affermazione.


3
Concordato in linea di principio, ma non bisogna sottovalutare l'interazione tra le prestazioni di un algoritmo / struttura dei dati e i dettagli dell'hardware sottostante. Ad esempio, gli alberi binari bilanciati sono ottimi per la ricerca / archiviazione dei dati, ma a seconda della latenza della memoria globale, una tabella hash potrebbe essere migliore.
Pedro,

1
Concordato. Gli algoritmi e la struttura dei dati possono fornire miglioramenti da O (10) a O (100). Tuttavia, per alcuni problemi limitati di calcolo (come nei calcoli della dinamica molecolare, astrofisica, elaborazione di immagini e video in tempo reale, finanza) un loop critico altamente ottimizzato può significare un tempo di esecuzione complessivo da 3 a 10 volte più veloce.
fcruz,

Ho visto loop nidificati male ordinati in codici di "produzione" di dimensioni sostanziali. A parte questo, penso che tu abbia ragione.
dmckee,

8

La prima cosa che dovresti fare è profilare il tuo codice. Vuoi scoprire quali parti del tuo programma ti stanno rallentando prima di iniziare l'ottimizzazione, altrimenti potresti finire per ottimizzare una parte del tuo codice che comunque non consumava gran parte del tempo di esecuzione.

Linux

gprof è abbastanza buono, ma ti dice solo quanto tempo impiega ogni funzione piuttosto che ogni riga.

Apple OS X

Potresti voler provare Shark . È disponibile nel sito per sviluppatori Apple in Download> Strumenti per sviluppatori> CHUD 4.6.2, la versione precedente qui . CHUD contiene anche altri strumenti di profilazione come front-end BigTop, strumento di ricerca dell'indice PMC, profiler a livello di funzione di Saturno e molti altri comandi. Shark arriverà con una versione da riga di comando.


+1 profilo? Sì, in un certo senso ... È molto meglio che indovinare, ma ecco un elenco di problemi che si applicano soprattutto a gprof e a molti altri profiler.
Mike Dunlavey,

Shark è un vecchio comando in OS X? Più qui . Con Mountain Lion, dovrei usare Instruments?
hhh

@hhh: era un profiler GUI per Mac, anche se sembra che non venga più mantenuto. Non ho programmato su una macchina Apple da quando ho scritto questa risposta, quindi non posso aiutarti molto.
Dan,

1
È disponibile nel sito per sviluppatori Apple in Download> Strumenti per sviluppatori> CHUD 4.6.2. La versione precedente qui e contiene tutti i tipi di cose di profilazione - sfortunatamente questa installazione non riesce: "Contatta il produttore", nessuna idea sul bug. Lo squalo è stato rimosso dall'Xcode apparentemente dopo Lion e successivamente rimesso sul sito Apple Dev dopo essere stato uno strumento gratuito in MacUpdate.
hhh

@hhh: sembri più qualificato a rispondere a questo di me. Sentiti libero di modificare la mia risposta per aggiornarla o di scrivere la tua.
Dan,

7

Per quanto riguarda le prestazioni che puoi ottenere, prendi i risultati dalla profilazione del tuo codice e diciamo che identifichi un pezzo che richiede "p" frazione del tempo. Se dovessi migliorare le prestazioni di quel pezzo solo di un fattore di "s", la tua velocità complessiva sarà 1 / ((1-p) + p / s). Pertanto è possibile aumentare al massimo la velocità di un fattore 1 / (1-p). Spero che tu abbia aree di alta p! Questo è l'equivalente della legge di Amdahl per l'ottimizzazione seriale.


5

L'ottimizzazione del codice deve essere eseguita con cura. Supponiamo anche che tu abbia già eseguito il debug del codice. Puoi risparmiare molto tempo se segui determinate priorità, vale a dire:

  1. Se possibile, utilizzare librerie altamente ottimizzate (o professionalmente ottimizzate). Alcuni esempi potrebbero includere FFTW, OpenBlas, Intel MKL, librerie NAG, ecc. A meno che tu non abbia molto talento (come lo sviluppatore di GotoBLAS), probabilmente non puoi battere i professionisti.

  2. Usa un profiler (alcuni nel seguente elenco sono già stati nominati in questo thread - Intel Tune, valgrind, gprof, gcov, ecc.) Per scoprire quali parti del tuo codice impiegano più tempo. Inutile perdere tempo ottimizzando porzioni di codice che vengono chiamate raramente.

  3. Dai risultati del profiler, osserva la parte del codice che ha impiegato più tempo. Determina la natura del tuo algoritmo: è legato alla CPU o alla memoria? Ognuno richiede un diverso insieme di tecniche di ottimizzazione. Se si verificano molti errori nella cache, la memoria potrebbe essere il collo di bottiglia: la CPU sta sprecando cicli di clock in attesa che la memoria diventi disponibile. Pensa se il loop si adatta alla cache L1 / L2 / L3 del tuo sistema. Se hai delle istruzioni "if" nel tuo ciclo, controlla se il profiler dice qualcosa sull'imputazione errata del ramo? Qual è la penalità per errore di filiale nel tuo sistema? A proposito, è possibile ottenere i dati di errore di filiale dai manuali di riferimento per l'ottimizzazione di Intel [1]. Si noti che la penalità per errore di branca è specifica del processore, come vedrai nel manuale Intel.

  4. Infine, affronta i problemi identificati dal profiler. Alcune tecniche sono già state discusse qui. Sono inoltre disponibili numerose risorse valide, affidabili e complete per l'ottimizzazione. Per citarne solo due, c'è il Manuale di riferimento per l'ottimizzazione di Intel [1] e i cinque manuali di ottimizzazione di Agner Fog [2]. Nota che ci sono alcune cose che potresti non dover fare, se il compilatore lo fa già, ad esempio lo svolgimento di cicli, l'allineamento della memoria, ecc. Leggi attentamente la documentazione del compilatore.

Riferimenti:

[1] Manuale di riferimento per l'ottimizzazione delle architetture Intel 64 e IA-32: http://www.intel.sg/content/dam/doc/manual/64-ia-32-architectures-optimization-manual.pdf

[2] Agner Fog, "Risorse per l'ottimizzazione del software": http://www.agner.org/optimize/

  • "Ottimizzazione del software in C ++: una guida all'ottimizzazione per piattaforme Windows, Linux e Mac"
  • "Ottimizzazione delle subroutine in linguaggio assembly: una guida all'ottimizzazione per piattaforme x86"
  • "La microarchitettura delle CPU Intel, AMD e VIA: una guida all'ottimizzazione per programmatori di assemblaggio e produttori di compilatori"
  • "Tabelle di istruzioni: elenchi di latenze di istruzione, throughput e guasti delle micro-operazioni per CPU Intel, AMD e VIA"
  • "Convocazione di convenzioni per diversi compilatori e sistemi operativi C ++"

3

Non sono uno scienziato computazionale come molti altri qui (quindi potrei sbagliarmi :)) ma oggigiorno è inutile dedicare troppo tempo alle prestazioni seriali purché utilizziamo librerie standard. Potrebbe essere più utile dedicare ulteriore tempo / impegno a rendere il codice più scalabile.

In ogni caso, ecco due esempi (se non li hai già letti) su come sono state migliorate le prestazioni (per problemi FE non strutturati).

Seriale : vedere la seconda metà del testo astratto e relativo.

Parallelo : in particolare la fase di inizializzazione, al punto 4.2.


3

Questa è forse più una meta-risposta che una risposta ...

Devi sviluppare un'intima familiarità con il tuo compilatore. Puoi acquisirlo nel modo più efficiente leggendo il manuale e sperimentando le opzioni.

Gran parte dei buoni consigli che le erogazioni di @Pedro possono essere implementati modificando la compilazione anziché il programma.


Non sono d'accordo con l'ultimo punto. Sapere cosa può fare il tuo compilatore è una cosa, ma scrivere il tuo codice in modo che il compilatore possa effettivamente fare qualcosa con esso è un problema completamente diverso. Non ci sono flag del compilatore che ordineranno i tuoi dati per te, utilizzeranno una precisione inferiore quando richiesto o riscriveranno i tuoi cicli più interni in modo che abbiano pochi o nessun ramo. Sapere che il tuo compilatore è una buona cosa, ma ti aiuterà solo a scrivere codice migliore, non renderà il tuo codice migliore di per sé.
Pedro,

1

Un modo semplice per profilare un programma (in Linux) è usare perfin statmodalità. Il modo più semplice è semplicemente eseguirlo come

perf stat ./my_program args ...

e ti darà un sacco di utili statistiche sulle prestazioni:

Performance counter stats for './simd_test1':

     3884.559489 task-clock                #    1.000 CPUs utilized
              18 context-switches          #    0.005 K/sec
               0 cpu-migrations            #    0.000 K/sec
             383 page-faults               #    0.099 K/sec
  10,911,904,779 cycles                    #    2.809 GHz
 <not supported> stalled-cycles-frontend
 <not supported> stalled-cycles-backend
  14,346,983,161 instructions              #    1.31  insns per cycle
   2,143,017,630 branches                  #  551.676 M/sec
          28,892 branch-misses             #    0.00% of all branches

     3.885986246 seconds time elapsed

A volte elencherà anche i carichi e gli errori D-cache. Se noti molti errori nella cache, il tuo programma richiede molta memoria e non gestisce bene le cache. In questi giorni, le CPU stanno diventando più veloci della larghezza di banda della memoria e di solito il problema è sempre l'accesso alla memoria.

Puoi anche provare perf record ./my_program; perf reportquale è un modo semplice per creare un profilo. Leggi le pagine man per saperne di più.

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.