Come evitare la maledizione dell'ottimizzazione prematura

Pubblicato: 2022-03-11

È quasi degno di una garanzia, davvero. Dai principianti agli esperti, dall'architettura fino all'ASM e ottimizzando qualsiasi cosa, dalle prestazioni della macchina alle prestazioni degli sviluppatori, è molto probabile che tu e il tuo team stiate mandando in corto circuito i vostri obiettivi.

Che cosa? Me? La mia squadra?

È un'accusa piuttosto pesante da livellare. Lasciatemi spiegare.

L'ottimizzazione non è il Santo Graal, ma può essere altrettanto difficile da ottenere. Voglio condividere con voi alcuni semplici consigli (e una montagna di insidie) per aiutare a trasformare l'esperienza del vostro team da un'esperienza di auto-sabotaggio a una di armonia, realizzazione, equilibrio e, infine, ottimizzazione.

Che cos'è l'ottimizzazione prematura?

L'ottimizzazione prematura sta tentando di ottimizzare le prestazioni:

  1. Quando si codifica per la prima volta un algoritmo
  2. Prima che i benchmark confermino che è necessario
  3. Prima di profilare, individua dove ha senso preoccuparsi dell'ottimizzazione
  4. A un livello inferiore rispetto a quello attualmente richiesto dal tuo progetto

Sono un ottimista, Optimus.

Almeno, farò finta di essere un ottimista mentre scrivo questo articolo. Da parte tua, puoi fingere di chiamarti Optimus, quindi questo ti parlerà più direttamente.

Come qualcuno in tecnologia, probabilmente a volte ti chiedi come potrebbe essere $year e tuttavia, nonostante tutti i nostri progressi, è in qualche modo uno standard accettabile per $task che richiede così tanto tempo. Vuoi essere magra. Efficiente. Stupendo. Qualcuno come i programmatori Rockstar per i quali chiedono a gran voce quegli annunci di lavoro, ma con doti da leader. Quindi, quando il tuo team scrive codice, lo incoraggi a farlo bene la prima volta (anche se "giusto" è un termine molto relativo, qui). Sanno che questo è il modo del programmatore intelligente, e anche il modo di coloro che non hanno bisogno di perdere tempo a rielaborare in seguito.

Sento che. La forza del perfezionismo a volte è forte anche dentro di me. Vuoi che il tuo team dedichi un po' di tempo ora per risparmiare molto tempo dopo, perché tutti hanno sgobbato la loro quota di "Codice di merda che altre persone hanno scritto (che diavolo stavano pensando?)." Questo è SCOPWWWTT in breve, perché so che ti piacciono gli acronimi impronunciabili.

So anche che non vuoi che il codice del tuo team sia quello per se stesso o per chiunque altro in futuro.

Vediamo quindi cosa si può fare per guidare il vostro team nella giusta direzione.

Cosa ottimizzare: benvenuto in questo essere un'arte

Prima di tutto, quando si pensa all'ottimizzazione dei programmi, spesso si presume subito che si parli di prestazioni. Anche questo è già più vago di quanto possa sembrare (velocità? utilizzo della memoria? ecc.), quindi fermiamoci qui.

Rendiamolo ancora più ambiguo! Solo all'inizio.

Al mio cervello da ragnatela piace creare ordine ove possibile, quindi ci vorrà ogni briciolo di ottimismo per me per considerare ciò che sto per dire come una buona cosa .

C'è una semplice regola di ottimizzazione (delle prestazioni) là fuori che dice Non farlo . Sembra abbastanza facile da seguire rigidamente, ma non tutti sono d'accordo con esso. Anche io non sono completamente d'accordo. Alcune persone semplicemente scriveranno un codice migliore fuori dal cancello rispetto ad altri. Si spera che, per una determinata persona, la qualità del codice che scriverebbero in un progetto nuovo di zecca generalmente migliorerà nel tempo. Ma so che, per molti programmatori, non sarà così, perché più ne sanno, più saranno tentati di ottimizzare prematuramente.

Per molti programmatori... più ne sanno, più saranno tentati di ottimizzare prematuramente.

Quindi questo non farlo non può essere una scienza esatta ma ha solo lo scopo di contrastare il tipico bisogno interiore del tecnico di risolvere il puzzle. Questo, dopotutto, è ciò che attrae molti programmatori al mestiere in primo luogo. Lo capisco. Ma chiedi loro di salvarlo , di resistere alla tentazione. Se uno ha bisogno di uno sfogo per la risoluzione di enigmi in questo momento , può sempre dilettarsi nel Sudoku del giornale della domenica, o prendere un libro della Mensa o andare a giocare a golf con qualche problema artificiale. Ma lascialo fuori dal repository fino al momento opportuno. Quasi sempre questo è un percorso più saggio della pre-ottimizzazione.

Ricorda, questa pratica è abbastanza nota che le persone si chiedono se l'ottimizzazione prematura sia la radice di tutti i mali. (Non andrei così lontano, ma sono d'accordo con il sentimento.)

Non sto dicendo che dovremmo scegliere il modo più insensibile che possiamo pensare a ogni livello di progettazione. Ovviamente no. Ma invece di scegliere l'aspetto più intelligente, possiamo considerare altri valori:

  1. Il più facile da spiegare al tuo nuovo assunto
  2. La più probabile che superi una revisione del codice da parte del tuo sviluppatore più esperto
  3. Il più manutenibile
  4. Il più veloce da scrivere
  5. Il più facile da testare
  6. Il più portatile
  7. eccetera.

Ma qui è dove il problema si rivela difficile. Non si tratta solo di evitare l'ottimizzazione per velocità, dimensioni del codice, footprint di memoria, flessibilità o a prova di futuro. Si tratta di equilibrio e se ciò che stai facendo è effettivamente in linea con i tuoi valori e obiettivi. È del tutto contestuale e talvolta persino impossibile da misurare oggettivamente.

È un'arte. (Cfr L'arte della programmazione informatica.)

Perché questa è una cosa buona? Perché la vita è così. È disordinato. I nostri cervelli orientati alla programmazione a volte vogliono creare ordine nel caos così tanto che finiamo per moltiplicare ironicamente il caos. È come il paradosso di cercare di costringere qualcuno ad amarti. Se pensi di esserci riuscito, non è più amore; nel frattempo, sei accusato di presa di ostaggi, probabilmente hai bisogno di più amore che mai, e questa metafora deve essere una delle più imbarazzanti che avrei potuto scegliere.

Ad ogni modo, se pensi di aver trovato il sistema perfetto per qualcosa, beh... goditi l'illusione finché dura, immagino. Va bene, i fallimenti sono meravigliose opportunità per imparare.

L'ottimizzazione prematura spesso complica inutilmente e inutilmente il tuo prodotto.

Tieni a mente l'UX

Esaminiamo come l'esperienza utente si inserisce tra queste potenziali priorità. Dopotutto, anche volere che qualcosa funzioni bene è, a un certo livello, una questione di UX.

Se stai lavorando su un'interfaccia utente, indipendentemente dal framework o dalla lingua utilizzata dal codice, ci sarà una certa quantità di standard e ripetizioni. Può sicuramente essere prezioso in termini di tempo del programmatore e chiarezza del codice cercare di ridurlo. Per aiutare con l'arte di bilanciare le priorità, voglio condividere un paio di storie.

In un lavoro, l'azienda per cui lavoravo utilizzava un sistema aziendale closed-source basato su uno stack tecnologico supponente. In effetti, era così supponente che il venditore che ce lo ha venduto si è rifiutato di apportare personalizzazioni dell'interfaccia utente che non corrispondevano alle opinioni dello stack, perché era così doloroso per i loro sviluppatori. Non ho mai usato il loro stack, quindi non li condanno per questo, ma il fatto era che questo compromesso "buono per il programmatore, cattivo per l'utente" era così ingombrante per i miei colleghi in determinati contesti che ho concluso fino a scrivere un componente aggiuntivo di terze parti che re-implementa questa parte dell'interfaccia utente del sistema. (È stato un enorme aumento della produttività. I ​​miei colleghi l'hanno adorato! Oltre un decennio dopo, sta ancora facendo risparmiare tempo e frustrazione a tutti.)

Non sto dicendo che l'ostinazione sia un problema in sé; solo che troppo è diventato un problema nel nostro caso. Come controesempio, uno dei grandi vantaggi di Ruby on Rails è proprio che è supponente, in un ecosistema front-end in cui si ottiene facilmente la vertigine dall'avere troppe opzioni. (Dammi qualcosa con le opinioni finché non riesco a capire la mia!)

Al contrario, potresti essere tentato di incoronare UX il re di tutto nel tuo progetto. Un traguardo degno, ma lasciatemi raccontare la mia seconda storia.

Alcuni anni dopo il successo del suddetto progetto, un mio collega è venuto da me per chiedermi di ottimizzare l'UX automatizzando un certo disordinato scenario di vita reale che a volte si presentava, in modo che potesse essere risolto con un solo clic. Ho iniziato ad analizzare se fosse anche possibile progettare un algoritmo che non avesse falsi positivi o negativi a causa dei molti e strani casi limite dello scenario. Più ne parlavo con il mio collega, più mi rendevo conto che i requisiti semplicemente non avrebbero pagato. Lo scenario si presentava solo una volta ogni tanto - mensilmente, diciamo - e attualmente una persona ha impiegato alcuni minuti per risolverlo. Anche se potessimo automatizzarlo con successo, senza alcun bug, ci vorrebbero secoli prima che il tempo di sviluppo e manutenzione richiesto venisse ripagato in termini di tempo risparmiato dai miei colleghi. Il piacere delle persone in me ha avuto un momento difficile dicendo "no", ma ho dovuto interrompere la conversazione.

Quindi lascia che il computer faccia il possibile per aiutare l'utente, ma solo in misura ragionevole. Come fai a sapere che misura è, però?

Cosa è facile e difficile per i computer

Un approccio che mi piace adottare è quello di profilare l'UX come i tuoi sviluppatori profilano il loro codice. Scopri dai tuoi utenti dove trascorrono la maggior parte del tempo facendo clic o digitando la stessa cosa più e più volte e vedi se riesci a ottimizzare tali interazioni. Il tuo codice può fare alcune ipotesi plausibili su ciò che molto probabilmente inseriranno e renderlo un valore predefinito senza input? A parte alcuni contesti proibiti (conferma EULA senza clic?) questo può davvero fare la differenza per la produttività e la felicità dei tuoi utenti. Se puoi, fai dei test di usabilità nel corridoio. A volte, potresti avere difficoltà a spiegare in cosa è facile aiutare i computer e cosa non lo è... ma nel complesso, è probabile che questo valore abbia un'importanza piuttosto alta per i tuoi utenti.

Evitare l'ottimizzazione prematura: quando e come ottimizzare

Nonostante la nostra esplorazione di altri contesti, ora assumiamo esplicitamente che stiamo ottimizzando alcuni aspetti delle prestazioni della macchina grezza per il resto di questo articolo. Il mio approccio suggerito si applica anche ad altri obiettivi, come la flessibilità, ma ogni obiettivo avrà i suoi trucchi; il punto principale è che l'ottimizzazione prematura per qualsiasi cosa probabilmente fallirà.

Quindi, in termini di prestazioni, quali metodi di ottimizzazione ci sono effettivamente da seguire? Scendiamo.

Questa non è un'iniziativa di base, è Triple-Eh

Il TL;DR è: Lavora dall'alto verso il basso. Le ottimizzazioni di livello superiore possono essere effettuate in precedenza nel progetto e quelle di livello inferiore dovrebbero essere lasciate per dopo. Questo è tutto ciò di cui hai bisogno per ottenere la maggior parte del significato della frase "ottimizzazione prematura"; fare le cose in questo ordine ha un'alta probabilità di far perdere tempo alla tua squadra e di essere controefficace. Dopotutto, non scrivi l'intero progetto in codice macchina fin dall'inizio, vero? Quindi il nostro modus operandi AAA è quello di ottimizzare in questo ordine:

  1. Architettura
  2. Algoritmi
  3. Assemblea

È opinione comune che gli algoritmi e le strutture dati siano spesso i luoghi più efficaci per l'ottimizzazione, almeno per quanto riguarda le prestazioni. Tieni presente, tuttavia, che l'architettura a volte determina quali algoritmi e strutture di dati possono essere utilizzati.

Una volta ho scoperto un software che esegue un report finanziario eseguendo query su un database SQL più volte per ogni singola transazione finanziaria, quindi eseguendo un calcolo molto semplice sul lato client. La piccola impresa ha impiegato solo pochi mesi di utilizzo del software prima che anche la loro quantità relativamente piccola di dati finanziari significasse che, con desktop nuovi di zecca e un server abbastanza robusto, il tempo di generazione del rapporto era già di diversi minuti, e questo era uno che dovevano usare abbastanza frequentemente. Ho finito per scrivere una semplice istruzione SQL che conteneva la logica di somma - vanificando la loro architettura spostando il lavoro sul server per evitare tutte le duplicazioni e i round trip di rete - e anche diversi anni di dati dopo, la mia versione poteva generare il stesso rapporto in pochi millisecondi sullo stesso vecchio hardware.

A volte non hai influenza sull'architettura di un progetto perché è troppo tardi nel progetto perché una modifica dell'architettura sia fattibile. A volte i tuoi sviluppatori possono aggirarlo come ho fatto nell'esempio sopra. Ma se sei all'inizio di un progetto e hai voce in capitolo sulla sua architettura, ora è il momento di ottimizzarlo.

Architettura

"Se i costruttori costruissero edifici nel modo in cui i programmatori scrivevano i programmi, il primo picchio che sarebbe arrivato distruggerebbe la civiltà". --Legge di Weinberg

In un progetto, l'architettura è la parte più costosa da modificare a posteriori, quindi questo è un luogo in cui può avere senso ottimizzare all'inizio. Se la tua app deve fornire dati tramite struzzi, ad esempio, ti consigliamo di strutturarla in pacchetti a bassa frequenza e ad alto carico utile per evitare di peggiorare ulteriormente un brutto collo di bottiglia. In questo caso, è meglio avere un'implementazione completa di Tetris per intrattenere i tuoi utenti, perché uno spinner di caricamento non lo taglierà. (Scherzi a parte: anni fa stavo installando la mia prima distribuzione Linux, Corel Linux 2.0, ed ero felice che il processo di installazione di lunga durata includesse proprio questo. Avendo visto le schermate pubblicitarie del programma di installazione di Windows 95 così tante volte che le avevo memorizzate, questo era una boccata d'aria fresca in quel momento.)

Come esempio di costosa modifica dell'architettura, il motivo per cui il suddetto report SQL è così altamente non scalabile in primo luogo è chiaro dalla sua storia. L'app si è evoluta nel tempo, dalle sue radici in MS-DOS e un database personalizzato fatto in casa che in origine non era nemmeno multiutente. Quando il fornitore è finalmente passato a SQL, lo schema e il codice di reporting sembrano essere stati portati uno per uno. Ciò ha lasciato loro anni di straordinari miglioramenti delle prestazioni di oltre il 1.000% da cospargere durante i loro aggiornamenti, ogni volta che sono riusciti a completare il passaggio all'architettura sfruttando effettivamente i vantaggi di SQL per un determinato report. Buono per gli affari con clienti bloccati come il mio datore di lavoro di allora e che tentano chiaramente di dare la priorità all'efficienza della codifica durante la transizione iniziale. Ma soddisfare le esigenze dei clienti, in alcuni casi, con la stessa efficacia di un martello che gira una vite.

L'architettura consiste in parte nell'anticipare fino a che punto il tuo progetto dovrà essere in grado di scalare e in che modo. Poiché l'architettura è di così alto livello, è difficile concretizzare le nostre cose da fare e da non fare senza restringere la nostra attenzione a tecnologie e domini specifici.

Non lo chiamerei così, ma lo fanno tutti gli altri

Per fortuna, Internet è pieno di saggezza raccolta su quasi tutti i tipi di architettura mai inventati. Quando sai che è il momento di ottimizzare la tua architettura, la ricerca delle insidie ​​si riduce praticamente a capire la parola d'ordine che descrive la tua brillante visione. È probabile che qualcuno abbia pensato come te, l'abbia provato, abbia fallito, ripetuto e pubblicato su un blog o un libro.

L'identificazione delle parole d'ordine può essere difficile da realizzare semplicemente cercando, perché per quello che chiami FLDSMDFR, qualcun altro ha già coniato il termine SCOPWWHWTT e descrivono lo stesso problema che stai risolvendo, ma usando un vocabolario completamente diverso da quello che faresti tu. Comunità di sviluppatori in soccorso! Colpisci StackExchange o HashNode con una descrizione quanto più completa possibile, oltre a tutte le parole d'ordine che la tua architettura non è , in modo che sappiano che hai svolto ricerche preliminari sufficienti. Qualcuno sarà lieto di illuminarti.

Nel frattempo, alcuni consigli generali potrebbero essere uno spunto di riflessione.

Algoritmi e Assemblaggio

Data un'architettura favorevole, qui è dove i programmatori del tuo team otterranno più T-bling per il loro tempo. Anche qui si applica l'evitamento di base dell'ottimizzazione prematura, ma i tuoi programmatori farebbero bene a considerare alcune delle specifiche a questo livello. C'è così tanto a cui pensare quando si tratta di dettagli di implementazione che ho scritto un articolo separato sull'ottimizzazione del codice orientato ai programmatori di prima linea e senior.

Ma una volta che tu e il tuo team avete implementato qualcosa di non ottimizzato dal punto di vista delle prestazioni, lo lasci davvero a Non farlo ? Non ottimizzi mai ?

Hai ragione. La regola successiva è, solo per esperti, non farlo ancora .

Tempo di benchmark!

Il tuo codice funziona. Forse è così lento che sai già che dovrai ottimizzare, perché è un codice che verrà eseguito spesso. Forse non sei sicuro, o hai un algoritmo O(n) e capisci che probabilmente va bene. In ogni caso, se valesse la pena ottimizzare questo algoritmo, la mia raccomandazione a questo punto è la stessa: esegui un semplice benchmark.

Come mai? Non è chiaro che il mio algoritmo O(n³) non può essere peggiore di qualsiasi altra cosa? Ebbene, per due motivi:

  1. Puoi aggiungere il benchmark alla tua suite di test, come misura oggettiva dei tuoi obiettivi di performance, indipendentemente dal fatto che siano attualmente raggiunti.
  2. Anche gli esperti possono inavvertitamente rallentare le cose. Anche quando sembra ovvio. Davvero ovvio.

Non mi credi su quel secondo punto?

Come ottenere risultati migliori da $ 1.400 hardware rispetto a $ 7.000 hardware

Jeff Atwood, famoso per StackOverflow, una volta ha sottolineato che a volte (di solito, secondo lui) può essere più conveniente acquistare semplicemente hardware migliore piuttosto che dedicare tempo prezioso all'ottimizzazione. OK, quindi supponi di aver raggiunto una conclusione ragionevolmente obiettiva che il tuo progetto si adatterebbe a questo scenario. Supponiamo inoltre che ciò che stai cercando di ottimizzare sia il tempo di compilazione, perché è un progetto Swift pesante su cui stai lavorando e questo è diventato un collo di bottiglia piuttosto grande per gli sviluppatori. Tempo di shopping hardware!

Cosa dovresti comprare? Bene, ovviamente, yen per yen, l'hardware più costoso tende a funzionare meglio dell'hardware più economico. Quindi, ovviamente, un Mac Pro da $ 7.000 dovrebbe compilare il tuo software più velocemente di un Mac Mini di fascia media, giusto?

Sbagliato!

Si scopre che a volte più core significa una compilazione più efficiente... e in questo caso particolare, LinkedIn ha scoperto nel modo più duro che è vero il contrario per il loro stack.

Ma ho visto la gestione che ha commesso un ulteriore errore: non hanno nemmeno eseguito il benchmark prima e dopo e hanno scoperto che un aggiornamento hardware non rendeva il loro software più veloce. Ma non c'era modo di saperlo con certezza; e inoltre, non avevano ancora idea di dove fosse il collo di bottiglia, quindi sono rimasti insoddisfatti delle prestazioni, avendo esaurito il tempo e il denaro che erano disposti a destinare al problema.

OK, ho già eseguito il benchmarking. Posso effettivamente ottimizzare ancora ??

Sì, ammesso che tu abbia deciso di doverlo fare. Ma forse quella decisione attenderà fino all'implementazione di più/tutti gli altri algoritmi, così puoi vedere come le parti mobili si incastrano e quali sono le più importanti tramite la profilazione. Questo potrebbe essere a livello di app per una piccola app o potrebbe essere applicato solo a un sottosistema. Ad ogni modo, ricorda, un particolare algoritmo può sembrare importante per l'app in generale, ma anche gli esperti, in particolare gli esperti, sono inclini a diagnosticare erroneamente questo.

Pensa prima di interrompere

"Non so voi gente, ma..."

Gavin Belson della Silicon Valley dicendo: "Non voglio vivere in un mondo in cui qualcun altro rende il mondo un posto migliore... migliore di noi".

Come ultimo spunto di riflessione, considera come applicare l'idea di falsa ottimizzazione a una visione molto più ampia: il tuo progetto o l'azienda stessa, o anche un settore dell'economia.

Lo so, è allettante pensare che la tecnologia salverà la situazione e che possiamo essere gli eroi che lo realizzano.

Inoltre, se non lo facciamo noi, lo farà qualcun altro.

Ma ricorda che il potere corrompe, nonostante le migliori intenzioni. Non mi collegherò a nessun articolo in particolare qui, ma se non hai vagato per nessuno, vale la pena cercare qualcosa sull'impatto più ampio di sconvolgimento dell'economia e su chi a volte serve alla fine. Potresti essere sorpreso da alcuni degli effetti collaterali del tentativo di salvare il mondo attraverso l'ottimizzazione.

Post scriptum

Hai notato qualcosa, Optimus? L'unica volta che ti ho chiamato Optimus è stato all'inizio e ora alla fine. Non sei stato chiamato Optimus per tutto l'articolo. Sarò onesto, dimenticavo. Ho scritto l'intero articolo senza chiamarti Optimus. Alla fine, quando ho capito che dovevo tornare indietro e cospargere il tuo nome per tutto il testo, una vocina dentro di me ha detto, non farlo .