Come documentare e insegnare agli altri un codice computazionalmente "ottimizzato oltre il riconoscimento"?


11

Occasionalmente esiste l'1% di codice sufficientemente intenso dal punto di vista computazionale che richiede il tipo più pesante di ottimizzazione a basso livello. Esempi sono l'elaborazione video, l'elaborazione delle immagini e tutti i tipi di elaborazione del segnale, in generale.

Gli obiettivi sono documentare e insegnare le tecniche di ottimizzazione, in modo che il codice non diventi irrinunciabile e incline alla rimozione da parte dei nuovi sviluppatori. (*)

(*) Nonostante la possibilità che la particolare ottimizzazione sia completamente inutile in alcune future CPU imprevedibili, in modo tale che il codice venga comunque eliminato.

Considerando che le offerte di software (commerciali o open source) mantengono il loro vantaggio competitivo avendo il codice più veloce e facendo uso della più recente architettura della CPU, gli autori di software devono spesso modificare il loro codice per farlo funzionare più velocemente ottenendo lo stesso output per un determinato compito, whlist tollerando una piccola quantità di errori di arrotondamento.

In genere, uno scrittore di software può conservare molte versioni di una funzione come documentazione di ogni riscrittura di ottimizzazione / algoritmo che ha luogo. Come si rendono queste versioni disponibili per gli altri per studiare le loro tecniche di ottimizzazione?

Relazionato:


1
Potresti semplicemente mantenere le diverse versioni nel codice, commentate, con molti commenti che dicono al lettore cosa sta succedendo.
Mike Dunlavey,

1
E non solo dire loro cosa sta facendo il codice, ma perché è più veloce in quel modo. Includi collegamenti ad algoritmi, se necessario, sia tuoi, come wiki, documenti o risorse disponibili su Internet (basta essere consapevoli del collegamento rot in quel caso, potrebbe essere saggio copiarlo nel tuo sistema di documenti con un collegamento all'originale .)
Marjan Venema,

1
@MikeDunlavey: Ahi, per favore non commentarlo. Basta avere diverse implementazioni della stessa funzione e chiamare quella più veloce. In questo modo puoi facilmente passare a una versione diversa del codice e confrontarli tutti.
sleske,

2
@sleske A volte basta avere più codice binario può rallentarlo.
quant_dev,

@quant_dev: Sì, può succedere. Penso solo che sia importante che il codice sia costruito ed eseguito (idealmente) regolarmente, per tenerlo aggiornato. Forse costruirlo solo in modalità debug.
sleske,

Risposte:


10

Risposta breve

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.ce test in fft_tests.c.

Poi arriva il Pentium e decidi di implementare la versione in virgola fissa fft_mmx.cusando 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 .


7

In particolare, poiché hai preso l'esempio dell'elaborazione di video e immagini, puoi mantenere il codice come parte della stessa versione ma attivo o inattivo a seconda del contesto.

Anche se non hai menzionato, presumo Cqui.

Il modo più semplice nel Ccodice, uno fa l'ottimizzazione (e si applica anche quando si cerca di rendere le cose portatili) è mantenere

 
#ifdef OPTIMIZATION_XYZ_ENABLE 
   // your optimzied code here... 
#else  
   // your basic code here...

Quando si attiva #define OPTIMIZATION_XYZ_ENABLEdurante la compilazione in Makefile, tutto funziona di conseguenza.

Di solito, tagliare alcune righe di codice nel mezzo di funzioni potrebbe diventare confuso quando ci sono troppe funzioni ottimizzate. Quindi, in questo caso si definiscono diversi puntatori a funzione per eseguire una funzione specifica.

il codice principale viene sempre eseguito tramite un puntatore a funzione come


   codec->computed_idct(blocks); 

Ma i puntatori a funzione sono definiti in base al tipo di esempio (ad es. Qui la funzione idct è ottimizzata per diverse architetture della CPU.



if(OPTIMIZE_X86) {
  codec->computed_idct = compute_idct_x86; 
}
else if(OPTIMZE_ARM) {
  codec->computed_idct = compute_idct_ARM;
}
else {
  codec->computed_idct = compute_idct_C; 
}

dovresti vedere il codice libjpeg e il codice libmpeg2 e potresti essere ffmpeg per tali tecniche.


6

Come ricercatore finisco per scrivere un po 'del codice "collo di bottiglia". Tuttavia, una volta che è entrato in produzione, l'onere di integrarlo nel prodotto e fornire il supporto successivo ricade sugli sviluppatori. Come puoi immaginare, comunicare chiaramente cosa e come dovrebbe funzionare il programma è della massima importanza.

Ho scoperto che ci sono tre ingredienti essenziali per completare con successo questo passaggio

  1. L'algoritmo utilizzato deve essere assolutamente chiaro.
  2. Lo scopo di ogni linea di attuazione deve essere chiaro.
  3. Deviazioni dai risultati attesi devono essere identificate al più presto.

Per il primo passo, scrivo sempre un breve white paper che documenta l'algoritmo. Lo scopo qui è di scriverlo effettivamente in modo che un'altra persona possa implementarlo da zero usando solo il white paper. Se è un noto algoritmo pubblicato, è sufficiente fornire i riferimenti e ripetere le equazioni chiave. Se è un lavoro originale, dovrai essere un po 'più esplicito. Questo ti dirà cosa dovrebbe fare il codice .

L'attuazione effettiva che viene trasmessa allo sviluppo deve essere documentata in modo tale da rendere esplicite tutte le sottigliezze. Se si acquisiscono blocchi in un ordine particolare per evitare deadlock, aggiungere un commento. Se si esegue l'iterazione sulle colonne anziché sulle righe di una matrice a causa di problemi di coerenza della cache, aggiungere un commento. Se fai qualcosa di anche leggermente intelligente, commentalo. Se è possibile garantire il white paper e il codice non verrà mai separato (tramite VCS o sistema simile), è possibile fare riferimento al white paper. Il risultato può essere facilmente oltre il 50% dei commenti. Va bene. Questo ti dirà perché il codice fa quello che fa.

Infine, devi essere in grado di garantire la correttezza di fronte ai cambiamenti. Fortunatamente siamo uno strumento utile per test automatici e piattaforme di integrazione continua . Questi ti diranno cosa sta realmente facendo il codice .

La mia più calorosa raccomandazione sarebbe di non saltare su nessuno dei passaggi. Ne avrai bisogno in seguito;)


Grazie per la tua risposta esaustiva. Sono d'accordo con tutti i tuoi punti. In termini di test automatizzati, trovo che coprire adeguatamente l'intervallo numerico dell'aritmetica in virgola fissa e del codice SIMD sia difficile, qualcosa che sono stato bruciato due volte. I presupposti che erano stati indicati solo nei commenti (senza codice da rinforzare) non erano sempre soddisfatti.
rwong,

Il motivo per cui non ho ancora accettato la tua risposta è perché ho bisogno di maggiori indicazioni su cosa significhi "un breve white paper" e su quali sforzi dovrebbero impegnarsi per produrlo. Per alcuni settori, questo fa parte della linea di business principale, ma in altri settori il costo deve essere considerato e le scorciatoie legalmente disponibili dovrebbero essere state prese.
rwong

Prima di tutto, provo dolore per i test automatici, l'aritmetica in virgola mobile e il codice parallelo. Temo che non esista una soluzione valida per tutti i casi. Di solito lavoro con tolleranze abbastanza liberali, ma nel tuo settore potrebbe non essere possibile.
drxzcl,

2
In pratica, il white paper appare spesso come la prima bozza di un documento scientifico, senza le parti "fluff" (nessuna introduzione significativa, nessun abstract, conclusioni / discussioni minime e solo i riferimenti necessari per capirlo). Vedo scrivere l'articolo come una relazione e parte integrante dello sviluppo dell'algoritmo e / o della selezione dell'algoritmo. Hai scelto di implementare questo algoritmo (ad esempio FFT spettrale). Che cosa è esattamente? Perché hai scelto questo tra gli altri? Quali sono le sue caratteristiche di parallelizzazione? Lo sforzo dovrebbe essere proporzionale al lavoro di selezione / sviluppo.
drxzcl,

5

Ritengo che ciò debba essere risolto nel modo migliore attraverso un commento completo del codice, al punto in cui ogni blocco significativo di codice ha precedentemente commenti esplicativi.

I commenti dovrebbero includere citazioni alle specifiche o materiale di riferimento hardware.

Utilizzare la terminologia a livello di settore e i nomi degli algoritmi ove appropriato, ad esempio "l'architettura X genera trap della CPU per letture non allineate, in modo che il dispositivo di Duff si riempia fino al prossimo confine di allineamento".

Vorrei utilizzare la denominazione variabile in-your-face per evitare equivoci su ciò che sta accadendo. Non ungherese, ma cose come "passo" per descrivere la distanza in byte tra due pixel verticali.

Vorrei anche integrare questo con un breve documento leggibile umanamente che ha diagrammi di alto livello e design a blocchi.


1
Usare una terminologia coerente per una sola cosa (ad es. Usare "passo" su termini con significati simili, ad esempio "passo", "allineamento") nello stesso progetto sarebbe di aiuto. Questo è alquanto difficile quando si integra la base di codice di più progetti in un unico progetto.
rwong
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.