Mantieni le ottimizzazioni locali, rendile ovvie, documentale bene e semplifica il confronto tra le versioni ottimizzate tra loro e con la versione non ottimizzata, sia in termini di codice sorgente che di prestazioni di runtime.
Risposta completa
Se tali ottimizzazioni sono davvero così importanti per il tuo prodotto, allora devi sapere non solo perché le ottimizzazioni sono state utili in precedenza, ma anche fornire informazioni sufficienti per aiutare gli sviluppatori a sapere se saranno utili in futuro.
Idealmente, è necessario includere i test delle prestazioni nel processo di compilazione, in modo da scoprire quando le nuove tecnologie invalidano le vecchie ottimizzazioni.
Ricorda:
La prima regola di ottimizzazione del programma: non farlo.
La seconda regola di ottimizzazione del programma (solo per esperti!): Non farlo ancora. "
- Michael A. Jackson
Per sapere se ora è il momento richiede benchmarking e test.
Come accennato, il problema più grande con il codice altamente ottimizzato è che è difficile da mantenere, quindi, per quanto possibile, è necessario mantenere le porzioni ottimizzate separate dalle porzioni non ottimizzate. Non importa se lo fai tramite il collegamento in fase di compilazione, le chiamate di funzione virtuale di runtime o qualcosa nel mezzo. Ciò che dovrebbe importare è che quando si eseguono i test, si desidera essere in grado di testare tutte le versioni a cui si è attualmente interessati.
Sarei propenso a costruire un sistema in modo tale che la versione non ottimizzata di base del codice di produzione possa sempre essere utilizzata per comprendere l' intento del codice, quindi costruire diversi moduli ottimizzati accanto a questo contenenti la versione o le versioni ottimizzate, documentando esplicitamente ovunque la versione ottimizzata differisce dalla linea di base. Quando si eseguono i test (unità e integrazione), lo si esegue sulla versione non ottimizzata e su tutti i moduli ottimizzati correnti.
Esempio
Ad esempio, supponiamo che tu abbia una funzione di Trasformata di Fourier veloce . Forse hai un'implementazione di base, algoritmica fft.c
e test in fft_tests.c
.
Poi arriva il Pentium e decidi di implementare la versione in virgola fissa fft_mmx.c
usando le istruzioni MMX . Successivamente arriva il pentium 3 e decidi di aggiungere una versione che utilizza Streaming SIMD Extensions in fft_sse.c
.
Ora vuoi aggiungere CUDA , quindi aggiungi fft_cuda.c
, ma scopri che con il set di dati di test che usi da anni, la versione CUDA è più lenta della versione SSE! Fai qualche analisi e finisci per aggiungere un set di dati 100 volte più grande e ottieni l'accelerazione che ti aspetti, ma ora sai che il tempo di configurazione per l'utilizzo della versione CUDA è significativo e che con set di dati piccoli dovresti usare un algoritmo senza quel costo di installazione.
In ognuno di questi casi stai implementando lo stesso algoritmo, tutti dovrebbero comportarsi allo stesso modo, ma funzioneranno con efficienze e velocità diverse su architetture diverse (se funzioneranno affatto). Dal punto di vista del codice, tuttavia, è possibile confrontare qualsiasi coppia di file di origine per scoprire perché la stessa interfaccia è implementata in modi diversi e di solito, il modo più semplice sarà fare riferimento alla versione originale non ottimizzata.
Lo stesso vale per un'implementazione OOP in cui una classe base che implementa l'algoritmo non ottimizzato e le classi derivate implementano diverse ottimizzazioni.
L'importante è mantenere le stesse cose uguali , in modo che le differenze siano evidenti .