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 log
e 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
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:
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.
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!