Costi di manutenzione della base di codici di programmazione SIMD


14

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 vtblcon vtblq) 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.)

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.


2
Hanno requisiti di prestazione? Riesci a soddisfare i requisiti di prestazione senza utilizzare SIMD? In caso contrario, la domanda è discutibile.
Charles E. Grant,

4
Questo è troppo lungo per una domanda, molto probabilmente perché una buona parte di essa è effettivamente un tentativo di rispondere alla domanda, e anzi anche per una risposta (in parte perché tocca molti più aspetti rispetto alla maggior parte delle risposte ragionevoli).

3
Mi piace avere sia il codice pulito / semplice / lento (per la prova iniziale del concetto e scopi di documentazione successiva) oltre alle alternative ottimizzate. Ciò semplifica la comprensione (poiché le persone possono semplicemente leggere il codice pulito / semplice / lento) e facile da verificare (confrontando la versione ottimizzata con la versione pulita / semplice / lenta manualmente e nei test unitari)
Brendan,

2
@Brendan Sono stato in un progetto simile e ho usato un approccio test con codice semplice / lento. Sebbene sia un'opzione da considerare, ha anche dei limiti. In primo luogo, la differenza di prestazioni potrebbe risultare proibitiva: i test che utilizzano codice non ottimizzato possono essere eseguiti per ore ... giorni. In secondo luogo, per l'elaborazione delle immagini potrebbe risultare che il confronto bit per bit semplicemente non funzionerà, quando il codice ottimizzato produce risultati leggermente diversi, quindi si dovrebbe usare un confronto più sofisticato, come ef root mean square diff
gnat

2
Sto votando per chiudere questa domanda come fuori tema perché non è un problema di programmazione concettuale come descritto nel Centro assistenza .
durron597,

Risposte:


6

Non ho scritto molto codice SIMD per me stesso, ma molto codice assembler alcuni decenni fa. AFAIK che utilizza SIMD intrinsics è essenzialmente una programmazione assembler e l'intera domanda potrebbe essere riformulata semplicemente sostituendo "SIMD" con la parola "assembly". Ad esempio, i punti che hai già menzionato, come

  • lo sviluppo del codice richiede da 10x a 100x rispetto al "codice di alto livello"

  • è legato a un'architettura specifica

  • il codice non è mai "pulito" né facile da refactoring

  • hai bisogno di esperti per scriverlo e mantenerlo

  • il debug e il mantenimento sono difficili, evolvendosi davvero duramente

non sono in alcun modo "speciali" per SIMD: questi punti sono validi per qualsiasi tipo di linguaggio di assemblaggio e sono tutti "consenso del settore". E la conclusione nel settore del software è praticamente la stessa di assembler:

  • non scriverlo se non è necessario: utilizzare un linguaggio di alto livello ove possibile e lasciare che i compilatori facciano il duro lavoro

  • se i compilatori non sono sufficienti, almeno incapsulare le parti di "basso livello" in alcune librerie, ma evitare di diffondere il codice in tutto il programma

  • poiché è quasi impossibile scrivere un assemblatore o un codice SIMD "auto-documentante", provare a bilanciarlo con molta documentazione.

Certamente, c'è davvero una differenza nella situazione con l'assemblaggio o il codice macchina "classico": oggi i compilatori moderni tipicamente producono codice macchina di alta qualità da un linguaggio di alto livello, che spesso è meglio ottimizzato rispetto al codice assembler scritto manualmente. Per le architetture SIMD oggi popolari, la qualità dei compilatori disponibili è AFAIK molto inferiore a quella - e forse non lo raggiungerà mai, poiché la vettorializzazione automatica è ancora un argomento di ricerca scientifica. Vedi, ad esempio, questo articolo che descrive le differenze nell'opimizzazione tra un compilatore e un essere umano, dando l'idea che potrebbe essere molto difficile creare buoni compilatori SIMD.

Come hai già descritto nella tua domanda, esiste anche un problema di qualità con le librerie più recenti. Quindi l'IMHO meglio che possiamo sperare è che nei prossimi anni la qualità dei compilatori e delle librerie aumenterà, forse l'hardware SIMD dovrà cambiare per diventare più "compilatore", forse linguaggi di programmazione specializzati che supportano una più semplice vettorializzazione (come Halide, che hai menzionato due volte) diventerà più popolare (non era già un punto di forza di Fortran?). Secondo Wikipedia , SIMD è diventato "un prodotto di massa" circa 15-20 anni fa (e Halide ha meno di 3 anni, quando interpreto correttamente i documenti). Confronta questo con i compilatori di tempo per un linguaggio di assemblaggio "classico" necessario per diventare maturo. Secondo questo articolo di Wikipediaci sono voluti quasi 30 anni (dal ~ 1970 alla fine degli anni '90) prima che i compilatori superassero le prestazioni degli esperti umani (nella produzione di codice macchina non parallelo). Quindi potremmo dover aspettare altri 10-15 anni prima che lo stesso accada ai compilatori abilitati SIMD.


secondo la mia lettura dell'articolo di Wikipedia , sembra esserci un consenso generale del settore secondo cui il codice ottimizzato a basso livello è "considerato difficile da usare, a causa dei numerosi dettagli tecnici che devono essere ricordati"
moscerino del

@gnat: sì, assolutamente, ma penso che se aggiungo questo alla mia risposta, dovrei una dozzina di altre cose già menzionate dall'OP in altre parole nella sua domanda troppo lunga.
Doc Brown,

d'accordo, l'analisi nella tua risposta sembra abbastanza buona così com'è, aggiungendo che il riferimento avrebbe il rischio di "sovraccaricarlo"
moscerino

4

La mia organizzazione ha affrontato questo esatto problema. I nostri prodotti si trovano nello spazio video, ma gran parte del codice che scriviamo è l'elaborazione delle immagini che funzionerebbe anche per le immagini fisse.

Abbiamo "risolto" (o forse "risolto") il problema scrivendo il nostro compilatore. Questo non è così folle come sembra all'inizio. Ha un set limitato di input. Sappiamo che tutto il codice funziona su immagini, principalmente immagini RGBA. Abbiamo impostato alcuni vincoli, in modo che i buffer di input e output non possano mai sovrapporsi, quindi non c'è alias di puntatore. Cose del genere.

Quindi scriviamo il nostro codice in OpenGL Shading Language (glsl). Viene compilato in codice scalare, SSE, SSE2, SSE3, AVX, Neon e ovviamente glsl reale. Quando abbiamo bisogno di supportare una nuova piattaforma, aggiorniamo il compilatore al codice di output per quella piattaforma.

Facciamo anche la piastrellatura delle immagini per migliorare la coerenza della cache e cose del genere. Ma mantenendo l'elaborazione delle immagini su un piccolo kernel e usando glsl (che non supporta nemmeno i puntatori) riduciamo notevolmente la complessità della compilazione del codice.

Questo approccio non è per tutti e ha i suoi problemi (è necessario garantire la correttezza del compilatore, ad esempio). Ma ha funzionato abbastanza bene per noi.


Sembra 🔥🔥! Questo prodotto viene venduto o reso disponibile autonomo? (Inoltre, 'AVC' = AVX?)
Ahmed Fasih,

Scusa, sì, intendevo AVX (lo aggiusterò.). Al momento non vendiamo il compilatore come prodotto autonomo, anche se potrebbe accadere in futuro.
user1118321

Non è uno scherzo, sembra davvero pulito. La cosa più simile che ho visto in questo modo è il modo in cui il compilatore CUDA era in grado di creare programmi "seriali" eseguiti su CPU per il debug - speravamo che si sarebbe generalizzato in un modo per scrivere codice CPU multi-thread e SIMD, ma ahimè. La prossima cosa a cui riesco a pensare è OpenCL: avete valutato OpenCL e lo avete trovato inferiore al vostro compilatore GLSL-to-all?
Ahmed Fasih,

1
Beh, OpenCL non esisteva quando abbiamo iniziato, non credo. (O se lo facesse, era abbastanza nuovo.) Quindi non è entrato nell'equazione.
user1118321

0

Non sembra aggiungere troppi costi di manutenzione se si considera l'utilizzo di un linguaggio di livello superiore:

Vector<float> values = GetValues();
Vector<float> increment = GetIncrement();

// Perform addition as a vector operation:
List<float> result = (values + increment).ToList();

vs

List<float> values = GetValues();
List<float> increment = GetIncrement();

// Perform addition as a monadic sequence operation:
List<float> result = values.Zip(increment, (v, i) => v + i).ToList();

Ovviamente dovrai affrontare i limiti della biblioteca, ma non la manterrai da solo. Potrebbe essere un buon equilibrio tra costi di manutenzione e prestazioni vincenti.

http://blogs.msdn.com/b/dotnet/archive/2014/04/07/the-jit-finally-proposed-jit-and-simd-are-getting-married.aspx

http://blogs.msdn.com/b/dotnet/archive/2014/05/13/update-to-simd-support.aspx


secondo la mia lettura, l'opzione per l'uso di librerie esterne è già stata studiata e affrontata da Asker: "Le librerie con un uso commerciale diffuso non sembrano essere fortemente abilitate al SIMD ..."
Gnat

@gnat In realtà ho letto l'intero paragrafo, non solo i punti elenco di primo livello, e il poster non menziona le librerie SIMD di uso generale, ma solo quelle di visione artificiale e di elaborazione delle immagini. Per non parlare del fatto che l'analisi delle applicazioni linguistiche di livello superiore è completamente mancante, nonostante nessun tag C ++ e nessuna specificità C ++ riflessa nel titolo della domanda. Questo mi porta a credere che mentre la mia domanda non sarà considerata primaria, è probabile che aggiunga valore, rendendo le persone consapevoli di altre opzioni.
Den,

1
A mio avviso, l'OP chiede se esistono soluzioni con un uso commerciale diffuso. Anche se apprezzo il tuo suggerimento (forse posso usare la lib per un progetto qui), da quello che vedo RyuJIT è ben lungi dall'essere uno "standard del settore ampiamente accettato".
Doc Brown,

@DocBrown forse, ma la sua vera domanda è formulata per essere più generica: "... consenso del settore riguardo al valore del codice pulito e semplice per il codice SIMD ...". Dubito che ci sia un consenso (ufficiale), ma sostengo che linguaggi di livello superiore possono ridurre la differenza tra il "solito" e il codice SIMD, proprio come C ++ dimentichiamoci dell'assemblaggio, riducendo così i costi di manutenzione.
Den,

-1

Ho svolto la programmazione di assemblaggio in passato, non di recente la programmazione SIMD.

Hai mai pensato di utilizzare un compilatore compatibile con SIMD come quello di Intel? È interessante una guida alla vettorializzazione con compilatori C ++ Intel® ?

Molti dei tuoi commenti come "balloon-popping" suggeriscono di utilizzare un compilatore (per ottenere vantaggi in tutto se non si dispone di un unico hot-spot).


secondo la mia lettura, questo approccio è stato provato da Asker, vedi menzioni di bug / difetti del compilatore nella domanda
moscerino

L'OP non ha detto se avevano provato il compilatore Intel , che è anche l'oggetto di questo argomento Programmers.SE . Molte persone non l'hanno provato. Non è per tutti; ma potrebbe adattarsi all'attività / alla domanda del PO (prestazioni migliori per costi di codifica / progettazione / manutenzione inferiori).
ChrisW,

bene quello che ho letto nella domanda suggerisce che Asker è a conoscenza dei compilatori per Intel e altre architetture: "Alcune architetture mantengono una perfetta compatibilità con le versioni precedenti (Intel); altre non sono
moscerino del

"Intel" in quella frase indica il progettista di chip Intel, non Intel-il compilatore-compilatore.
ChrisW,
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.