Dove ottimizzi?


9

Esistono due aree in cui è possibile ottimizzare la velocità in:

  • Dove trascorre la maggior parte del tempo
  • Il codice che si chiama di più

Qual è il posto migliore per iniziare a ottimizzare?

Spesso il codice chiamato più spesso ha già tempi di esecuzione bassi. Ottimizzi le aree più lente e meno chiamate o passi il tempo a ottimizzare le aree più veloci e molto utilizzate?


Ottimizza l'area dell'applicazione che maggiormente sollecita i tuoi clienti o la tua architettura, a seconda che i tuoi clienti o i tuoi server si lamentino più forte.
Andy,

È un'equazione di valore - la risposta potrebbe essere una delle due. Quando non hai un'analisi reale, vai con il tuo istinto, basato sul probabile profitto delle tue migliori idee.
Nicole,

Nessuno dei due. Cerca il codice che si trova nello stack per gran parte del tempo.
Mike Dunlavey,

Risposte:


4

Dovresti ignorare le piccole efficienze il 95% delle volte. Innanzitutto, fallo funzionare correttamente , quindi analizza ...

Il tuo design.

La scelta di algoritmi di alto livello può avere un impatto enorme sulle prestazioni complessive del software, al punto in cui una scelta apparentemente banale può fare la differenza tra l'attesa di 20 minuti per l'avvio del programma e un'interfaccia utente rapida e reattiva.

Ad esempio, in un gioco 3D: se inizi con un semplice elenco piatto di oggetti per il tuo grafico di scena, vedrai prestazioni estremamente scarse per un numero relativamente piccolo di oggetti; ma se invece si implementa una gerarchia di volumi (come un ottetto o BVH) e si tagliano parti dell'albero mentre si disegna, si noterà un enorme aumento delle prestazioni.

Quando il tuo design sembra corretto, puoi passare a ...

Logica di basso livello.

Anche gli algoritmi di livello inferiore possono avere un impatto significativo. Quando si esegue l'elaborazione delle immagini, ad esempio, se si legge l'immagine nell'ordine errato, si verificheranno notevoli rallentamenti mentre si verificano costanti errori nella cache L2; il riordino delle operazioni potrebbe comportare un aumento di dieci volte delle prestazioni.

A questo punto, profilo e trova il luogo in cui viene trascorsa la maggior parte del tempo del programma e trova un modo per eliminarlo.


Il programma su cui sto lavorando è corretto. Vogliamo renderlo più veloce se possibile, poiché è un servizio Web che può richiedere da 30 a un minuto per l'esecuzione.
Michael K,

1
@Michael: In tal caso, è tempo di ottenere uno strumento di profilazione per analizzare alcune esecuzioni tipiche del programma e individuare le sezioni di codice che funzionano più lentamente. Consiglio vivamente di usare uno strumento per questo. Puoi fare un certo importo per intuizione, ma a volte troverai una sorpresa in cui un'API che chiama impiega molto tempo, piuttosto che il tuo codice. In tal caso, è tempo di esaminare l'API e vedere quali altre funzioni sono disponibili o se dovresti scrivere la tua funzione di sostituzione ... Il maiale delle prestazioni effettive non è sempre il maiale delle prestazioni sospetto ...
FrustratedWithFormsDesigner

1
Abbiamo uno strumento di profilazione. Sto ancora imparando e cosa fare con le informazioni. È un argomento relativamente nuovo per me e molto interessante.
Michael K,

@Michael: Bene! Sei (si spera) sulla buona strada per il successo della messa a punto delle prestazioni! :)
FrustratedWithFormsDesigner il

+1: Questo è quello che stavo per dire, ma molto più eloquentemente.
Dominique McDonnell,

3

Innanzitutto, esegui un profiler per scoprire dove il tuo codice sta trascorrendo il suo tempo.

Quindi, guarda quei luoghi per vedere quali sembrano facili da ottimizzare.

Cerca le soluzioni più semplici che otterranno prima i guadagni più grandi (scegli la frutta a sospensione bassa). Non preoccuparti troppo di quanto sia importante, appunto. Se è facile, risolvilo. Si sommerà. 25 correzioni semplici potrebbero essere più veloci di 1 correzione grande e i loro effetti cumulativi potrebbero essere più grandi. Se è difficile, prendi nota o invia una segnalazione di bug in modo da poter dare la priorità in un secondo momento. Non preoccuparti così tanto di "grande" o "piccolo" a questo punto - fallo e basta, finché non arriverai a funzioni che impiegano pochissimo tempo. Una volta fatto questo, dovresti avere un'idea migliore di quale degli altri problemi che hai scoperto potrebbe ottenere le maggiori vittorie per il minor tempo di investimento.

Non dimenticare di dare seguito alla profilazione dopo le correzioni come una sorta di test di regressione, per verificare che le modifiche alle prestazioni abbiano avuto gli effetti sperati. Inoltre, non dimenticare di eseguire la tua suite di regressione, per assicurarti che nessuna funzionalità sia stata interrotta. A volte le cattive prestazioni indicano soluzioni alternative e il tentativo di risolvere le prestazioni interromperà la funzionalità.

Le piccole funzioni che non possono essere ottimizzate ma che impiegano molto tempo potrebbero comunque essere suggerimenti su dove ottimizzare. Perché quella funzione viene chiamata così tanto? C'è una funzione che chiama quella piccola funzione che non ha bisogno di usarla così tanto? Il lavoro viene duplicato o viene svolto un lavoro non necessario? Cerca nello stack i tempi in cui viene chiamato fino a quando non sei sicuro che debba essere chiamato così spesso e vedi se trovi una funzione più grande con un algoritmo inefficiente.

Modificato per aggiungere: poiché si dispone di funzionalità specifiche che richiedono molto tempo, provare a eseguire i passaggi precedenti con solo quella specifica funzione eseguita 10 o più volte.


2

È difficile da dire. Questo dipende davvero da cosa sta facendo il codice. Esegui un test delle prestazioni, ottieni un profilo delle prestazioni e guarda e vedi quanto tempo effettivo viene trascorso in varie aree. Le tue generalizzazioni sono ... generalizzazioni e variano da progetto a progetto.

Ad esempio, il codice che viene chiamato di più potrebbe semplicemente accedere a un file o una console. Non ha molto senso ottimizzare se una o due righe di codice non possono essere rese più semplici e potrebbe essere che qualsiasi sforzo per ottimizzare qualcosa del genere non valga il costo di codificarlo. Il codice meno chiamato potrebbe essere una query di dimensioni mostruose utilizzata in alcune funzioni orribilmente complesse. La funzione potrebbe essere chiamata solo 100 volte durante un'intera esecuzione (rispetto a 10000 per la semplice istruzione di registrazione), ma se occorrono 20 secondi per ogni ora di chiamata, forse è qui che dovrebbe iniziare l'ottimizzazione? Oppure potrebbe essere il contrario, con la query più grande è la più chiamata e l'istruzione di registrazione ne chiama solo una per ogni 100 query ...

Di solito non mi preoccupo di questo genere di cose (fino a quando non devo fare il tuning delle prestazioni) a meno che non abbia in anticipo un'idea di ciò che accadrà.


1

Bene, "noi" di solito non ottimizziamo fino a quando non c'è un'ovvia necessità di ottimizzazione quando qualcosa è inaccettabilmente lento.

E quando questa esigenza si manifesta di solito porta con sé buoni suggerimenti su ciò che richiede esattamente l'ottimizzazione.

Quindi la risposta è normale: "Dipende".


1

Dovresti usare un profiler su una manciata di esecuzioni tipiche e guardare il tempo totale impiegato in ogni parte del codice, indipendentemente da quanto o quanto spesso ci arrivi. L'ottimizzazione di queste parti dovrebbe sempre aumentare la velocità.

A seconda di quanto sia bassa la tua lingua di implementazione, dovresti anche scoprire quali parti causano la maggior parte delle mancate cache. Il consolidamento del codice chiamante aiuterà qui.


1

Il problema è che la frase "dove si trascorre la maggior parte del tempo" è ambigua.

Se significa "dove si trova il contatore del programma più spesso", ho visto i programmi in cui è stata impiegata la maggior parte del tempo in funzioni di libreria matematica di confronto stringhe, allocazione di memoria. In altre parole, funzioni che il programmatore quotidiano non dovrebbe mai toccare.

Se significa "dove nel codice del programmatore vengono eseguite istruzioni che consumano una grande frazione di tempo", questo è un concetto più utile.

Il problema con il concetto di "codice che viene chiamato di più" è che la quantità di tempo necessaria è il prodotto di quanto spesso viene chiamato e di quanto tempo impiega per chiamata (compresi callees e I / O). Poiché la quantità di tempo necessaria può variare su diversi ordini di grandezza, il numero di volte in cui viene chiamato non ti dice quanto sia un problema. La funzione A può essere chiamata 10 volte e richiedere 0,1 secondi, mentre la funzione B può essere chiamata 1000 volte e richiedere un microsecondo.

Una cosa che ti dirà dove cercare è questa: ogni volta che una riga di codice fa perdere tempo è nello stack . Quindi, ad esempio, se una riga di codice è un hot spot o se è una chiamata a una funzione di libreria o se è la 20a chiamata in un albero di chiamate a 30 livelli, se è responsabile per il 20% delle volte , quindi è in pila il 20% delle volte. I campioni in ordine casuale della pila avranno ciascuno una probabilità del 20% di visualizzarla. Inoltre, se è possibile prelevare campioni durante l'I / O, mostreranno quali sono gli account dell'I / O, che possono essere altrettanto o più dispendiosi dei cicli di CPU sprecati.

E questo è totalmente indipendente da quante volte viene invocato.


Per "programmatore di tutti i giorni non dovrebbe mai toccare" vuoi dire che non è probabile che tocchi? Inoltre, il campionamento della pila è un metodo pratico di profilazione?
Michael K,

@Michael: Sì, campionare lo stack è un metodo su cui si basano i moderni profiler, come Zoom . Inoltre, il funzionamento completamente manuale funziona sorprendentemente bene .
Mike Dunlavey,

Molto interessante. Ho qualche studio da fare ora!
Michael K,

@Michael: è come il concetto legale di responsabilità congiunta. In un determinato momento, la responsabilità del PC in un'istruzione è la responsabilità congiunta non solo di quell'istruzione, ma di ogni chiamata sopra di essa nello stack. L'eliminazione di uno qualsiasi di questi impedirebbe che entri in quel particolare stato.
Mike Dunlavey,

0

Ottimizza dove viene trascorsa la maggior parte del tempo, a meno che non vi sia un motivo particolare per non farlo (vale a dire, la maggior parte del tempo viene impiegato per l'elaborazione asincrona a cui agli umani non importa davvero se termina in 5 minuti o 10 minuti). Il codice più chiamato, naturalmente, tenderà a accumulare una porzione relativamente grande del tempo totale trascorso semplicemente perché anche i tempi di esecuzione brevi si sommano quando lo fai migliaia di volte.


0

Devi lavorare sul codice che impiega più tempo. Migliorare il codice che rappresenta solo una percentuale del tempo di esecuzione può solo darti un piccolo miglioramento.

Hai preso le misure in modo da sapere quale codice impiega più tempo?


0

Ero solito fare benchmarking e marketing per un fornitore di supercomputer, quindi battere la concorrenza in modo rapido non era la cosa più importante, era l'UNICA cosa. Questo tipo di risultato richiedeva l'uso di una combinazione di algoritmi validi e una struttura di dati che consentisse alle parti che consumano più CPU di eseguire un picco in modo efficiente. Ciò significava che dovevi avere una buona idea di quali sarebbero state le operazioni più impegnative dal punto di vista computazionale e quali tipi di strutture dati avrebbero permesso loro di funzionare più velocemente. Quindi si trattava di costruire l'applicazione attorno a quei kernel / strutture dati ottimizzati.

In senso più generale, il tipico dopo l'ottimizzazione dei fatti. Profili, guardi i punti caldi e i punti caldi che pensi di poter accelerare sono quelli su cui lavori. Ma raramente questo approccio ti darà qualcosa di simile all'implementazione più veloce possibile.

Più in generale, tuttavia, (nonostante gli errori algoritmici), per le macchine moderne devi pensare che le prestazioni siano determinate da tre cose: accesso ai dati, accesso ai dati e accesso ai dati! Scopri la gerarchia di memoria (registri, cache, TLB, pagine, ecc.) E progetta le tue strutture di dati per sfruttarle nel miglior modo possibile. In genere ciò significa che si desidera poter eseguire i loop all'interno di un footprint di memoria compatto. Se invece semplicemente scrivi (o ti viene data) un'applicazione e poi cerchi di ottimizzarla, di solito sei sellato da strutture di dati che fanno un cattivo uso della gerarchia di memoria, e cambiare le strutture di dati di solito comporta un grande esercizio di refactoring, quindi sei spesso bloccato.


0

Se vuoi un ritorno sul tuo sforzo di ottimizzazione, devi guardare il codice che impiega più tempo. Il mio obiettivo generale è qualcosa che richiede almeno l'80% delle volte. In genere, ho come obiettivo un aumento delle prestazioni di 10 volte. Questo a volte richiede un cambiamento sostanziale nella progettazione di quel codice. Questo tipo di cambiamento ti dà qualcosa che viene eseguito circa quattro volte più veloce.

Il mio miglior guadagno in assoluto è quello di ridurre il tempo di esecuzione da 3 giorni a 9 minuti. Il codice che ho ottimizzato è passato da 3 giorni a 3 minuti. L'applicazione la sostituì con quella ridotta a 9 secondi, ma ciò richiedeva un cambio di lingua e una completa riscrittura.

L'ottimizzazione di un'applicazione già veloce può essere una folle commissione. Avrei comunque preso di mira l'area prendendo più tempo. Se riesci a prendere qualcosa utilizzando il 10% delle volte per tornare istantaneamente, hai comunque bisogno del 90% delle volte. Hai rapidamente raggiunto la regola dei rendimenti decrescenti.

A seconda di ciò che si sta ottimizzando per la regola, si applica ancora. Cerca i principali utenti delle risorse e ottimizzali. Se la risorsa che stai ottimizzando è il collo di bottiglia dei sistemi, potresti scoprire che tutto ciò che fai è cambiare il collo di bottiglia in un'altra risorsa.


0

In genere nella maggior parte dei casi si tratterà di funzioni più scarse, non delle funzioni chiamate più spesso un miliardo di volte in un ciclo.

Quando si esegue la profilazione basata su campioni (con uno strumento o manualmente), spesso i punti di crisi più grandi si troveranno in chiamate a foglia minuscole che fanno cose semplici, come una funzione per confrontare due numeri interi.

Questa funzione spesso non trarrà beneficio da un'ottimizzazione molto elevata. Per lo meno quei punti caldi granulari raramente hanno la massima priorità. È la funzione che chiama quella funzione foglia che potrebbe essere il creatore di problemi, o la funzione che chiama la funzione che chiama la funzione, come un algoritmo di ordinamento non ottimale. Con buoni strumenti puoi eseguire il drill-down dalla chiamata al chiamante e anche vedere chi passa più tempo a chiamare la chiamata.

Spesso è un errore ossessionare i callees e non guardare i chiamanti lungo il grafico delle chiamate in una sessione di profilazione a meno che non si stiano facendo cose in modo molto inefficiente a livello micro. Altrimenti potresti sudare eccessivamente le piccole cose e perdere di vista il quadro generale. Solo avere un profiler in mano non ti protegge dall'ossessione per cose banali. È solo un primo passo nella giusta direzione.

Inoltre, devi assicurarti di eseguire operazioni di profilatura in linea con le cose che gli utenti vogliono effettivamente fare, altrimenti essere totalmente disciplinati e scientifici nelle misurazioni e nei benchmark è inutile poiché non si allinea con ciò che i clienti fanno con il prodotto. Una volta ho avuto un collega che ha messo a punto un algoritmo di suddivisione per suddividere un cubo in un miliardo di sfaccettature e ne era molto orgoglioso .... tranne per il fatto che gli utenti non suddividono semplici cubi di 6 poligoni in un miliardo sfaccettature. Il tutto rallentò fino a gattonare quando provò a correre su un modello di auto di produzione con oltre 100.000 poligoni da suddividere, a quel punto non riuscì nemmeno a fare 2 o 3 livelli di suddivisione senza rallentare a gattonare. In parole povere, ha scritto un codice che era super ottimizzato per input di dimensioni non irrealistiche che non

Devi ottimizzare i casi d'uso reali allineati con gli interessi dei tuoi utenti, altrimenti è peggio che inutile, dal momento che tutte quelle ottimizzazioni che tendono almeno a degradare in qualche modo la manutenibilità del codice hanno pochi vantaggi per l'utente e solo tutti quei negativi per la base di codice.

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.