Domanda:
Il consenso dell'industria del software è che il codice pulito e semplice è fondamentale per la fattibilità a lungo termine della base di codice e dell'organizzazione che lo possiede. Queste proprietà comportano minori costi di manutenzione e una maggiore probabilità che la base di codice continui.
Tuttavia, il codice SIMD è diverso dal codice generale dell'applicazione e vorrei sapere se esiste un consenso simile per quanto riguarda il codice pulito e semplice che si applica specificamente al codice SIMD.
Contesto della mia domanda.
Scrivo un sacco di codice SIMD (istruzione singola, dati multipli) per varie attività di elaborazione e analisi delle immagini. Di recente ho anche dovuto trasferire un numero limitato di queste funzioni da un'architettura (SSE2) a un'altra (ARM NEON).
Il codice è scritto per un software termoretraibile, pertanto non può dipendere da linguaggi proprietari senza diritti di ridistribuzione senza restrizioni come MATLAB.
Un esempio di struttura tipica del codice:
- Utilizzo del tipo di matrice OpenCV (
Mat
) per tutta la memoria, il buffer e la gestione della durata. - Dopo aver verificato la dimensione (dimensioni) degli argomenti di input, vengono presi i puntatori all'indirizzo iniziale di ogni riga di pixel.
- Il conteggio dei pixel e gli indirizzi iniziali di ogni riga di pixel da ciascuna matrice di input vengono passati in alcune funzioni C ++ di basso livello.
- Queste funzioni C ++ di basso livello utilizzano intrinseche SIMD (per Intel Architecture e ARM NEON ), caricando e salvando su indirizzi puntatore non elaborati.
- Caratteristiche di queste funzioni C ++ di basso livello:
- Esclusivamente monodimensionale (consecutivo in memoria)
- Non si occupa delle allocazioni di memoria.
(Ogni allocazione, compresi i provvisori, è gestita dal codice esterno utilizzando le strutture OpenCV.) - L'intervallo delle lunghezze dei nomi dei simboli (valori intrinseci, nomi delle variabili, ecc.) È di circa 10-20 caratteri, il che è piuttosto eccessivo.
(Legge come tecno-chiacchiere.) - Il riutilizzo delle variabili SIMD è sconsigliato perché i compilatori sono piuttosto buggy nell'analizzare correttamente il codice che non è scritto nello stile di codifica "assegnazione singola".
(Ho presentato diverse segnalazioni di bug del compilatore.)
Quali aspetti della programmazione SIMD farebbero differire la discussione dal caso generale? Oppure, perché SIMD è diverso?
In termini di costi di sviluppo iniziale
- È noto che il costo di sviluppo iniziale del codice C ++ SIMD con buone prestazioni è di circa 10x - 100x (con un ampio margine) rispetto al codice C ++ scritto casualmente .
- Come indicato nelle risposte alla scelta tra prestazioni e codice leggibile / più pulito? , la maggior parte del codice (incluso il codice scritto casualmente e il codice SIMD) non è inizialmente né pulito né veloce .
- I miglioramenti evolutivi delle prestazioni del codice (sia in codice scalare che in codice SIMD) sono sconsigliati (perché sono visti come una sorta di rilavorazione del software ) e i costi e i benefici non sono tracciati.
In termini di propensione
(ad esempio il principio di Pareto, alias la regola 80-20 )
- Anche se l'elaborazione delle immagini comprende solo il 20% di un sistema software (sia nella dimensione del codice che nella funzionalità), l'elaborazione delle immagini è relativamente lenta (se vista come percentuale del tempo di CPU impiegato), impiegando più dell'80% del tempo.
- Ciò è dovuto all'effetto della dimensione dei dati: una dimensione tipica dell'immagine viene misurata in megabyte, mentre la dimensione tipica dei dati non immagine viene misurata in kilobyte.
- All'interno del codice di elaborazione delle immagini, un programmatore SIMD è addestrato a riconoscere automaticamente il codice del 20% comprendente gli hotspot identificando la struttura del ciclo nel codice C ++. Pertanto, dal punto di vista di un programmatore SIMD, il 100% del "codice che conta" è un collo di bottiglia delle prestazioni.
- Spesso in un sistema di elaborazione delle immagini esistono più hotspot che occupano proporzioni di tempo comparabili. Ad esempio, potrebbero esserci 5 punti attivi ciascuno (20%, 18%, 16%, 14%, 12%) del tempo totale. Per ottenere un elevato rendimento, tutti gli hotspot devono essere riscritti in SIMD.
- Questo è riassunto come la regola di scoppiare i palloncini : un palloncino non può essere lanciato due volte.
- Supponiamo che ci siano alcuni palloncini, diciamo 5 di loro. L'unico modo per decimarli è farli scoppiare uno a uno.
- Una volta che il primo palloncino viene lanciato, i restanti 4 palloncini ora comprendono una percentuale più alta del tempo totale di esecuzione.
- Per ottenere ulteriori guadagni, si deve quindi far scoppiare un altro pallone.
(Ciò è in contrasto con la regola dell'ottimizzazione dell'80-20: un buon risultato economico può essere raggiunto dopo che è stato raccolto il 20% dei frutti più bassi).
In termini di leggibilità e manutenzione
Il codice SIMD è palesemente difficile da leggere.
- Ciò è vero anche se si seguono tutte le migliori pratiche di ingegneria del software, ad esempio denominazione, incapsulamento, correttezza const (e rendendo evidenti gli effetti collaterali), decomposizione delle funzioni, ecc.
- Questo vale anche per programmatori SIMD esperti.
Il codice SIMD ottimale è molto contorto, (vedi osservazione) rispetto al suo equivalente codice prototipo C ++.
- Esistono molti modi per contorcere il codice SIMD, ma solo 1 su 10 di questi tentativi otterrà risultati accettabilmente veloci.
- (Cioè, in sintonia con incrementi di prestazioni 4x-10x per giustificare un elevato costo di sviluppo. In pratica sono stati osservati anche guadagni più elevati.)
(Nota)
Questa è la tesi principale del progetto alogenuro del MIT - citando il titolo del documento alla lettera:
"disaccoppiamento degli algoritmi dalle pianificazioni per una facile ottimizzazione delle condotte di elaborazione delle immagini"
In termini di applicabilità diretta
- Il codice SIMD è strettamente legato a una singola architettura. Ogni nuova architettura (o ogni ampliamento dei registri SIMD) richiede una riscrittura.
- A differenza della maggior parte dello sviluppo software, ogni pezzo di codice SIMD è in genere scritto per un singolo scopo che non cambia mai.
(Ad eccezione del porting su altre architetture.) - Alcune architetture mantengono una perfetta compatibilità con le versioni precedenti (Intel); alcuni mancano di una quantità banale (ARM AArch64, sostituendo
vtbl
convtblq
) ma che è sufficiente per impedire la compilazione del codice.
In termini di competenze e formazione
- Non è chiaro quali siano i prerequisiti di conoscenza necessari per addestrare correttamente un nuovo programmatore a scrivere e conservare il codice SIMD.
- I laureati che hanno imparato la programmazione SIMD a scuola sembrano disprezzarla e liquidarla come impraticabile percorso di carriera.
- La lettura del disassemblaggio e la profilazione delle prestazioni di basso livello sono citate come due abilità fondamentali per la scrittura di codice SIMD ad alte prestazioni. Tuttavia, non è chiaro come addestrare sistematicamente i programmatori in queste due abilità.
- La moderna architettura della CPU (che differisce in modo significativo da ciò che viene insegnato nei libri di testo) rende la formazione ancora più difficile.
In termini di correttezza e costi relativi ai difetti
- Una singola funzione di elaborazione SIMD è in realtà abbastanza coerente da poter stabilire la correttezza mediante:
- Applicazione di metodi formali (con carta e penna) e
- Verifica degli intervalli interi di output (con codice prototipo ed eseguito al di fuori del runtime) .
- Il processo di verifica è tuttavia molto costoso (impiega il 100% del tempo per la revisione del codice e il 100% del tempo per il controllo del modello prototipo), il che triplica il già costoso costo di sviluppo del codice SIMD.
- Se in qualche modo un bug riesce a superare questo processo di verifica, è quasi impossibile "riparare" (correggere) se non per sostituire (riscrivere) la sospetta funzione difettosa.
- Il codice SIMD soffre del contrappeso di difetti nel compilatore C ++ (ottimizzazione del generatore di codice).
- Anche il codice SIMD generato utilizzando i modelli di espressione C ++ soffre notevolmente dei difetti del compilatore.
In termini di innovazioni dirompenti
Molte soluzioni sono state proposte dal mondo accademico, ma poche vedono un uso commerciale diffuso.
- Alogenuro del MIT
- Stanford Darkroom
- NT2 (Numerical Template Toolbox) e il relativo Boost.SIMD
Le librerie con un uso commerciale diffuso non sembrano essere fortemente abilitate al SIMD.
- Le librerie open source sembrano tiepide per SIMD.
- Di recente ho avuto questa osservazione diretta di questo dopo aver profilato un gran numero di funzioni API OpenCV, a partire dalla versione 2.4.9.
- Molte altre librerie di elaborazione delle immagini che ho profilato, inoltre, non fanno un uso pesante di SIMD, o mancano i veri hotspot.
- Le biblioteche commerciali sembrano evitare del tutto il SIMD.
- In alcuni casi, ho persino visto le librerie di elaborazione delle immagini che ripristinavano il codice ottimizzato SIMD in una versione precedente in codice non SIMD in una versione successiva, causando gravi regressioni delle prestazioni.
(La risposta del fornitore è che era necessario evitare i bug del compilatore.)
- In alcuni casi, ho persino visto le librerie di elaborazione delle immagini che ripristinavano il codice ottimizzato SIMD in una versione precedente in codice non SIMD in una versione successiva, causando gravi regressioni delle prestazioni.
- Le librerie open source sembrano tiepide per SIMD.
La domanda di questo programmatore: Il codice a bassa latenza a volte deve essere "brutto"? è correlato e in precedenza ho scritto una risposta a questa domanda per spiegare i miei punti di vista alcuni anni fa.
Tuttavia, tale risposta è praticamente "appagamento" dal punto di vista dell '"ottimizzazione prematura", vale a dire dal punto di vista che:
- Tutte le ottimizzazioni sono premature per definizione (o, a breve termine per natura ) e
- L'unica ottimizzazione che ha vantaggi a lungo termine è verso la semplicità.
Ma tali punti di vista sono contestati in questo articolo ACM .
Tutto ciò mi porta a chiedere: il
codice SIMD è diverso dal codice generale dell'applicazione e vorrei sapere se esiste un consenso del settore simile per quanto riguarda il valore del codice pulito e semplice per il codice SIMD.