performance - tua - tag title wordpress




Strategie di ottimizzazione delle prestazioni di ultima istanza (20)

Ci sono già un sacco di domande sulle prestazioni su questo sito, ma mi viene in mente che quasi tutte sono molto specifiche e abbastanza ristrette. E quasi tutti ripetono il consiglio per evitare l'ottimizzazione prematura.

Assumiamo:

  • il codice funziona già correttamente
  • gli algoritmi scelti sono già ottimali per le circostanze del problema
  • il codice è stato misurato e le routine incriminate sono state isolate
  • Verranno inoltre misurati tutti i tentativi di ottimizzazione per garantire che non peggiorino le cose

Quello che sto cercando qui sono le strategie e i trucchi per spremere fino all'ultimo percento in un algoritmo critico quando non c'è nient'altro da fare se non quello che serve.

Idealmente, cerca di rendere le risposte linguistiche agnostiche e indica eventuali lati negativi delle strategie suggerite, laddove applicabile.

Aggiungerò una risposta con i miei suggerimenti iniziali e attendo con ansia qualsiasi altra cosa la comunità di Overflow dello stack possa pensare.


Altri suggerimenti:

  • Evita I / O : Qualsiasi I / O (disco, rete, porte, ecc.) Sarà sempre molto più lento di qualsiasi codice che sta eseguendo calcoli, quindi elimina qualsiasi I / O di cui non hai strettamente bisogno.

  • Sposta I / O in avanti : carica in anticipo tutti i dati di cui hai bisogno per un calcolo, in modo da non avere ripetute attese di I / O nel nucleo di un algoritmo critico (e forse come risultato ripetuto ricerca di dischi, quando si caricano tutti i dati in un colpo si può evitare di cercare).

  • Ritardo I / O : non scrivere i risultati fino a quando il calcolo è finito, memorizzarli in una struttura dati e poi scaricarli in un colpo solo alla fine, quando il lavoro è svolto.

  • I / O filettato : per coloro che sono abbastanza audaci, combinare "I / O in anticipo" o "Delay I / O" con il calcolo effettivo spostando il caricamento in un thread parallelo, in modo che mentre si caricano più dati si possa lavorare su un calcolo sui dati che hai già, o mentre calcoli il prossimo lotto di dati puoi simultaneamente scrivere i risultati dell'ultimo batch.


Dal momento che molti dei problemi di prestazioni riguardano problemi di database, ti fornirò alcuni aspetti specifici da osservare durante la sintonizzazione di query e stored procedure.

Evita i cursori nella maggior parte dei database. Evita anche il looping. La maggior parte delle volte, l'accesso ai dati dovrebbe essere basato su set, non registrato mediante elaborazione dei record. Ciò include non riutilizzare una singola stored procedure di record quando si desidera inserire 1.000.000 di record contemporaneamente.

Non usare mai select *, restituisce solo i campi effettivamente necessari. Questo è particolarmente vero se ci sono join in quanto i campi di join verranno ripetuti e quindi causeranno un caricamento non necessario sul server e sulla rete.

Evitare l'uso di sottoquery correlate. Utilizzare i join (inclusi i join nelle tabelle derivate ove possibile) (so che questo è vero per Microsoft SQL Server, ma testare il consiglio quando si utilizza un backend diverso).

Indice, indice, indice. E ottieni le statistiche aggiornate se applicabili al tuo database.

Rendi la query sargable . Il significato evita cose che rendono impossibile l'uso degli indici come l'utilizzo di un carattere jolly nel primo carattere di una clausola simile o una funzione nel join o come parte sinistra di un'istruzione where.

Utilizzare i tipi di dati corretti. È più rapido eseguire la matematica della data su un campo data piuttosto che provare a convertire un tipo di dati stringa in un tipo di dati data, quindi eseguire il calcolo.

Non inserire mai un loop di alcun tipo in un trigger!

La maggior parte dei database ha un modo per verificare come verrà eseguita l'esecuzione della query. In Microsoft SQL Server questo è chiamato un piano di esecuzione. Controlla chi è il primo a vedere dove si trovano le aree problematiche.

Considera la frequenza con cui viene eseguita la query e quanto tempo occorre per eseguire quando si determina ciò che deve essere ottimizzato. A volte è possibile ottenere più prestazioni da una leggera modifica a una query che viene eseguita milioni di volte al giorno di quanto non sia possibile per cancellare il tempo da una query long_running che viene eseguita solo una volta al mese.

Utilizzare una sorta di strumento di profilatura per scoprire cosa viene realmente inviato al e dal database. Ricordo una volta, in passato, in cui non riuscivamo a capire perché la pagina fosse così lenta da caricare quando la stored procedure era veloce e abbiamo scoperto attraverso la profilatura che la pagina web chiedeva la query molte volte invece che una volta.

Il profiler ti aiuterà anche a scoprire chi sta bloccando chi. Alcune query che vengono eseguite rapidamente durante l'esecuzione da sole possono diventare molto lente a causa di blocchi da altre query.


Il singolo fattore limitante più importante oggi è la banda limitata di memoria . I multicores stanno peggiorando la situazione, poiché la larghezza di banda è condivisa tra i core. Inoltre, l'area chip limitata dedicata all'implementazione delle cache è divisa tra i core e i thread, aggravando ulteriormente questo problema. Infine, la segnalazione inter-chip necessaria per mantenere coerenti le diverse cache aumenta anche con un numero maggiore di core. Questo aggiunge anche una penalità.

Questi sono gli effetti che devi gestire. A volte attraverso la gestione del codice, ma a volte attraverso un'attenta valutazione e refactoring.

Molti commenti menzionano già codice cache friendly. Ci sono almeno due sapori distinti di questo:

  • Evita le latenze di recupero della memoria.
  • Minore pressione del bus di memoria (larghezza di banda).

Il primo problema riguarda in particolare la regolarità dei pattern di accesso ai dati, consentendo al prefetcher hardware di funzionare in modo efficiente. Evita l'allocazione dinamica della memoria che diffonde gli oggetti dati in memoria. Utilizzare contenitori lineari anziché elenchi, hash e alberi collegati.

Il secondo problema riguarda il miglioramento del riutilizzo dei dati. Modifica i tuoi algoritmi per lavorare su sottoinsiemi di dati che si adattano alla cache disponibile e riutilizzare i dati il ​​più possibile mentre è ancora nella cache.

Imballando i dati più strettamente e assicurandoti di utilizzare tutti i dati nelle linee della cache nei loop caldi, potrai evitare questi altri effetti e consentire l'inserimento di dati più utili nella cache.


OK, stai definendo il problema dove sembra che non ci sia molto margine di miglioramento. Questo è abbastanza raro, secondo la mia esperienza. Ho cercato di spiegarlo in un articolo del Dr. Dobbs nel novembre '93, partendo da un programma non banale convenzionalmente ben progettato senza sprechi ovvi e passando attraverso una serie di ottimizzazioni fino a che il suo tempo dell'orologio a muro fosse stato ridotto da 48 secondi a 1,1 secondi e la dimensione del codice sorgente è stata ridotta di un fattore 4. Il mio strumento diagnostico era questo . La sequenza dei cambiamenti era questa:

  • Il primo problema riscontrato è stato l'uso di cluster di elenchi (ora chiamati "iteratori" e "classi di contenitori") che rappresentano più della metà del tempo. Questi sono stati sostituiti con un codice abbastanza semplice, portando il tempo a 20 secondi.

  • Ora il più grande time-taker è più una costruzione di liste. Come percentuale, non era così grande prima, ma ora è perché il problema più grande è stato rimosso. Trovo un modo per velocizzarlo e il tempo scende a 17 sec.

  • Ora è più difficile trovare colpevoli evidenti, ma ce ne sono alcuni più piccoli su cui posso fare qualcosa, e il tempo scende a 13 sec.

Ora sembro aver colpito un muro. I campioni mi stanno dicendo esattamente cosa sta facendo, ma non riesco a trovare nulla che possa migliorare. Poi rifletto sulla struttura di base del programma, sulla sua struttura basata sulle transazioni, e mi chiedo se tutta la ricerca di elenchi che sta facendo sia effettivamente dettata dai requisiti del problema.

Poi ho scelto un re-design, in cui il codice del programma viene effettivamente generato (tramite macro del preprocessore) da un insieme più piccolo di sorgenti, e in cui il programma non è costantemente in grado di capire le cose che il programmatore sa essere abbastanza prevedibili. In altre parole, non "interpretare" la sequenza delle cose da fare, "compilarle".

  • Quella riprogettazione è fatta, riducendo il codice sorgente di un fattore 4 e il tempo è ridotto a 10 secondi.

Ora, dato che sta diventando così veloce, è difficile da campionare, quindi gli do 10 volte tanto lavoro da fare, ma i tempi seguenti sono basati sul carico di lavoro originale.

  • Una maggiore diagnosi rivela che sta trascorrendo del tempo nella gestione delle code. In-lining questi riduce il tempo a 7 secondi.

  • Ora un grande acquirente è la stampa diagnostica che stavo facendo. Lavala per 4 secondi.

  • Ora i più grandi vincitori di tempo sono le chiamate a malloc e gratuite . Ricicla oggetti: 2,6 secondi.

  • Continuando a campionare, trovo ancora operazioni che non sono strettamente necessarie - 1,1 secondi.

Total speedup factor: 43.6

Ora non ci sono due programmi uguali, ma nei software non giocattolo ho sempre visto una progressione come questa. Prima prendi le cose facili, e poi le più difficili, fino a che non arrivi ad un punto di rendimenti decrescenti. Quindi l'intuizione che ottieni potrebbe portare a una riprogettazione, dando inizio a un nuovo round di accelerazioni, fino a quando non riscuoti nuovamente rendimenti decrescenti. Ora questo è il punto in cui potrebbe avere senso chiedersi se ++i o i++ o for(;;) o while(1) siano più veloci: il tipo di domande che vedo così spesso su SO.

PS Ci si potrebbe chiedere perché non ho usato un profiler. La risposta è che quasi ognuno di questi "problemi" era un sito di chiamata di funzioni, che raggruppa i campioni. I profileristi, ancora oggi, stanno appena arrivando all'idea che le istruzioni e le istruzioni di chiamata siano più importanti da individuare e più facili da sistemare rispetto a intere funzioni. In realtà ho creato un profiler per fare questo, ma per una vera intimità intimidatoria con quello che sta facendo il codice, non c'è nulla che vi possa sostituire. Non è un problema che il numero di campioni sia piccolo, perché nessuno dei problemi riscontrati è così piccolo da essere facilmente trascurato.

AGGIUNTO: jerryjvl ha richiesto alcuni esempi. Ecco il primo problema. Consiste in un piccolo numero di righe separate di codice, che insieme richiedono più della metà del tempo:

 /* IF ALL TASKS DONE, SEND ITC_ACKOP, AND DELETE OP */
if (ptop->current_task >= ILST_LENGTH(ptop->tasklist){
. . .
/* FOR EACH OPERATION REQUEST */
for ( ptop = ILST_FIRST(oplist); ptop != NULL; ptop = ILST_NEXT(oplist, ptop)){
. . .
/* GET CURRENT TASK */
ptask = ILST_NTH(ptop->tasklist, ptop->current_task)

Questi stavano usando il list cluster ILST (simile a una classe list). Esse sono implementate nel modo usuale, con "clandestinità dell'informazione" che significa che gli utenti della classe non avrebbero dovuto preoccuparsi di come sono stati implementati. Quando sono state scritte queste righe (su circa 800 righe di codice) non si è pensato all'idea che questi potrebbero essere un "collo di bottiglia" (odio quella parola). Sono semplicemente il modo consigliato di fare le cose. È facile dire a posteriori che questi dovrebbero essere evitati, ma nella mia esperienza tutti i problemi di prestazioni sono così. In generale, è bene cercare di evitare di creare problemi di prestazioni. È ancora meglio trovare e aggiustare quelli che vengono creati, anche se "dovrebbero essere stati evitati" (a posteriori). Spero che dia un po 'di sapore.

Ecco il secondo problema, in due righe separate:

 /* ADD TASK TO TASK LIST */ 
ILST_APPEND(ptop->tasklist, ptask)
. . .
/* ADD TRANSACTION TO TRANSACTION QUEUE */
ILST_APPEND(trnque, ptrn)

Si tratta di compilare elenchi aggiungendo articoli ai loro fini. (La correzione consisteva nel raccogliere gli elementi negli array e creare gli elenchi tutti in una volta.) La cosa interessante è che queste dichiarazioni costano solo (cioè erano nello stack di chiamate) 3/48 dell'originale, quindi non erano in Infatti un grosso problema all'inizio . Tuttavia, dopo aver rimosso il primo problema, sono costati 3/20 del tempo e quindi erano ora un "pesce più grande". In generale, è così che va.

Potrei aggiungere che questo progetto è stato distillato da un progetto reale su cui ho contribuito. In quel progetto, i problemi di prestazioni erano molto più drammatici (come lo erano gli speedup), come chiamare una routine di accesso al database all'interno di un loop interno per vedere se un'operazione era stata completata.

RIFERIMENTI AGGIUNTI: Il codice sorgente, sia originale che ridisegnato, può essere trovato in www.ddj.com , per il 1993, nel file 9311.zip, file slug.asc e slug.zip.

EDIT 2011/11/26: Ora c'è un progetto sourceforge che contiene codice sorgente in Visual C ++ e una descrizione dettagliata di come è stata sintonizzata. Passa solo attraverso la prima metà dello scenario descritto sopra, e non segue esattamente la stessa sequenza, ma ottiene comunque un aumento di 2-3 ordini di magnitudine.


Probabilmente dovresti considerare la "prospettiva di Google", cioè determinare in che modo la tua applicazione può diventare in gran parte parallela e concorrente, il che inevitabilmente implicherà anche a un certo punto la distribuzione della tua applicazione su macchine e reti diverse, in modo che possa scalare idealmente in modo quasi lineare con l'hardware che ci passi sopra.

D'altra parte, le persone di Google sono anche conosciute per il lancio di molte risorse umane e risorse per risolvere alcuni dei problemi relativi a progetti, strumenti e infrastrutture che stanno utilizzando, come ad esempio l' ottimizzazione dell'intero programma per gcc con un team dedicato di ingegneri hacking gcc internals per prepararlo agli scenari dei casi d'uso tipici di Google.

Allo stesso modo, la profilatura di un'applicazione non significa più semplicemente profilare il codice del programma, ma anche tutti i suoi sistemi e infrastrutture (pensate a reti, switch, server, array RAID) per identificare ridondanze e potenziale di ottimizzazione dal punto di vista del sistema.


Quando non puoi più migliorare la performance, vedi se puoi migliorare invece la performance percepita .

Potrebbe non essere possibile rendere più veloce l'algoritmo fooCalc, ma spesso ci sono modi per rendere l'applicazione più reattiva all'utente.

Alcuni esempi:

  • anticipando ciò che l'utente sta per richiedere e iniziare a lavorare su quello prima di allora
  • visualizzare i risultati mentre arrivano, anziché tutti alla volta alla fine
  • Indicatore di avanzamento accurato

Questi non renderanno il tuo programma più veloce, ma potrebbero rendere i tuoi utenti più felici con la velocità che hai.


Trascorro la maggior parte della mia vita proprio in questo posto. Le linee generali sono per eseguire il tuo profiler e farlo registrare:

  • Cache manca . La cache di dati è la fonte n. 1 di bancarelle nella maggior parte dei programmi. Migliorare il tasso di hit della cache riorganizzando le strutture di dati offensive per avere una localizzazione migliore; impacchettare le strutture e i tipi numerici per eliminare i byte sprecati (e quindi gli sprechi di cache sprecati); prelavare i dati laddove possibile per ridurre le bancarelle.
  • Load-hit-stores . Le ipotesi del compilatore sull'alias del puntatore e i casi in cui i dati vengono spostati tra insiemi di registri disconnessi tramite la memoria, possono causare un determinato comportamento patologico che causa l'annullamento dell'intera pipeline della CPU su un op di carico. Trova i luoghi in cui flottanti, vettori e intarsi vengono lanciati l'uno contro l'altro ed eliminali. Usa __restrict liberamente per promettere al compilatore l'aliasing.
  • Operazioni con microcodice . La maggior parte dei processori ha alcune operazioni che non possono essere pipeline, ma eseguono invece una piccola subroutine memorizzata nella ROM. Gli esempi sul PowerPC sono interi moltiplicano, dividono e spostano per variabile. Il problema è che l'intera pipeline si blocca mentre questa operazione è in esecuzione. Cerca di eliminare l'uso di queste operazioni o almeno di suddividerle nelle loro operazioni di pipeline costituenti in modo da poter ottenere il vantaggio della spedizione superscalare su qualunque cosa stia facendo il resto del tuo programma.
  • Branch imprevedibili . Anche questi svuotano la conduttura. Trova i casi in cui la CPU impiega molto tempo a ricaricare la pipa dopo una diramazione e utilizza l'accenno alla diramazione, se disponibile, per farlo prevedere più spesso. O meglio ancora, sostituisci i rami con le mosse condizionali laddove possibile, specialmente dopo le operazioni in virgola mobile perché la loro pipa è solitamente più profonda e legge i flag di condizione dopo che fcmp può causare uno stallo.
  • Operazioni in virgola mobile sequenziali . Crea questi SIMD.

E un'altra cosa che mi piace fare:

  • Imposta il tuo compilatore per visualizzare gli elenchi di assiemi e guarda cosa emette per le funzioni di hotspot nel tuo codice. Tutte quelle intelligenti ottimizzazioni che "un buon compilatore dovrebbe essere in grado di fare automaticamente per te"? È probabile che il tuo attuale compilatore non li faccia. Ho visto GCC emettere un vero codice WTF.

suggerimenti:

  • Pre-calcolare piuttosto che ricalcolare : eventuali loop o chiamate ripetute che contengono calcoli con un intervallo di input relativamente limitato, prendere in considerazione la possibilità di effettuare una ricerca (array o dizionario) che contenga il risultato di tale calcolo per tutti i valori nell'intervallo valido di ingressi. Quindi utilizzare una semplice ricerca all'interno dell'algoritmo.
    Lati negativi : se alcuni dei valori pre-calcolati sono effettivamente usati questo potrebbe peggiorare le cose, anche la ricerca potrebbe richiedere una memoria significativa.
  • Non usare i metodi di libreria : la maggior parte delle librerie deve essere scritta per funzionare correttamente in una vasta gamma di scenari ed eseguire controlli nulli sui parametri, ecc. Riorganizzando un metodo potresti essere in grado di eliminare molte logiche che non si applica nella circostanza esatta in cui lo stai usando.
    Down-sides : scrivere un codice aggiuntivo significa più superficie per i bug.
  • Usa i metodi della biblioteca : per contraddirmi, le librerie linguistiche vengono scritte da persone molto più intelligenti di te o te; le probabilità sono che lo facessero meglio e più velocemente. Non implementare te stesso a meno che tu non sia in grado di farlo più velocemente (es .: misura sempre!)
  • Cheat : in alcuni casi, sebbene esista un calcolo esatto per il tuo problema, potresti non aver bisogno di "esatto", a volte un'approssimazione potrebbe essere "abbastanza buona" e molto più veloce nell'affare. Chiediti, è davvero importante se la risposta è fuori dell'1%? 5%? addirittura il 10%?
    Lati negativi: beh ... la risposta non sarà esatta.

Caching! Un modo economico (in termini di programmatori) per rendere quasi tutto più veloce è aggiungere un livello di astrazione di memorizzazione nella cache a qualsiasi area di spostamento dei dati del programma. Sia esso I / O o semplicemente il passaggio / creazione di oggetti o strutture. Spesso è facile aggiungere cache alle classi di fabbrica e ai lettori / scrittori.

A volte la cache non ti guadagna molto, ma è un metodo semplice per aggiungere tutto il caching e poi disabilitarlo dove non aiuta. Ho spesso trovato questo per ottenere prestazioni enormi senza dover analizzare il codice in micro-analisi.


Dividere e conquistare

Se il set di dati in elaborazione è troppo grande, esegui il loop su blocchi di esso. Se hai fatto bene il tuo codice, l'implementazione dovrebbe essere facile. Se hai un programma monolitico, ora lo sai meglio.


Ridurre le dimensioni variabili (nei sistemi embedded)

Se la dimensione della variabile è maggiore della dimensione della parola su un'architettura specifica, può avere un effetto significativo sia sulla dimensione del codice che sulla velocità. Ad esempio, se si dispone di un sistema a 16 bit e si utilizza una long intvariabile molto spesso e in seguito si rende conto che non può mai uscire dall'intervallo (-32.768 ... 32.767), prendere in considerazione la possibilità di ridurlo ashort int.

Dalla mia esperienza personale, se un programma è pronto o quasi pronto, ma ci rendiamo conto che occupa circa il 110% o il 120% della memoria del programma dell'hardware di destinazione, una rapida normalizzazione delle variabili di solito risolve il problema il più delle volte.

A questo punto, l'ottimizzazione degli algoritmi o parti del codice stesso può diventare frustrantemente inutile:

  • riorganizzare l'intera struttura e il programma non funziona più come previsto, o almeno si introducono molti bug.
  • fai qualche trucco intelligente: di solito passi un sacco di tempo a ottimizzare qualcosa, e scopri una diminuzione nulle o molto piccola delle dimensioni del codice, poiché il compilatore l'avrebbe comunque ottimizzata.

Molte persone commettono l'errore di avere variabili che memorizzano esattamente il valore numerico di un'unità che usano la variabile per: ad esempio, la loro variabile timememorizza il numero esatto di millisecondi, anche se sono rilevanti solo i passi temporali di 50 ms. Forse se la tua variabile rappresentasse 50 ms per ogni incremento di uno, potresti essere in grado di adattarsi a una variabile più piccola o uguale alla dimensione della parola. Su un sistema a 8 bit, ad esempio, anche una semplice aggiunta di due variabili a 32 bit genera una discreta quantità di codice, specialmente se si hanno registri bassi, mentre le aggiunte a 8 bit sono sia piccole che veloci.


Ho passato un po 'di tempo a lavorare sull'ottimizzazione dei sistemi aziendali client / server che operano su reti a bassa larghezza di banda ea latenza lunga (ad esempio satellite, remoto, offshore), e sono stato in grado di ottenere notevoli miglioramenti delle prestazioni con un processo abbastanza ripetibile.

  • Misura : inizia comprendendo la capacità e la topologia sottostanti della rete. Parlare con le persone di networking rilevanti nel business e utilizzare strumenti di base come ping e traceroute per stabilire (come minimo) la latenza di rete da ogni posizione del cliente, durante i periodi operativi tipici. Successivamente, eseguire misurazioni precise del tempo di funzioni specifiche dell'utente finale che mostrano i sintomi problematici. Registra tutte queste misurazioni, insieme alle loro posizioni, date e orari. Considera la possibilità di creare funzionalità di "test delle prestazioni di rete" per l'utente finale nell'applicazione client, consentendo ai tuoi utenti esperti di partecipare al processo di miglioramento; responsabilizzarli in questo modo può avere un enorme impatto psicologico quando si ha a che fare con utenti frustrati da un sistema poco performante.

  • Analisi : utilizzo di tutti i metodi di registrazione disponibili per stabilire esattamente quali dati vengono trasmessi e ricevuti durante l'esecuzione delle operazioni interessate. Idealmente, l'applicazione può acquisire i dati trasmessi e ricevuti sia dal client che dal server. Se questi includono anche i timestamp, ancora meglio. Se non è disponibile una registrazione sufficiente (ad esempio, un sistema chiuso o l'impossibilità di distribuire le modifiche in un ambiente di produzione), utilizzare uno sniffer di rete e assicurarsi di capire veramente cosa sta succedendo a livello di rete.

  • Cache : cerca i casi in cui i dati modificati statici o infrequentemente vengono trasmessi ripetutamente e prendi in considerazione una strategia di caching appropriata. Esempi tipici includono valori di "lista di prelievo" o altre "entità di riferimento", che possono essere sorprendentemente grandi in alcune applicazioni aziendali. In molti casi, gli utenti possono accettare di dover riavviare o aggiornare l'applicazione per aggiornare i dati aggiornati di rado, soprattutto se può ridurre il tempo significativo dalla visualizzazione degli elementi dell'interfaccia utente comunemente utilizzati. Assicurati di comprendere il reale comportamento degli elementi di caching già implementati - molti metodi di memorizzazione nella cache comuni (ad es. ETag HTTP) richiedono ancora un round-trip di rete per garantire coerenza e laddove la latenza della rete è costosa, potresti essere in grado di evitarlo del tutto con un diverso approccio di caching.

  • Parallelamente : cerca transazioni sequenziali che non devono essere emesse logicamente in modo sequenziale e rielaborano il sistema per emetterle in parallelo. Ho trattato un caso in cui una richiesta end-to-end aveva un ritardo di rete inerente di ~ 2s, che non era un problema per una singola transazione, ma quando erano necessari 6 round sequenziali sequenziali di 2 secondi prima che l'utente riprendesse il controllo dell'applicazione client , divenne un'enorme fonte di frustrazione. Scoprire che queste transazioni erano indipendenti, infatti, consentiva di eseguirle in parallelo, riducendo il ritardo dell'utente finale molto vicino al costo di un singolo round trip.

  • Combina : se le richieste sequenziali devono essere eseguite in sequenza, cerca le opportunità per combinarle in un'unica richiesta più completa. Esempi tipici includono la creazione di nuove entità, seguita dalla richiesta di correlare tali entità ad altre entità esistenti.

  • Comprimi : cerca opportunità per sfruttare la compressione del carico utile, sostituendo un modulo testuale con uno binario o utilizzando la tecnologia di compressione effettiva. Molti stack tecnologici moderni (ovvero entro un decennio) supportano questo aspetto in modo quasi trasparente, quindi assicurati che sia configurato. Sono stato spesso sorpreso dal notevole impatto della compressione in cui sembrava chiaro che il problema fosse fondamentalmente latente piuttosto che larghezza di banda, scoprendo dopo il fatto che consentiva alla transazione di adattarsi all'interno di un singolo pacchetto o altrimenti evitare perdite di pacchetti e quindi avere un outsize impatto sulle prestazioni.

  • Ripeti : torna all'inizio e rimisura le tue operazioni (nelle stesse posizioni e orari) con i miglioramenti in atto, registra e segnala i risultati. Come con tutte le ottimizzazioni, alcuni problemi potrebbero essere stati risolti esponendo altri che ora dominano.

Nei passaggi precedenti, mi concentro sul processo di ottimizzazione relativo all'applicazione, ma ovviamente è necessario assicurarsi che la rete sottostante sia configurata nel modo più efficiente per supportare anche l'applicazione. Coinvolgi gli specialisti di reti nel business e determina se sono in grado di applicare miglioramenti di capacità, QoS, compressione di rete o altre tecniche per risolvere il problema. Solitamente, non capiranno le esigenze della tua applicazione, quindi è importante che tu sia equipaggiato (dopo il passo Analizza) per discuterne con loro, e anche per fare il business case per qualsiasi costo tu stia chiedendo loro di sostenere . Ho riscontrato casi in cui la configurazione errata della rete ha causato la trasmissione dei dati delle applicazioni su un collegamento via satellite lento piuttosto che su un collegamento via terra,semplicemente perché stava usando una porta TCP che non era "ben nota" dagli specialisti del networking; ovviamente rettificare un problema come questo può avere un impatto drammatico sulle prestazioni, senza che sia necessario alcun codice software o modifiche alla configurazione.


La via di Google è un'opzione "Cache it .. Ogni volta che è possibile non toccare il disco"


Non così profondi o complessi come le risposte precedenti, ma qui va: (questi sono più principianti / livello intermedio)

  • ovvio: asciutto
  • eseguire cicli all'indietro in modo da confrontare sempre 0 anziché una variabile
  • usa operatori bit a bit ogni volta che puoi
  • interrompere il codice ripetitivo in moduli / funzioni
  • oggetti cache
  • le variabili locali hanno un leggero vantaggio in termini di prestazioni
  • limitare la manipolazione delle stringhe il più possibile

passa per riferimento anziché per valore


A volte cambiare il layout dei tuoi dati può aiutarti. In C, è possibile passare da una matrice o strutture a una struttura di array o viceversa.


Impossibile da dire Dipende da come appare il codice. Se possiamo supporre che il codice esista già, allora possiamo semplicemente guardarlo e capire da questo, come ottimizzarlo.

Migliore localizzazione della cache, loop di srotolamento, Prova ad eliminare le catene di dipendenza lunghe, per ottenere un parallelismo migliore a livello di istruzione. Preferisci le mosse condizionate sui rami quando possibile. Sfruttare le istruzioni SIMD quando possibile.

Scopri cosa sta facendo il tuo codice e capisci l'hardware su cui è in esecuzione. Quindi diventa abbastanza semplice determinare cosa devi fare per migliorare le prestazioni del tuo codice. Questo è davvero l'unico consiglio veramente generale che riesco a pensare.

Bene, questo e "Mostra il codice su SO e chiedi consigli sull'ottimizzazione per quel pezzo di codice specifico".


Molto difficile dare una risposta generica a questa domanda. Dipende davvero dal dominio del problema e dall'implementazione tecnica. Una tecnica generale che è abbastanza neutrale rispetto alla lingua: identifica gli hotspot del codice che non possono essere eliminati e ottimizza il codice assembler a mano.


  • Quando arrivi al punto che stai usando algoritmi efficienti, è una questione di cosa hai bisogno di più velocità o memoria . Utilizzare la memorizzazione nella cache per "pagare" in memoria per una maggiore velocità o utilizzare i calcoli per ridurre il footprint di memoria.
  • Se possibile (e più economico) gettare l'hardware sul problema - CPU più veloce, più memoria o HD potrebbero risolvere il problema più velocemente cercando di codificarlo.
  • Utilizzare la parallelizzazione, se possibile, eseguire parte del codice su più thread.
  • Usa lo strumento giusto per il lavoro . alcuni linguaggi di programmazione creano codice più efficiente, utilizzando il codice gestito (cioè Java / .NET) accelerano lo sviluppo ma i linguaggi di programmazione nativi creano un codice in esecuzione più veloce.
  • Micro ottimizzare . Solo erano applicabili è possibile utilizzare l'assembly ottimizzato per accelerare piccoli pezzi di codice, utilizzando le ottimizzazioni SSE / vettoriali nelle posizioni giuste può aumentare notevolmente le prestazioni.

  • Routine inline (eliminare call / return e parametri push)
  • Prova a eliminare test / switch con le ricerche di tabelle (se sono più veloci)
  • Srotolare i loop (dispositivo di Duff) fino al punto in cui si inseriscono nella cache della CPU
  • Localizza l'accesso alla memoria per non far saltare la cache
  • Localizza i calcoli correlati se l'ottimizzatore non lo sta già facendo
  • Elimina gli invarianti di loop se l'ottimizzatore non lo sta già facendo




language-agnostic