Strategie di ottimizzazione delle prestazioni di ultima istanza [chiuso]


609

Ci sono già molte domande sulle prestazioni in questo sito, ma mi viene in mente che quasi tutte sono molto specifiche del problema e abbastanza strette. E quasi tutti ripetono i consigli 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 offensive sono state isolate
  • verranno inoltre misurati tutti i tentativi di ottimizzazione per garantire che non peggiorino le cose

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

Idealmente, prova a rendere agnostico il linguaggio delle risposte e, ove applicabile, indica eventuali svantaggi delle strategie suggerite.

Aggiungerò una risposta con i miei suggerimenti iniziali e attendo con impazienza tutto ciò a cui la comunità Stack Overflow può pensare.

Risposte:


427

OK, stai definendo il problema in cui sembrerebbe che non ci sia molto margine di miglioramento. Questo è abbastanza raro, nella mia esperienza. Ho provato a spiegarlo in un articolo del Dr. Dobbs nel novembre 1993, partendo da un programma non banale convenzionalmente ben progettato senza sprechi evidenti e sottoponendolo a una serie di ottimizzazioni fino a ridurre il tempo del suo orologio da parete 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 è stata questa:

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

  • Ora il più grande acquirente è più lista-costruzione. In percentuale, non era così grande prima, ma ora è perché il problema più grande è stato rimosso. Trovo un modo per accelerarlo e il tempo scende a 17 secondi.

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

Ora mi sembra di aver colpito un muro. I campioni mi dicono esattamente cosa sta facendo, ma non riesco a trovare nulla che possa migliorare. Quindi rifletto sulla progettazione di base del programma, sulla sua struttura guidata dalle transazioni e chiedo se tutta la ricerca dell'elenco che sta facendo sia effettivamente richiesta dai requisiti del problema.

Poi mi sono imbattuto in una riprogettazione, in cui il codice del programma è effettivamente generato (tramite macro preprocessore) da un set più piccolo di sorgente e in cui il programma non è costantemente alla ricerca di cose che il programmatore sa che sono abbastanza prevedibili. In altre parole, non "interpretare" la sequenza delle cose da fare, "compilarla".

  • La riprogettazione viene eseguita, riducendo il codice sorgente di un fattore 4 e il tempo viene ridotto a 10 secondi.

Ora, poiché sta diventando così veloce, è difficile campionarlo, quindi gli do 10 volte più lavoro da fare, ma i seguenti tempi si basano sul carico di lavoro originale.

  • Ulteriori diagnosi rivelano che sta impiegando tempo nella gestione delle code. Il rivestimento interno riduce il tempo a 7 secondi.

  • Ora un grande impegno è la stampa diagnostica che stavo facendo. Lavare quello - 4 secondi.

  • Ora i più grandi che richiedono tempo sono le chiamate a malloc e gratuite . Ricicli gli oggetti - 2,6 secondi.

  • Continuando a campionare, trovo ancora operazioni non strettamente necessarie: 1,1 secondi.

Fattore di accelerazione totale: 43,6

Ora non ci sono due programmi uguali, ma nei software non giocattolo ho sempre visto una progressione come questa. Per prima cosa ottieni le cose facili, e poi le più difficili, fino a quando non arrivi a un calo dei rendimenti. Quindi l'intuizione che ottieni potrebbe portare a una riprogettazione, iniziando un nuovo round di accelerazioni, fino a quando non raggiungi nuovamente rendimenti decrescenti. Ora, questo è il punto in cui potrebbe dare un senso a chiedersi se ++io i++o for(;;)o while(1)sono più veloci: il tipo di domande che vedo così spesso su Stack Overflow.

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 funzione, che impilava i punti di riferimento. I profilatori, anche oggi, stanno appena arrivando all'idea che le dichiarazioni e le istruzioni di chiamata sono più importanti da individuare e più facili da correggere rispetto a intere funzioni.

In realtà ho creato un profiler per farlo, ma per una vera intimità con ciò che il codice sta facendo, non c'è sostituto per avere le dita giuste. Non è un problema il fatto che il numero di campioni sia piccolo, perché nessuno dei problemi rilevati è così piccolo da poter essere facilmente perso.

AGGIUNTO: jerryjvl ha richiesto alcuni esempi. Ecco il primo problema. Consiste in un numero limitato di righe di codice separate, 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 utilizzavano il cluster di elenchi ILST (simile a una classe di elenchi). Sono implementati nel solito modo, con "occultamento delle informazioni" che significa che gli utenti della classe non dovevano preoccuparsi di come sono stati implementati. Quando sono state scritte queste righe (su circa 800 righe di codice) non è stata data l'idea che queste potrebbero essere un "collo di bottiglia" (odio quella parola). Sono semplicemente il modo raccomandato per fare le cose. Con il senno di poi è facile dire che questi avrebbero dovuto essere evitati, ma nella mia esperienza tutti i problemi di prestazione sono così. In generale, è bene cercare di evitare di creare problemi di prestazioni. È anche meglio trovare e correggere quelli che sono stati creati, anche se "avrebbero dovuto essere evitati" (col senno di poi).

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 creare elenchi aggiungendo elementi alle loro estremità. (La correzione consisteva nel raccogliere gli elementi negli array e creare gli elenchi tutti in una volta.) La cosa interessante è che queste affermazioni costano solo (cioè erano nello stack di chiamate) 3/48 del tempo originale, quindi non erano in infatti un grosso problema all'inizio . Tuttavia, dopo aver rimosso il primo problema, costano 3/20 delle volte e quindi ora erano un "pesce più grande". In generale, è così che va.

Potrei aggiungere che questo progetto è stato distillato da un vero progetto su cui ho collaborato. In quel progetto, i problemi di prestazioni erano molto più drammatici (come lo erano le accelerazioni), come chiamare una routine di accesso al database all'interno di un ciclo interno per vedere se un'attività era terminata.

RIFERIMENTO AGGIUNTO: Il codice sorgente, sia originale che riprogettato, è disponibile in www.ddj.com , per il 1993, nel file 9311.zip, nei file slug.asc e slug.zip.

EDIT 2011/11/26: ora esiste un progetto SourceForge che contiene il codice sorgente in Visual C ++ e una descrizione dettagliata di come è stato messo a punto. Attraversa solo la prima metà dello scenario sopra descritto e non segue esattamente la stessa sequenza, ma ottiene comunque un accelerazione di grandezza di 2-3 ordini di grandezza.


3
Mi piacerebbe leggere alcuni dei dettagli dei passaggi descritti in precedenza. È possibile includere alcuni frammenti delle ottimizzazioni per il sapore? (senza rendere il post troppo lungo?)
jerryjvl,

8
... Ho anche scritto un libro che è ora fuori stampa, quindi sta andando a un prezzo ridicolo su Amazon - "Costruire applicazioni migliori" ISBN 0442017405. Essenzialmente lo stesso materiale è nel primo capitolo.
Mike Dunlavey,

3
@ Mike Dunlavey, suggerirei di dire a Google che l'hai già scansionato. Probabilmente hanno già un accordo con chiunque abbia acquistato il tuo editore.
Thorbjørn Ravn Andersen,

19
@ Thorbjørn: solo per seguire, mi sono collegato a GoogleBooks, ho compilato tutti i moduli e inviato loro una copia cartacea. Ho ricevuto una e-mail che mi chiedeva se possedevo davvero il copyright. L'editore Van Nostrand Reinhold, che è stato acquistato da International Thompson, che è stato acquistato da Reuters, e quando provo a chiamarli o inviarli via e-mail è come un buco nero. Quindi è in un limbo - non ho ancora avuto l'energia per inseguirlo davvero.
Mike Dunlavey,


188

suggerimenti:

  • Pre-calcolo anziché ricalcolo : tutti i loop o le chiamate ripetute che contengono calcoli con un intervallo relativamente limitato di input, prendere in considerazione la possibilità di effettuare una ricerca (array o dizionario) che contenga il risultato di quel 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 vengono effettivamente utilizzati, ciò potrebbe peggiorare le cose, anche la ricerca potrebbe richiedere una memoria significativa.
  • Non utilizzare i metodi delle librerie: la maggior parte delle librerie deve essere scritta per funzionare correttamente in una vasta gamma di scenari ed eseguire controlli nulli sui parametri, ecc. Ri-implementando un metodo potresti essere in grado di eliminare molta logica che non si applica nella circostanza esatta in cui lo si utilizza.
    Lati negativi : la scrittura di un codice aggiuntivo significa una maggiore superficie per i bug.
  • Usa metodi di biblioteca : per contraddire me stesso, le biblioteche di lingua vengono scritte da persone molto più intelligenti di te o di me; è probabile che lo abbiano fatto meglio e più velocemente. Non implementarlo tu stesso a meno che tu non possa effettivamente renderlo più veloce (es .: misura sempre!)
  • Cheat : in alcuni casi, sebbene possa esistere un calcolo esatto per il tuo problema, potresti non aver bisogno di "esatto", a volte un'approssimazione può essere "abbastanza buona" e molto più veloce nell'affare. Chiediti, importa davvero se la risposta è dell'1%? 5%? anche il 10%?
    Lati negativi: beh ... la risposta non sarà esatta.

32
La precomputazione non sempre aiuta e talvolta può persino danneggiare: se la tabella di ricerca è troppo grande, può compromettere le prestazioni della cache.
Adam Rosenfield,

37
Barare spesso può essere la vittoria. Ho avuto un processo di correzione del colore che al centro era un 3-vettore punteggiato da una matrice 3x3. La CPU aveva una matrice moltiplicata nell'hardware che tralasciava alcuni dei termini incrociati e andava molto velocemente rispetto a tutti gli altri modi per farlo, ma supportava solo matrici 4x4 e 4 vettori di float. La modifica del codice da portare nello slot vuoto extra e la conversione del calcolo in virgola mobile da un punto fisso hanno consentito un risultato leggermente meno accurato ma molto più veloce.
RBerteig,

6
L'inganno è stato l'utilizzo di un moltiplicatore di matrice che ha tralasciato alcuni dei prodotti interni, rendendo possibile l'implementazione in microcodice per una singola istruzione CPU che è stata completata più velocemente di quanto non fosse possibile nemmeno la sequenza equivalente di singole istruzioni. È un imbroglio perché non ottiene la risposta "corretta", solo una risposta che è "abbastanza corretta".
RBerteig,

6
@RBerteig: solo "abbastanza corretto" è un'opportunità di ottimizzazione che la maggior parte delle persone manca nella mia esperienza.
Martin Thompson,

5
Non puoi sempre presumere che tutti siano più intelligenti di te. Alla fine siamo tutti professionisti. Puoi presumere, tuttavia, che esiste una libreria specifica che usi e ha raggiunto il tuo ambiente a causa della sua qualità, quindi la scrittura di questa libreria deve essere molto accurata, non puoi farlo anche solo perché non sei specializzato in quello campo, e non investire lo stesso tipo di tempo in esso. Non perché sei meno intelligente. Dai.
v

164

Quando non è più possibile migliorare le prestazioni, vedere invece se è possibile migliorare le prestazioni percepite .

Potresti non essere in grado di rendere più veloce il tuo algoritmo fooCalc, ma spesso ci sono modi per rendere la tua applicazione più reattiva per l'utente.

Alcuni esempi:

  • anticipando ciò che l'utente sta per richiedere e iniziare a lavorarci prima di allora
  • mostrando i risultati man mano che arrivano, anziché tutti contemporaneamente alla fine
  • Indicatore di avanzamento accurato

Ciò non renderà il tuo programma più veloce, ma potrebbe rendere i tuoi utenti più felici della velocità che hai.


27
Una barra di avanzamento che accelera alla fine può essere percepita come più veloce di una assolutamente accurata. In "Rethinking the Progress Bar" (2007) Harrison, Amento, Kuznetsov e Bell testano diversi tipi di barre su un gruppo di utenti e discutono alcuni modi per riorganizzare le operazioni in modo che i progressi possano essere percepiti come più veloci.
Emil Vikström,

9
naxa, la maggior parte delle barre di avanzamento è falsa perché prevedere più passaggi molto diversi di un flusso in una singola percentuale è difficile o talvolta impossibile. Guarda tutti quei bar che rimangono bloccati al 99% :-(
Emil Vikström,

138

Trascorro gran parte della mia vita in questo posto. Le grandi linee sono di eseguire il tuo profiler e farlo registrare:

  • Manca la cache . La cache di dati è la prima fonte di bancarelle nella maggior parte dei programmi. Migliorare la percentuale di accessi alla cache riorganizzando le strutture di dati offensive per avere una località migliore; comprimere strutture e tipi numerici verso il basso per eliminare i byte sprecati (e quindi i recuperi di cache sprecati); precaricare i dati ove possibile per ridurre le bancarelle.
  • Load-hit-store . Le ipotesi del compilatore sull'aliasing del puntatore e i casi in cui i dati vengono spostati tra i set di registri disconnessi tramite la memoria, possono causare un certo comportamento patologico che provoca la cancellazione dell'intera pipeline della CPU in un'operazione di caricamento. Trova i luoghi in cui float, vettori e ints vengono lanciati l'uno verso l'altro ed eliminali. Usa __restrictliberamente per promettere al compilatore di aliasing.
  • Operazioni microcodificate . La maggior parte dei processori ha alcune operazioni che non possono essere pipeline, ma che invece eseguono una piccola subroutine memorizzata nella ROM. Esempi su PowerPC sono i numeri interi moltiplicati, divisi e spostati per quantità variabile. Il problema è che l'intera pipeline si ferma mentre questa operazione è in esecuzione. Cerca di eliminare l'uso di queste operazioni o almeno di scomporle nelle loro operazioni pipeline costituenti in modo da poter ottenere il vantaggio dell'invio superscalare su qualunque cosa stia facendo il resto del tuo programma.
  • Errori di filiale . Anche questi svuotano la pipeline. Trova casi in cui la CPU impiega molto tempo a riempire la pipa dopo un ramo e usa i suggerimenti sul ramo, se disponibili, per farlo prevedere più spesso correttamente. O meglio, sostituisci i rami con mosse condizionate ove possibile, specialmente dopo operazioni in virgola mobile perché la loro pipe è generalmente più profonda e la lettura dei flag di condizione dopo fcmp può causare uno stallo.
  • Operazioni sequenziali in virgola mobile . Crea questi SIMD.

E un'altra cosa che mi piace fare:

  • Imposta il tuo compilatore per generare elenchi di assiemi e guarda cosa emette per le funzioni 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 vero compilatore non li faccia. Ho visto GCC emettere veramente codice WTF.

8
Uso principalmente Intel VTune e PIX. Non ho idea se possono adattarsi a C #, ma in realtà una volta ottenuto quel livello di astrazione JIT la maggior parte di queste ottimizzazioni sono fuori dalla tua portata, tranne per migliorare la localizzazione della cache e forse evitare alcuni rami.
Crashworks,

6
Anche così, il controllo dell'output post-JIT può aiutare a capire se ci sono costrutti che non si ottimizzano bene durante la fase JIT ... le indagini non possono mai ferire, anche se risulta un vicolo cieco.
jerryjvl,

5
Penso che molte persone, incluso me stesso, sarebbero interessate a questo "assemblaggio di wtf" prodotto da gcc. Il tuo sembra un lavoro molto interessante :)
BlueRaja - Danny Pflughoeft

1
Examples on the PowerPC ...<- Cioè, alcune implementazioni di PowerPC. PowerPC è un ISA, non una CPU.
Billy ONeal,

1
@BillyONeal Anche su hardware x86 moderno, imul può bloccare la pipeline; vedere "Manuale di riferimento per l'ottimizzazione delle architetture Intel® 64 e IA-32" §13.3.2.3: "L'istruzione di moltiplicazione di numeri interi richiede diversi cicli per essere eseguita. Vengono pipeline in modo tale che un'istruzione di moltiplicazione di numeri interi e un'altra istruzione di lunga latenza possano fare progressi nel fase di esecuzione. Tuttavia, le istruzioni di moltiplicazione di numeri interi bloccheranno l'emissione di altre istruzioni di numeri interi a ciclo singolo a causa del requisito dell'ordine del programma. " Ecco perché di solito è meglio usare dimensioni di array allineate a parole e lea.
Crashworks

78

Lancia più hardware!


30
più hardware non è sempre un'opzione quando si dispone di software che dovrebbe essere eseguito su hardware già presente sul campo.
Doug T.

76
Non è una risposta molto utile a qualcuno che produce software di consumo: il cliente non vorrà sentirti dire "acquista un computer più veloce". Soprattutto se stai scrivendo software per indirizzare qualcosa come una console per videogiochi.
Crashworks,

19
@Crashworks, o per quello, un sistema incorporato. Quando l'ultima funzionalità è finalmente disponibile e il primo lotto di schede è già stato fatto girare non è il momento di scoprire che dovresti aver usato una CPU più veloce in primo luogo ...
RBerteig,

71
Una volta ho dovuto eseguire il debug di un programma che presentava un'enorme perdita di memoria: la sua dimensione della VM è aumentata di circa 1 Mb all'ora. Un collega ha scherzato sul fatto che tutto ciò che dovevo fare era aggiungere memoria a un ritmo costante . :)
j_random_hacker il

9
Più hardware: ah sì, la linfa vitale dello sviluppatore mediocre. Non so quante volte ho sentito "aggiungere un'altra macchina e raddoppiare la capacità!"
Olof Forshell,

58

Più suggerimenti:

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

  • Sposta gli I / O in anticipo : carica tutti i dati necessari per un calcolo in anticipo, in modo da non avere ripetute attese di I / O all'interno del nucleo di un algoritmo critico (e forse di conseguenza ripetute ricerca del disco, quando si caricano tutti i dati in un colpo può evitare la ricerca).

  • Ritarda I / O : non scrivere i risultati fino a quando il calcolo non è terminato, archiviarli in una struttura di dati e quindi scaricarli in una volta sola alla fine quando il duro lavoro è fatto.

  • I / O threaded : per chi è abbastanza audace, combina "I / O up-front" o "Delay I / O" con il calcolo effettivo spostando il caricamento in un thread parallelo, in modo che mentre carichi più dati puoi lavorare su un calcolo sui dati che hai già, o mentre calcoli il successivo batch di dati puoi contemporaneamente scrivere i risultati dell'ultimo batch.


3
Si noti che "spostare l'IO in un thread parallelo" deve essere eseguito come IO asincrono su molte piattaforme (ad esempio Windows NT).
Billy ONeal,

2
L'I / O è davvero un punto critico, perché è lento e ha latenze enormi, e puoi ottenere più velocemente con questo consiglio, ma è ancora fondamentalmente imperfetto: i punti sono la latenza (che deve essere nascosta) e il syscall overhead ( che deve essere ridotto riducendo il numero di chiamate I / O). Il miglior consiglio è: utilizzare mmap()per l'input, effettuare madvise()chiamate appropriate e utilizzare aio_write()per scrivere grossi blocchi di output (= alcuni MiB).
cmaster - ripristina monica il

1
Quest'ultima opzione è abbastanza facile da implementare in Java, in particolare. Ha dato un ENORME aumento delle prestazioni per le applicazioni che ho scritto. Un altro punto importante (più che spostare I / O in anticipo) è renderlo I / O SEQUENZIALE e di grandi blocchi. Molte letture piccole sono molto più costose di una grande, a causa del tempo di ricerca del disco.
BobMcGee,

A un certo punto ho imbrogliato per evitare l'I / O, spostando temporaneamente tutti i file su un disco RAM prima del calcolo e spostandoli successivamente. Questo è sporco, ma potrebbe essere utile in situazioni in cui non si controlla la logica che effettua le chiamate I / O.
MD

48

Poiché molti dei problemi di prestazioni riguardano problemi di database, ti fornirò alcuni aspetti specifici da tenere in considerazione durante l'ottimizzazione delle query e delle procedure memorizzate.

Evita i cursori nella maggior parte dei database. Evita anche il loop. Il più delle volte, l'accesso ai dati dovrebbe essere basato su set, non registrare mediante elaborazione dei record. Ciò include il non riutilizzo di una procedura memorizzata per singolo record quando si desidera inserire 1.000.000 di record contemporaneamente.

Non usare mai seleziona *, restituisce solo i campi di cui hai effettivamente bisogno. Ciò è particolarmente vero se ci sono dei join poiché i campi di join verranno ripetuti e quindi causeranno un carico non necessario sia sul server che sulla rete.

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

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

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

Utilizzare tipi di dati corretti. È più veloce eseguire calcoli sulla data in un campo data piuttosto che provare a convertire un tipo di dati stringa in un tipo di dati data, quindi eseguire il calcolo.

Non mettere mai un loop di alcun tipo in un grilletto!

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

Considera la frequenza con cui viene eseguita la query e il tempo necessario per determinare 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 rispetto a quando si cancella il tempo libero da una query long_running che viene eseguita solo una volta al mese.

Utilizzare una sorta di strumento di profiler per scoprire cosa viene realmente inviato da e verso il database. Ricordo una volta in passato in cui non siamo riusciti a capire perché la pagina fosse così lenta da caricare quando la procedura memorizzata era veloce e ho scoperto attraverso la profilazione che la pagina web richiedeva la query molte volte anziché una volta.

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


29

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

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

Molti commenti citano già un codice compatibile con la cache. Ci sono almeno due distinti gusti di questo:

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

Il primo problema riguarda in particolare la regolarità dei modelli di accesso ai dati, consentendo al prefetcher hardware di funzionare in modo efficiente. Evita l'allocazione dinamica della memoria che diffonde gli oggetti dati nella 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 riutilizzali il più possibile mentre è ancora nella cache.

L'imballaggio più stretto dei dati e la garanzia di utilizzare tutti i dati nelle righe della cache nei hot loop, aiuteranno a evitare questi altri effetti e consentiranno di inserire dati più utili nella cache.


25
  • Su quale hardware stai eseguendo? Puoi utilizzare le ottimizzazioni specifiche della piattaforma (come la vettorializzazione)?
  • Puoi ottenere un compilatore migliore? Ad esempio passare da GCC a Intel?
  • Riesci a far funzionare il tuo algoritmo in parallelo?
  • È possibile ridurre i problemi di cache riorganizzando i dati?
  • Puoi disabilitare le affermazioni?
  • Micro-ottimizzazione per il compilatore e la piattaforma. Nello stile di "in un if / else, inserisci prima la frase più comune"

4
Dovrebbe essere "passare da GCC a LLVM" :)
Zifre,

4
Riesci a far funzionare il tuo algoritmo in parallelo? - vale anche l'inverso
appena il

4
È vero che ridurre la quantità di thread può essere ugualmente una buona ottimizzazione
Johan Kotlinski il

ri: micro-ottimizzazione: se si controlla l'output asm del compilatore, è spesso possibile modificare l'origine per tenerlo a mano per produrre un asm migliore. Vedi Perché questo codice C ++ è più veloce del mio assembly scritto a mano per testare la congettura di Collatz? per ulteriori informazioni su come aiutare o battere il compilatore sul moderno x86.
Peter Cordes,

17

Anche se mi piace la risposta di Mike Dunlavey, in realtà è un'ottima risposta con un esempio di supporto, penso che potrebbe essere espressa in modo molto semplice così:

Scopri prima cosa richiede più tempo e capisci perché.

È il processo di identificazione dei maiali del tempo che ti aiuta a capire dove devi affinare il tuo algoritmo. Questa è l'unica risposta agnostica del linguaggio onnicomprensivo che riesco a trovare a un problema che dovrebbe già essere completamente ottimizzato. Supponendo anche che tu voglia essere indipendente dall'architettura nella tua ricerca della velocità.

Quindi, mentre l'algoritmo può essere ottimizzato, la sua implementazione potrebbe non esserlo. L'identificazione ti consente di sapere quale parte è quale: algoritmo o implementazione. Quindi, qualunque sia il tempo che spendi di più, è il tuo candidato principale per la revisione. Ma dal momento che dici di voler spremere l'ultimo% in meno, potresti voler esaminare anche le parti minori, le parti che non hai esaminato da vicino all'inizio.

Infine, un po 'di tentativi ed errori con i dati sulle prestazioni in diversi modi per implementare la stessa soluzione, o algoritmi potenzialmente diversi, possono portare approfondimenti che aiutano a identificare perditempo e risparmiatori di tempo.

HPH, asoudmove.


16

Probabilmente dovresti considerare la "prospettiva di Google", cioè determinare come la tua applicazione può diventare in gran parte parallelizzata e concorrente, il che inevitabilmente significherà anche a un certo punto esaminare la distribuzione della tua applicazione su macchine e reti diverse, in modo che possa adattarsi idealmente quasi linearmente con l'hardware che ci si lancia.

D'altra parte, la gente di Google è anche nota per aver investito un sacco di risorse umane e risorse nel risolvere alcuni dei problemi in progetti, strumenti e infrastrutture che sta usando, come ad esempio l' ottimizzazione dell'intero programma per gcc con un team dedicato di ingegneri hacking gcc internals al fine di prepararlo per scenari di casi d'uso tipici di Google.

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


15
  • Routine in linea (elimina la chiamata / ritorno e il push dei parametri)
  • Prova a eliminare i test / switch con le ricerche delle tabelle (se sono più veloci)
  • Svolgere 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

2
Il dispositivo IIRC Duff è molto raramente più veloce. Solo quando l'operazione è molto breve (come una singola piccola espressione matematica)
BCS,

12
  • Quando si arriva al punto in cui si utilizzano algoritmi efficienti, si tratta di ciò che è necessario maggiore velocità o memoria . Utilizzare la memorizzazione nella cache per "pagare" in memoria per una maggiore velocità o utilizzare i calcoli per ridurre il footprint della memoria.
  • Se possibile (e più conveniente) gettare l'hardware al problema : CPU più veloce, più memoria o HD potrebbero risolvere il problema più velocemente quindi provare a codificarlo.
  • Usa la parallelizzazione se possibile - esegui parte del codice su più thread.
  • Usa lo strumento giusto per il lavoro . alcuni linguaggi di programmazione creano codice più efficiente, usando il codice gestito (cioè Java / .NET) accelerano lo sviluppo, ma i linguaggi di programmazione nativi creano un codice più veloce.
  • Ottimizza micro . Solo se fosse possibile, è possibile utilizzare l'assemblaggio ottimizzato per accelerare piccoli pezzi di codice, utilizzando le ottimizzazioni SSE / vettoriale nei punti giusti può aumentare notevolmente le prestazioni.

12

Dividere e conquistare

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


9
+1 per il suono "smack" flyswatter che ho sentito durante la lettura dell'ultima frase.
Bryan Boettcher,

11

Prima di tutto, come menzionato in diverse risposte precedenti, scopri cosa morde le tue prestazioni: memoria o processore o rete o database o qualcos'altro. A seconda di ciò ...

  • ... se è memoria - trova uno dei libri scritti molto tempo fa da Knuth, uno della serie "The Art of Computer Programming". Molto probabilmente si tratta di smistamento e ricerca: se la mia memoria è sbagliata, dovrai scoprire in che cosa parla di come gestire l'archiviazione dei dati su nastro lenta. Trasforma mentalmente la sua coppia memoria / nastro nella tua coppia di cache / memoria principale (o nella coppia di cache L1 / L2) rispettivamente. Studia tutti i trucchi che descrive: se non trovi qualcosa che risolve il tuo problema, assumi un informatico professionista per condurre una ricerca professionale. Se il tuo problema di memoria è per caso con FFT (la cache manca negli indici bit-reverse durante l'esecuzione di farfalle radix-2), allora non assumere uno scienziato, invece ottimizza manualmente i passaggi uno a uno fino a quando ' o vincere o arrivare a un vicolo cieco. Hai nominatospremere fino all'ultimo percento giusto? Se sono davvero poche , probabilmente vincerai.

  • ... se è un processore: passa al linguaggio assembly. Studia le specifiche del processore: cosa richiede tick , VLIW, SIMD. Le chiamate di funzione sono molto probabilmente mangiatori di zecca sostituibili. Scopri le trasformazioni del loop: pipeline, svolgete. Le moltiplicazioni e le divisioni potrebbero essere sostituibili / interpolate con spostamenti di bit (le moltiplicazioni per numeri interi piccoli potrebbero essere sostituibili con aggiunte). Prova i trucchi con dati più brevi: se sei fortunato, un'istruzione con 64 bit potrebbe risultare sostituibile con due su 32 o anche 4 su 16 o 8 su 8 bit. Prova anche più a lungodati - ad es. i calcoli del float potrebbero risultare più lenti di quelli doppi in un determinato processore. Se hai oggetti trigonometrici, combatti con tabelle pre-calcolate; tieni inoltre presente che un seno di piccolo valore potrebbe essere sostituito con quel valore se la perdita di precisione rientra nei limiti consentiti.

  • ... se si tratta di rete, pensa a comprimere i dati che gli passi sopra. Sostituisci trasferimento XML con binario. Protocolli di studio Prova UDP anziché TCP se riesci in qualche modo a gestire la perdita di dati.

  • ... se è un database, beh, vai a qualsiasi forum di database e chiedi consiglio. Griglia dati in memoria, ottimizzazione del piano di query, ecc. Ecc.

HTH :)


9

Caching! Un modo economico (nello sforzo del programmatore) per rendere quasi tutto più veloce è quello di aggiungere un livello di astrazione della cache a qualsiasi area di spostamento dei dati del programma. Che si tratti di I / O o semplicemente di 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 semplicemente la cache dappertutto e quindi disabilitarlo dove non aiuta. Ho spesso trovato questo per ottenere prestazioni enormi senza dover analizzare il codice.


8

Penso che sia già stato detto in modo diverso. Ma quando hai a che fare con un algoritmo ad alta intensità di processore, dovresti semplificare tutto all'interno del ciclo più interno a spese di tutto il resto.

Per alcuni potrebbe sembrare ovvio, ma è qualcosa su cui cerco di concentrarmi indipendentemente dalla lingua con cui sto lavorando. Se hai a che fare con loop nidificati, ad esempio, e trovi l'opportunità di abbassare un po 'di codice, puoi in alcuni casi velocizzare drasticamente il tuo codice. Come altro esempio, ci sono piccole cose a cui pensare come lavorare con numeri interi invece di variabili in virgola mobile ogni volta che puoi e usare la moltiplicazione invece di divisione ogni volta che puoi. Ancora una volta, queste sono cose che dovrebbero essere considerate per il tuo ciclo più interno.

A volte potresti trovare il vantaggio di eseguire le tue operazioni matematiche su un numero intero all'interno del loop interno e quindi ridimensionarlo in una variabile a virgola mobile con cui puoi lavorare in seguito. Questo è un esempio di sacrificare la velocità in una sezione per migliorare la velocità in un'altra, ma in alcuni casi può valere la pena pagare.


8

Ho trascorso un po 'di tempo lavorando sull'ottimizzazione dei sistemi di business client / server che operano su reti a bassa larghezza di banda e lunga latenza (ad es. Satellite, remoto, offshore) e sono stato in grado di ottenere alcuni notevoli miglioramenti delle prestazioni con un processo abbastanza ripetibile.

  • Misura : iniziare a comprendere la capacità e la topologia di base della rete. Parlare con le persone di rete pertinenti nel settore e utilizzare strumenti di base come ping e traceroute per stabilire (come minimo) la latenza della rete da ogni posizione del client, durante i periodi operativi tipici. Quindi, eseguire misurazioni del tempo accurate di specifiche funzioni dell'utente finale che visualizzano i sintomi problematici. Registra tutte queste misurazioni, insieme a posizioni, date e orari. Considerare la possibilità di integrare funzionalità di "test delle prestazioni di rete" degli utenti finali nell'applicazione client, consentendo agli utenti esperti di partecipare al processo di miglioramento; potenziarli in questo modo può avere un enorme impatto psicologico quando si ha a che fare con utenti frustrati da un sistema poco performante.

  • Analizza : utilizzando 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 dati trasmessi e ricevuti sia dal client che dal server. Se questi includono anche i timestamp, anche meglio. Se non è disponibile una registrazione sufficiente (ad es. Sistema chiuso o incapacità di distribuire modifiche in un ambiente di produzione), utilizzare uno sniffer di rete e assicurarsi di capire davvero cosa sta succedendo a livello di rete.

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

  • Parallelizzare : cerca le transazioni sequenziali che non devono necessariamente essere emesse in modo strettamente sequenziale e rielabora il sistema per emetterle in parallelo. Mi sono occupato di un caso in cui una richiesta end-to-end presentava un ritardo di rete intrinseco di ~ 2 secondi, il che non costituiva un problema per una singola transazione, ma quando erano necessari 6 round trip 2 sequenziali prima che l'utente riprendesse il controllo dell'applicazione client , divenne un'enorme fonte di frustrazione. Scoprire che queste transazioni erano in effetti indipendenti ha permesso di eseguirle in parallelo, riducendo il ritardo dell'utente finale a un costo molto vicino al costo di un singolo round trip.

  • Combina : dove 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à, seguite da richieste per mettere in relazione tali entità con altre entità esistenti.

  • Comprimi : cerca opportunità per sfruttare la compressione del payload, sostituendo un modulo testuale con uno binario o utilizzando la tecnologia di compressione effettiva. Molti stack tecnologici moderni (ovvero entro un decennio) lo supportano in modo quasi trasparente, quindi assicurati che sia configurato. Sono stato spesso sorpreso dal significativo impatto della compressione in cui sembrava evidente che il problema fosse fondamentalmente la latenza piuttosto che la larghezza di banda, scoprendo dopo che consentiva alla transazione di rientrare in un singolo pacchetto o altrimenti evitare la perdita di pacchetti e quindi avere una dimensione impatto sulle prestazioni.

  • Ripeti : torna all'inizio e ridimensiona le tue operazioni (nelle stesse posizioni e orari) con i miglioramenti in atto, registra e segnala i risultati. Come per 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 stessa rete sottostante sia configurata nel modo più efficiente per supportare anche l'applicazione. Coinvolgi gli specialisti della rete nel business e determina se sono in grado di applicare miglioramenti della capacità, QoS, compressione della rete o altre tecniche per affrontare il problema. Di solito, non capiranno le esigenze della tua applicazione, quindi è importante che tu sia in grado (dopo il passaggio Analizza) di discuterne con loro, e anche di sostenere il business case per qualsiasi costo che stai chiedendo loro di sostenere. . Ho riscontrato casi in cui una configurazione di rete errata ha causato la trasmissione dei dati delle applicazioni su un collegamento satellitare lento anziché su un collegamento via terra, semplicemente perché utilizzava una porta TCP non "ben nota" dagli specialisti della rete; ovviamente, correggere un problema come questo può avere un impatto drammatico sulle prestazioni, senza che sia necessario modificare il codice del software o la configurazione.


7

Molto difficile dare una risposta generica a questa domanda. Dipende molto dal dominio del problema e dall'implementazione tecnica. Una tecnica generale che è abbastanza neutra dal punto di vista linguistico: identificare gli hotspot di codice che non possono essere eliminati e ottimizzare manualmente il codice assembler.


7

Gli ultimi pochi% dipendono molto dalla CPU e dall'applicazione ....

  • le architetture della cache differiscono, alcuni chip hanno RAM su chip che puoi mappare direttamente, ARM (a volte) hanno un'unità vettoriale, SH4 è un utile opcode a matrice. C'è una GPU - forse uno shader è la strada da percorrere. I TMS320 sono molto sensibili ai rami all'interno dei loop (quindi separare i loop e spostare le condizioni all'esterno, se possibile).

L'elenco potrebbe continuare .... Ma questo genere di cose è davvero l'ultima risorsa ...

Compilare per x86 ed eseguire Valgrind / Cachegrind sul codice per una corretta profilazione delle prestazioni. O CCStudio di Texas Instruments ha un dolce profiler. Allora saprai davvero dove concentrarti ...


7

Did you know that a CAT6 cable is capable of 10x better shielding off extrenal inteferences than a default Cat5e UTP cable?

Per qualsiasi progetto non offline, pur avendo il miglior software e il miglior hardware, se il tuo throughput è debole, quella linea sottile comprimerà i dati e ti farà ritardare, anche se in millisecondi ... ma se stai parlando degli ultimi drop , sono alcune gocce ottenute, 24 ore su 24, 7 giorni su 7 per qualsiasi pacchetto inviato o ricevuto.


7

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

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

4
Informazioni sul looping all'indietro: sì, il confronto per la fine del loop sarà più veloce. In genere, tuttavia, si utilizza la variabile per indicizzarla in memoria e accedervi in ​​modo inverso potrebbe essere controproducente a causa di frequenti errori nella cache (nessun prefetch).
Andreas Reiff,

1
AFAIK, nella maggior parte dei casi, qualsiasi ottimizzatore ragionevole farà bene con i loop, senza che il programmatore debba eseguire esplicitamente al contrario. O l'ottimizzatore invertirà il ciclo stesso o ha un altro modo che è ugualmente buono. Ho notato l'output ASM identico per i loop (certamente relativamente semplici) scritti sia in ordine crescente che massimo e decrescente rispetto a 0. Certo, i miei giorni Z80 mi hanno preso l'abitudine di scrivere riflessivamente i cicli all'indietro, ma sospetto che menzionarli per i neofiti sia di solito un aringhe rosse / ottimizzazione prematura, quando il codice leggibile e l'apprendimento di pratiche più importanti dovrebbero essere le priorità.
underscore_d

Al contrario, l'esecuzione di un ciclo all'indietro sarà più lenta nei linguaggi di livello inferiore perché in una guerra tra confronto a zero più sottrazione aggiuntiva rispetto a un confronto a singolo numero intero, il confronto a singolo numero intero è più veloce. Invece di diminuire, è possibile avere un puntatore all'indirizzo iniziale in memoria e un puntatore all'indirizzo finale in memoria. Quindi, incrementa il puntatore iniziale fino a quando non è uguale al puntatore finale. Ciò eliminerà l'operazione di offset della memoria aggiuntiva nel codice assembly, dimostrando così molto più performante.
Jack Giffin,

5

Impossibile dirlo. Dipende dall'aspetto del codice. Se possiamo presumere che il codice esista già, allora possiamo semplicemente guardarlo e capire da quello, come ottimizzarlo.

Migliore localizzazione della cache, srotolamento del ciclo, Prova ad eliminare lunghe catene di dipendenze, per ottenere un miglior parallelismo a livello di istruzioni. Preferisci i movimenti condizionali sui rami quando possibile. Sfruttare le istruzioni SIMD quando possibile.

Comprendi cosa sta facendo il tuo codice e comprendi l'hardware su cui è in esecuzione. Quindi diventa abbastanza semplice determinare cosa è necessario fare per migliorare le prestazioni del codice. Questo è davvero l'unico consiglio veramente generale che mi viene in mente.

Bene, e "Mostra il codice su SO e chiedi consigli di ottimizzazione per quel particolare pezzo di codice".


5

Se l'hardware migliore è un'opzione, allora sicuramente. Altrimenti

  • Verifica di utilizzare le migliori opzioni di compilatore e linker.
  • Se la routine hotspot in una libreria diversa per frequentare il chiamante, prendere in considerazione lo spostamento o la clonazione nel modulo chiamanti. Elimina parte dell'overhead della chiamata e può migliorare gli hit della cache (vedi come AIX collega strcpy () staticamente in oggetti condivisi collegati separatamente). Ciò potrebbe ovviamente ridurre anche gli accessi alla cache, motivo per cui una misura.
  • Verifica se esiste la possibilità di utilizzare una versione specializzata della routine dell'hotspot. Il rovescio della medaglia è più di una versione da mantenere.
  • Guarda l'assemblatore. Se pensi che potrebbe essere migliore, considera perché il compilatore non l'ha capito e come potresti aiutare il compilatore.
  • Considera: stai davvero usando l'algoritmo migliore? È l'algoritmo migliore per le dimensioni dell'input?

Vorrei aggiungere al tuo primo par . : non dimenticare di disattivare tutte le informazioni di debug nelle opzioni del compilatore .
varnie,

5

Il modo google è un'opzione "Cache it .. Ogni volta che è possibile non toccare il disco"


5

Ecco alcune tecniche di ottimizzazione rapida e sporca che utilizzo. Considero questa un'ottimizzazione di primo passaggio.

Scopri dove trascorrere il tempo Scopri esattamente cosa sta impiegando il tempo. È il file IO? È tempo della CPU? È la rete? È il database? È inutile ottimizzare l'IO se questo non è il collo di bottiglia.

Conosci il tuo ambiente Sapere dove ottimizzare dipende in genere dall'ambiente di sviluppo. In VB6, ad esempio, il passaggio per riferimento è più lento del passaggio per valore, ma in C e C ++ il riferimento è molto più veloce. In C, è ragionevole provare qualcosa e fare qualcosa di diverso se un codice di ritorno indica un errore, mentre in Dot Net, la rilevazione delle eccezioni è molto più lenta rispetto al controllo di una condizione valida prima di tentare.

Indici Crea indici su campi di database sottoposti a query frequenti. Puoi quasi sempre scambiare spazio per la velocità.

Evita le ricerche All'interno del ciclo per essere ottimizzato, evito di fare qualsiasi ricerca. Trova l'offset e / o l'indice all'esterno del ciclo e riutilizzi i dati all'interno.

Riduci a icona IO, prova a progettare in modo da ridurre il numero di volte in cui devi leggere o scrivere, specialmente tramite una connessione di rete

Ridurre le astrazioni Più strati di astrazione deve attraversare il codice, più lento è. All'interno del circuito critico, ridurre le astrazioni (ad esempio rivelare metodi di livello inferiore che evitano il codice extra)

Fili Spawn per i progetti con un'interfaccia utente, generando un nuovo thread per preforme compiti più lenti rende l'applicazione sensazione più reattivo, anche se non lo è.

Pre-processo In genere è possibile scambiare spazio per la velocità. Se ci sono calcoli o altre operazioni intense, vedi se riesci a pre-calcolare alcune delle informazioni prima di entrare nel ciclo critico.


5

Se hai molti calcoli a virgola mobile altamente paralleli, specialmente con precisione singola, prova a scaricarlo su un processore grafico (se presente) usando OpenCL o (per i chip NVidia) CUDA. Le GPU hanno un'enorme potenza di calcolo in virgola mobile nei loro shader, che è molto maggiore di quella di una CPU.


5

Aggiungendo questa risposta poiché non l'ho vista inclusa in tutte le altre.

Riduci al minimo la conversione implicita tra tipi e segno:

Questo vale per C / C ++, almeno, anche se già pensate Sei libero di conversioni - a volte il suo bene di prova aggiungendo avvisi del compilatore in tutto funzioni che richiedono prestazioni, soprattutto guardare-out per le conversioni all'interno di cicli.

GCC spesific: puoi verificarlo aggiungendo alcuni pragmi dettagliati attorno al tuo codice,

#ifdef __GNUC__
#  pragma GCC diagnostic push
#  pragma GCC diagnostic error "-Wsign-conversion"
#  pragma GCC diagnostic error "-Wdouble-promotion"
#  pragma GCC diagnostic error "-Wsign-compare"
#  pragma GCC diagnostic error "-Wconversion"
#endif

/* your code */

#ifdef __GNUC__
#  pragma GCC diagnostic pop
#endif

Ho visto casi in cui è possibile ottenere una percentuale di accelerazione ridotta riducendo le conversioni generate da avvisi come questo.

In alcuni casi ho un'intestazione con avvertimenti rigorosi che tengo incluso per prevenire conversioni accidentali, tuttavia questo è un compromesso poiché potresti finire con l'aggiunta di molti cast a conversioni intenzionali silenziose che potrebbero solo rendere il codice più disordinato per il minimo guadagni.


Questo è il motivo per cui mi piace che in OCaml, il casting tra tipi numerici debba essere esplicito.
Gaius,

Il punto giusto di @Gaius - ma in molti casi cambiare lingua non è una scelta realistica. Poiché C / C ++ sono così ampiamente utilizzati, è utile per renderli più rigorosi, anche se specifici per il compilatore.
ideasman42

4

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


4

Modifica il sistema operativo e il framework.

Può sembrare eccessivo ma pensaci in questo modo: i sistemi operativi e i frame sono progettati per fare molte cose. La tua applicazione fa solo cose molto specifiche. Se riuscissi a far fare al sistema operativo esattamente ciò di cui la tua applicazione ha bisogno e far capire alla tua applicazione come funziona il framework (php, .net, java), potresti ottenere molto meglio dal tuo hardware.

Facebook, ad esempio, ha cambiato alcune cose a livello di kernel in Linux, ha cambiato il modo in cui memcached funziona (per esempio hanno scritto un proxy memcached e hanno usato udp invece di tcp ).

Un altro esempio di questo è Window2008. Win2K8 ha una versione in cui è possibile installare solo il sistema operativo di base necessario per eseguire le applicazioni X (ad es. Web-App, Server App). Ciò riduce gran parte del sovraccarico che il sistema operativo ha sui processi in esecuzione e offre prestazioni migliori.

Ovviamente, dovresti sempre aggiungere più hardware come primo passo ...


2
Sarebbe un approccio valido dopo il fallimento di tutti gli altri approcci, o se una specifica funzionalità del sistema operativo o del framework fosse responsabile della riduzione marcata delle prestazioni, ma il livello di competenza e controllo necessari per realizzarlo potrebbe non essere disponibile per ogni progetto.
Andrew Neely,
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.