L'ottimizzazione prematura è davvero la radice di tutto il male?

198

Un mio collega oggi ha impegnato una classe chiamata ThreadLocalFormat , che fondamentalmente spostava le istanze delle classi di Java Format in un thread locale, dal momento che non sono thread-safe e "relativamente costosi" da creare. Ho scritto un breve test e ho calcolato che avrei potuto creare 200.000 istanze al secondo, gli ho chiesto se ne stesse creando così tante, a cui ha risposto "da nessuna parte vicino a tanti". È un grande programmatore e tutti i componenti del team sono altamente qualificati, quindi non abbiamo alcun problema a capire il codice risultante, ma è stato chiaramente un caso di ottimizzazione in cui non vi è alcuna reale necessità. Ha appoggiato il codice su mia richiesta. Cosa pensi? È un caso di "ottimizzazione prematura" e quanto è grave?

    
posta Craig Day 29.12.2015 - 08:56
fonte

17 risposte

303

È importante tenere a mente la citazione completa:

We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%.

Ciò significa che, in assenza di problemi prestazionali misurati, non dovresti ottimizzare perché pensi otterrai un guadagno in termini di prestazioni. Ci sono ovvie ottimizzazioni (come non fare concatenazioni di stringhe all'interno di un loop stretto), ma tutto ciò che non è una ottimizzazione banalmente chiara dovrebbe essere evitato fino a quando non può essere misurato.

I maggiori problemi con "l'ottimizzazione prematura" sono che può introdurre bug inattesi e può essere un enorme perdita di tempo.

    
risposta data 11.12.2014 - 18:46
fonte
103

Le ottimizzazioni precoci micro sono la radice di tutti i mali, perché le micro ottimizzazioni non tengono conto del contesto. Non si comportano quasi mai come loro sono attesi.

Quali sono alcune buone ottimizzazioni anticipate nell'ordine di importanza:

  • Ottimizzazioni architettoniche (struttura dell'applicazione, il modo in cui è componente e stratificato)
  • Ottimizzazione del flusso di dati (all'interno e all'esterno dell'applicazione)

Alcune ottimizzazioni del ciclo di sviluppo intermedio:

  • Strutture dati, introducono nuove strutture dati che offrono prestazioni migliori o un sovraccarico se necessario
  • Algoritmi (ora è un buon momento per iniziare a decidere tra quicksort3 e heapsort ;-))

Alcune ottimizzazioni del ciclo di sviluppo finale

  • Ricerca di hotpots di codice (cicli stretti, che dovrebbero essere ottimizzati)
  • Ottimizzazione basata sul profiling delle parti computazionali del codice
  • Le micro ottimizzazioni possono essere fatte ora come sono fatte nel contesto dell'applicazione e il loro impatto può essere misurato correttamente.

Non tutte le ottimizzazioni iniziali sono malvagie, le micro ottimizzazioni sono malvagie se eseguite nel momento sbagliato nel ciclo di vita dello sviluppo , poiché possono influire negativamente sull'architettura, possono influire negativamente sulla produttività iniziale, possono essere prestazioni irrilevanti saggio o addirittura avere un effetto dannoso alla fine dello sviluppo a causa delle diverse condizioni ambientali.

Se le prestazioni sono preoccupanti (e dovrebbero sempre essere), pensa sempre grande . Le prestazioni sono un quadro più ampio e non riguardano cose come: dovrei usare int o long ?. Scegli Top Down quando lavori con le prestazioni invece di Bottom Up .

    
risposta data 06.10.2015 - 15:07
fonte
49

l'ottimizzazione senza prima misurazione è quasi sempre prematura.

Credo che sia vero in questo caso, e vero anche nel caso generale.

    
risposta data 17.10.2008 - 11:29
fonte
40

L'ottimizzazione è "cattiva" se causa:

  • codice meno chiaro
  • significativamente più codice
  • codice meno sicuro
  • tempo del programmatore sprecato

Nel tuo caso, sembra che sia trascorso un po 'di tempo da programmatore, il codice non era troppo complesso (una supposizione del tuo commento che tutti i membri del team sarebbero in grado di capire), e il codice è un po' più futuro prova (essendo thread-safe ora, se ho capito la tua descrizione). Sembra solo un piccolo male. :)

    
risposta data 17.10.2008 - 10:42
fonte
33

Sono sorpreso che questa domanda abbia 5 anni, eppure nessuno ha pubblicato più di quello che Knuth ha da dire di un paio di frasi. La coppia di paragrafi che circonda la famosa citazione lo spiega abbastanza bene. La carta che viene citata si chiama " Programmazione strutturata con go to Statements ", e mentre è quasi 40 anni, parla di una polemica e di un movimento di software che non esistono più e che ha esempi di linguaggi di programmazione di cui molte persone non hanno mai sentito parlare, una quantità sorprendentemente ampia di ciò che ha detto si applica ancora.

Ecco una citazione più ampia (da pagina 8 del pdf, pagina 268 nell'originale):

The improvement in speed from Example 2 to Example 2a is only about 12%, and many people would pronounce that insignificant. The conventional wisdom shared by many of today's software engineers calls for ignoring efficiency in the small; but I believe this is simply an overreaction to the abuses they see being practiced by penny-wise-and-pound-foolish programmers, who can't debug or maintain their "optimized" programs. In established engineering disciplines a 12% improvement, easily obtained, is never considered marginal; and I believe the same viewpoint should prevail in software engineering. Of course I wouldn't bother making such optimizations on a one-shot job, but when it's a question of preparing quality programs, I don't want to restrict myself to tools that deny me such efficiencies.

There is no doubt that the grail of efficiency leads to abuse. Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.

Yet we should not pass up our opportunities in that critical 3%. A good programmer will not be lulled into complacency by such reasoning, he will be wise to look carefully at the critical code; but only after that code has been identified. It is often a mistake to make a priori judgments about what parts of a program are really critical, since the universal experience of programmers who have been using measurement tools has been that their intuitive guesses fail.

Un altro bel po 'dalla pagina precedente:

My own programming style has of course changed during the last decade, according to the trends of the times (e.g., I'm not quite so tricky anymore, and I use fewer go to's), but the major change in my style has been due to this inner loop phenomenon. I now look with an extremely jaundiced eye at every operation in a critical inner loop, seeking to modify my program and data structure (as in the change from Example 1 to Example 2) so that some of the operations can be eliminated. The reasons for this approach are that: a) it doesn't take long, since the inner loop is short; b) the payoff is real; and c) I can then afford to be less efficient in the other parts of my programs, which therefore are more readable and more easily written and debugged.

    
risposta data 27.10.2013 - 04:20
fonte
16

Ho spesso visto questa citazione usata per giustificare ovviamente codice o codice errato che, sebbene le sue prestazioni non siano state misurate, potrebbe probabilmente essere reso più veloce abbastanza facilmente, senza aumentare le dimensioni del codice o comprometterne la leggibilità.

In generale, penso che le prime micro-ottimizzazioni potrebbero essere una cattiva idea. Tuttavia, le macro-ottimizzazioni (cose come la scelta di un algoritmo O (log N) invece di O (N ^ 2)) sono spesso utili e dovrebbero essere fatte in anticipo, dal momento che potrebbe essere uno spreco scrivere un algoritmo O (N ^ 2) e quindi buttalo via completamente a favore di un approccio O (log N).

Nota le parole potrebbe essere : se l'algoritmo O (N ^ 2) è semplice e facile da scrivere, puoi buttarlo via più tardi senza troppi sensi di colpa se risulta troppo lento. Ma se entrambi gli algoritmi sono similmente complessi, o se il carico di lavoro previsto è così grande che sai già che avrai bisogno di quello più veloce, l'ottimizzazione anticipata è una decisione ingegneristica che ridurrà il tuo carico di lavoro totale a lungo termine.

Quindi, in generale, penso che l'approccio giusto sia scoprire quali sono le opzioni prima di iniziare a scrivere il codice e scegliere consapevolmente il miglior algoritmo per la tua situazione. Soprattutto, la frase "l'ottimizzazione prematura è la radice di tutti i mali" non è una scusa per l'ignoranza. Gli sviluppatori di carriera dovrebbero avere un'idea generale di quanto costano le operazioni comuni; dovrebbero sapere, per esempio,

  • le stringhe costano più dei numeri
  • le lingue dinamiche sono molto più lente delle lingue tipizzate staticamente
  • i vantaggi degli elenchi array / vettoriali sugli elenchi concatenati e viceversa
  • quando utilizzare un hashtable, quando usare una mappa ordinata e quando usare un heap
  • che (se funzionano con dispositivi mobili) "double" e "int" hanno prestazioni simili sui desktop (FP può anche essere più veloce) ma "double" può essere cento volte più lento su dispositivi mobili di fascia bassa senza FPU;
  • il trasferimento di dati su Internet è più lento dell'accesso all'HDD, gli HDD sono molto più lenti della RAM, la RAM è molto più lenta della cache e dei registri L1 e le operazioni Internet possono bloccarsi indefinitamente (e fallire in qualsiasi momento).

E gli sviluppatori dovrebbero avere familiarità con una cassetta degli attrezzi di strutture dati e algoritmi in modo che possano facilmente utilizzare gli strumenti giusti per il lavoro.

Avere un sacco di conoscenze e una cassetta degli attrezzi personale consente di ottimizzare quasi senza sforzo. Mettendo molto impegno in un'ottimizzazione che potrebbe non essere necessaria è male (e ammetto di cadere in quella trappola più di una volta). Ma quando l'ottimizzazione è facile come scegliere un set / hashtable anziché un array, o memorizzare un elenco di numeri in doppio [] invece di string [], perché no? Potrei essere in disaccordo con Knuth qui, non ne sono sicuro, ma penso che stesse parlando di ottimizzazione a basso livello mentre sto parlando di ottimizzazione ad alto livello.

Ricorda che la citazione è originaria del 1974. Nel 1974 i computer erano lenti e la potenza di calcolo era costosa, il che dava ad alcuni sviluppatori la tendenza a sovraottimizzare, linea per linea. Penso che sia quello contro cui Knuth stava spingendo. Non stava dicendo "non preoccuparti affatto delle prestazioni", perché nel 1974 sarebbe stato solo un pazzo parlare. Knuth stava spiegando come ottimizzare; in breve, uno dovrebbe concentrarsi solo sui colli di bottiglia, e prima di farlo è necessario eseguire misurazioni per trovare i colli di bottiglia.

Tieni presente che non riesci a trovare i colli di bottiglia fino a quando non hai scritto un programma su misura, il che significa che alcune decisioni relative alle prestazioni devono essere eseguite prima che esista qualcosa da misurare. A volte queste decisioni sono difficili da cambiare se si sbagliano. Per questo motivo, è utile avere un'idea generale delle cose che costano in modo da poter prendere decisioni ragionevoli quando non sono disponibili dati rigidi.

Quanto presto ottimizzare, e quanto preoccuparsi delle prestazioni dipende dal lavoro. Quando si scrivono script che verranno eseguiti solo poche volte, preoccuparsi delle prestazioni è in genere una completa perdita di tempo. Ma se lavori per Microsoft o Oracle e stai lavorando su una libreria che migliaia di altri sviluppatori utilizzeranno in migliaia di modi diversi, potrebbe essere utile per ottimizzare l'inferno, quindi che puoi coprire tutti i diversi casi d'uso in modo efficiente. Anche così, la necessità di prestazioni deve essere sempre bilanciata con la necessità di leggibilità, manutenibilità, eleganza, estensibilità e così via.

    
risposta data 01.05.2012 - 23:58
fonte
13

Personalmente, come spiegato in un thread precedente , non lo faccio "Credo che l'ottimizzazione anticipata non sia buona in situazioni in cui si sa che si verificheranno problemi di prestazioni. Ad esempio, scrivo software di modellazione e analisi delle superfici, dove gestisco regolarmente decine di milioni di entità. La pianificazione per prestazioni ottimali in fase di progettazione è di gran lunga superiore all'ottimizzazione tardiva di un design debole.

Un'altra cosa da considerare è come la tua applicazione verrà scalata in futuro. Se consideri che il tuo codice avrà una lunga vita, anche l'ottimizzazione delle prestazioni in fase di progettazione è una buona idea.

Nella mia esperienza, l'ottimizzazione tardiva offre magri premi ad un prezzo elevato. Ottimizzare in fase di progettazione, attraverso la selezione dell'algoritmo e il tweaking, è molto meglio. A seconda di un profiler per capire come funziona il tuo codice non è un ottimo modo per ottenere codice ad alte prestazioni, dovresti saperlo in anticipo.

    
risposta data 23.05.2017 - 14:40
fonte
9

In effetti ho imparato che la non ottimizzazione prematura è più spesso la radice di tutto il male.

Quando la gente scrive software, inizialmente avrà problemi, come instabilità, funzionalità limitate, cattiva usabilità e cattive prestazioni. Tutti questi di solito vengono corretti, quando il software matura.

Tutti questi, eccetto le prestazioni. Nessuno sembra preoccuparsi delle prestazioni. Il motivo è semplice: se un software si blocca, qualcuno risolverà il bug e il gioco è fatto, se una funzionalità è mancante, qualcuno lo implementerà e lo farà, se il software ha prestazioni scadenti in molti casi non è dovuto a microottimizzazione mancante, ma a causa del cattivo design e nessuno toccherà il design del software. MAI.

Guarda Bochs. È lento come l'inferno. Sarà mai più veloce? Forse, ma solo nel range di qualche percento. Non otterrà mai prestazioni paragonabili a software di virtualizzazione come VMWare o VBox o anche QEMU. Perché è lento in base alla progettazione!

Se il problema di un software è che è lento, allora perché è MOLTO lento e questo può essere risolto solo migliorando le prestazioni di una moltitudine. + 10% semplicemente non renderà veloce un software lento. E di solito non ottieni più del 10% di ottimizzazioni successive.

Quindi, se le prestazioni sono NULLA per il tuo software, dovresti tenerne conto fin dall'inizio, quando lo stai progettando, invece di pensare "oh sì, è lento, ma possiamo migliorarlo più tardi". Perché non puoi!

So che non si adatta al caso specifico, ma risponde alla domanda generale "L'ottimizzazione prematura è davvero la radice di tutti i mali?" - con un chiaro NO.

Ogni ottimizzazione, come qualsiasi funzione, ecc. deve essere progettata attentamente e implementata con cura. E ciò include una corretta valutazione dei costi e dei benefici. Non ottimizzare un algoritmo per risparmiare alcuni cicli qua e là, quando non crea un guadagno di prestazioni misurabile.

Proprio come un esempio: puoi migliorare le prestazioni di una funzione integrandola, possibilmente salvando una manciata di cicli, ma allo stesso tempo probabilmente aumenterai le dimensioni del tuo eseguibile, aumentando le possibilità di TLB e di mancate cache che costano migliaia di cicli o anche operazioni di paging, che uccideranno completamente le prestazioni. Se non capisci queste cose, la tua "ottimizzazione" può risultare negativa.

L'ottimizzazione stupida è più dannosa dell'ottimizzazione "prematura", tuttavia entrambi sono ancora migliori della prematura non ottimizzazione.

    
risposta data 11.12.2014 - 19:08
fonte
6

Ci sono due problemi con il PO: in primo luogo, il tempo di sviluppo viene utilizzato per il lavoro non essenziale, che potrebbe essere usato per scrivere più funzionalità o correggere più bug, e in secondo luogo, il falso senso di sicurezza che il codice sta funzionando in modo efficiente. L'OP spesso implica l'ottimizzazione del codice che non sarà il collo a bottiglia, mentre non si accorge del codice che lo farà. Il bit "prematuro" significa che l'ottimizzazione viene eseguita prima che un problema venga identificato mediante misurazioni appropriate.

Quindi in sostanza, sì, questo sembra un'ottimizzazione prematura, ma non lo escluderei necessariamente a meno che non introduca bug - dopo tutto, è stato ottimizzato ora (!)

    
risposta data 17.10.2008 - 10:39
fonte
3

Credo che sia ciò che Mike Cohn chiama "placcare d'oro" il codice - cioè passare del tempo su cose che potrebbero essere belle ma non necessarie.

Ha consigliato di non farlo.

P.S. 'Gold-plating' potrebbe essere il tipo di funzionalità di campane e fischietti in base alle specifiche. Quando si guarda il codice, esso prende forma di ottimizzazione non necessaria, classi 'a prova di futuro' ecc.

    
risposta data 17.10.2008 - 10:42
fonte
3

Poiché non c'è alcun problema nel comprendere il codice, allora questo caso potrebbe essere considerato un'eccezione.

Ma in generale l'ottimizzazione porta a un codice meno leggibile e meno comprensibile e dovrebbe essere applicata solo quando necessario. Un semplice esempio: se sai di dover ordinare solo un paio di elementi, usa BubbleSort. Ma se sospetti che gli elementi possano aumentare e non sai quanto, allora l'ottimizzazione con QuickSort (ad esempio) non è malvagia, ma un must. E questo dovrebbe essere considerato durante la progettazione del programma.

    
risposta data 17.10.2008 - 10:40
fonte
3

Ho scoperto che il problema con l'ottimizzazione prematura si verifica soprattutto quando si riscrive il codice esistente per essere più veloce. Posso vedere come potrebbe essere un problema scrivere in primo luogo un'ottimizzazione complicata, ma soprattutto vedo un'ottimizzazione prematura che impenna la sua brutta testa nel correggere ciò che non è (noto essere) rotto.

E il peggiore esempio di questo è ogni volta che vedo qualcuno che re-implementa funzionalità da una libreria standard. Questa è una grande bandiera rossa. Ad esempio, una volta ho visto qualcuno implementare routine personalizzate per la manipolazione delle stringhe perché era preoccupato che i comandi integrati fossero troppo lenti.

Questo risulta in un codice che è più difficile da capire (cattivo) e che brucia molto tempo sul lavoro che probabilmente non è utile (cattivo).

    
risposta data 29.05.2011 - 14:54
fonte
3

Da una prospettiva diversa, è la mia esperienza che la maggior parte dei programmatori / sviluppatori non pianifica il successo e il "prototipo" è quasi sempre la Release 1.0. Ho esperienza di prima mano con 4 prodotti originali separati in cui il front-end di classe, sexy e altamente funzionale (fondamentalmente l'interfaccia utente) ha portato all'adozione e all'entusiasmo da parte degli utenti. In ciascuno di questi prodotti, i problemi di prestazioni hanno iniziato a insinuarsi in tempi relativamente brevi (da 1 a 2 anni), in particolare quando i clienti più grandi e più esigenti hanno iniziato ad adottare il prodotto. Ben presto le prestazioni hanno dominato la lista delle problematiche, sebbene lo sviluppo di nuove funzionalità abbia dominato l'elenco delle priorità del management. I clienti sono diventati sempre più frustrati poiché ogni versione ha aggiunto nuove funzionalità che sembravano grandiose ma erano quasi inaccessibili a causa di problemi di prestazioni.

Quindi, i difetti di progettazione e di implementazione molto fondamentali che erano di poca o nessuna preoccupazione nel "prototipo" divennero importanti ostacoli al successo a lungo termine dei prodotti (e delle aziende).

La demo del tuo cliente può sembrare eccezionale sul tuo laptop con DOM XML, SQL Express e molti dati memorizzati nella cache sul lato client. Se hai successo, il sistema di produzione probabilmente bloccherà una bruciatura.

Nel 1976 discutevamo ancora dei modi ottimali per calcolare una radice quadrata o ordinare un grande array e il motto di Don Knuth era diretto all'errore di concentrarsi sull'ottimizzazione di quel tipo di routine di basso livello all'inizio del processo di progettazione piuttosto che concentrarsi su risolvere il problema e quindi ottimizzare regioni di codice localizzate.

Quando si ripete l'adagio come scusa per non scrivere codice efficiente (C ++, VB, T-SQL o altro), o per non progettare correttamente l'archivio dati, o per non considerare l'architettura di lavoro di rete, allora IMO sono solo dimostrando una comprensione molto superficiale della vera natura del nostro lavoro. Ray

    
risposta data 11.12.2014 - 18:09
fonte
1

Suppongo che dipenda da come si definisce "prematura". Rendere veloce la funzionalità di basso livello quando stai scrivendo non è intrinsecamente malvagio. Penso che sia un fraintendimento della citazione. A volte penso che la citazione potrebbe fare qualche qualifica in più. Tuttavia, farei eco ai commenti di m_pGladiator sulla leggibilità.

    
risposta data 17.10.2008 - 10:42
fonte
1

La risposta è: dipende. Discuterò che l'efficienza è un grosso problema per determinati tipi di lavoro, come le query complesse sui database. In molti altri casi il computer passa la maggior parte del tempo in attesa di input da parte dell'utente, quindi ottimizzare la maggior parte del codice è nel migliore dei casi uno spreco di sforzi e, nel peggiore dei casi, controproducente.

In alcuni casi è possibile progettare per l'efficienza o le prestazioni (percepite o reali) - selezionando un algoritmo appropriato o progettando un'interfaccia utente in modo che alcune costose operazioni avvengano in background, ad esempio. In molti casi, la profilazione o altre operazioni per determinare gli hotspot ti daranno un vantaggio di 10/90.

Un esempio di ciò che posso descrivere è il modello di dati che ho usato per un sistema di gestione dei casi giudiziari che conteneva circa 560 tabelle. È iniziato normalizzato ("magnificamente normalizzato" come lo ha detto il consulente di una certa azienda di grandi dimensioni) e abbiamo dovuto inserire solo quattro elementi di dati denormalizzati:

  • Una vista materializzata per supportare una schermata di ricerca

  • Una tabella gestita dal trigger per supportare un'altra schermata di ricerca che non può essere eseguita con una vista materializzata.

  • Una tabella di rapporto denormalizzata (questo esisteva solo perché dovevamo prendere alcuni rapporti di throughput quando un progetto di data warehouse è finito)

  • Una tabella gestita da trigger per un'interfaccia che ha dovuto cercare il più recente numero di eventi disparati all'interno del sistema.

Questo era (al momento) il più grande progetto J2EE in Australasia - ben oltre 100 anni di tempo per lo sviluppo - e aveva 4 elementi denormalizzati nello schema del database, uno dei quali in realtà non apparteneva affatto.

    
risposta data 01.05.2012 - 18:41
fonte
1

L'ottimizzazione prematura non è la radice di TUTTO il male, questo è certo. Ci sono tuttavia degli svantaggi:

  • investi più tempo durante lo sviluppo
  • investi più tempo a provarlo
  • investi più tempo nel correggere bug che altrimenti non sarebbero presenti

Al posto dell'ottimizzazione prematura, è possibile eseguire i primi test di visibilità per verificare se è effettivamente necessaria una ottimizzazione migliore.

    
risposta data 11.12.2014 - 18:47
fonte
1

La maggior parte di coloro che aderiscono a "PMO" (la citazione parziale, cioè) dice che le ottimizzazioni devono essere basate su misurazioni e le misurazioni non possono essere eseguite fino alla fine.

È anche la mia esperienza con lo sviluppo di grandi sistemi che i test delle prestazioni vengono eseguiti alla fine, a mano a mano che lo sviluppo si avvicina al completamento.

Se dovessimo seguire il "consiglio" di queste persone, tutti i sistemi sarebbero terribilmente lenti. Sarebbero anche costosi perché i loro bisogni hardware erano molto più grandi di quanto inizialmente previsto.

Da molto tempo sostengo di fare test delle prestazioni a intervalli regolari nel processo di sviluppo: indicherà sia la presenza del nuovo codice (dove prima non c'era nessuno) sia lo stato del codice esistente.

  • Le prestazioni del codice appena implementato possono essere confrontate con quello di codice esistente, simile. Una "sensazione" per le prestazioni del nuovo codice sarà stabilito nel tempo.
  • Se il codice esistente va improvvisamente in tilt capisci qualcosa è successo e puoi investigarlo immediatamente, non (molto) in seguito quando interessa l'intero sistema.

Un'altra idea da intenditori è il software dello strumento a livello di blocco funzionale. Mentre il sistema esegue, raccoglie informazioni sui tempi di esecuzione per i blocchi funzione. Quando viene eseguito un aggiornamento del sistema, è possibile determinare quali blocchi funzione eseguono come hanno fatto nella versione precedente e quelli che si sono deteriorati. Sullo schermo di un software è possibile accedere ai dati sulle prestazioni dal menu di aiuto.

Dai un'occhiata a questo pezzo eccellente su ciò che il PMO potrebbe o potrebbe non significare.

    
risposta data 06.10.2015 - 15:15
fonte

Leggi altre domande sui tag