Avevo già messo in cantiere un bel post sugli SSE Intrinsics con un bel codice di esempio per mostrare il vantaggio di usarli. Sfortunatamente non è andata come speravo e questo post è una specie di confessione e dimostrazione al tempo stesso.
Confessione perchè di solito si tende solamente a portare “esempi vincenti“, dimenticando che non sempre le ricerche e gli esperimenti producono successi.
Dimostrazione perchè sapendo già in partenza di cercare il pelo nell’uovo, non trovarlo non sconvolge più di tanto.
Ma resta lo smacco del fallimento…
Contesto
Per motivi di lavoro ho necessità di accelerare la conversione dallo spazio colore YUV al “canonico” RGB. Non posso ovviamente scendere nei dettagli però posso dire che parto dal formato planare YUV420 la cui conversione in RGB24 è relativamente costosa, in termini computazionali.
L’SDK del dispositivo che produce i frame da convertire per fortuna conteneva già un codice ottimizzato per cui sulle prime mi sono “accontentato” di usarlo. Ma poi ho ricevuto la richiesta di provare a “risparmiare” cicli di CPU così ho tentato varie strade.
Scartate varie tecnologie per impossibilità hardware, mi son prima buttato su OpenMP (cfr. l’ottimo post di Contezero sull’argomento) e poi sugli SSE/SSE2 Intrinsics, “tecnologie” supportate sia da Visual C++ che dal GCC.
In questo post dirò due rapide parole sulla seconda delle due nonchè del relativo smacco…
Gli Intrinsics
Un Intrinsic è un qualcosa, tipo o funzione, fornito da un compilatore per gestire qualcosa di particolare, ad esempio una qualche funzionalità specifica di una data architettura (quindi generalmente non portabile).
Nel caso dei processori x86/x86-64 esistono ad esempio specifici intrinsics per poter utilizzare rapidamente i particolari registri SIMD a 128-bit riassunti sotto i nomi registrati di MMX, SSE, SSE2, … Tali registri, normalmente accessibili al programmatore solamente tramite assembly, vengono così resi facilmente disponibili ad alto livello, in C/C++.
Gli intrinsics SSE2 sono in soldoni operazioni ottimizzate su tipi decimali e interi a 128-bit. Inventate e vendute per l’utilizzo in ambito multimediale, se usate accuratamente, permettono di effettuare più conti per ciclo di clock rispetto alle operazioni sui registri comuni.
L’idea in pratica è impacchettare fra loro più valori e farli processare in questi registri, in un colpo solo.
Non a caso il formato YUV420 si presta molto bene perchè, per come è pensato, ragiona in termini di blocchi di 2×2 pixel per la sola componente Y che è quella che richiede il maggior numero di conti.
Da Wikipedia:
![]()
Per cui, se invece di processare un pixel Y alla volta se ne riescono a processare 4 tramite SSE/SSE2, il vantaggio dovrebbe essere evidente…
Le cose spesso non vanno come ci si aspetterebbe…
Armato di buone speranze, ho quindi ripreso il codice liscio (se vi interessa è simile a questo) il mano e mi son messo a “tradurlo” per impiegare gli Intrinsics SSE2 di Visual C++ procedendo a colpi di sostituzione.
Per essere precisi ho usato un approccio SSA-like, abbondando cioè inizialmente come numero di variabili per poter verificare la correttezza della traduzione ad ogni passo; infine ho eliminato quelle superflue.
Dopo qualche ora di lavoro (di cui svariate extra, ossia nelle pause) ho completato l’opera. Il codice risultante è estremamente compatto e, a livello teorico, fa quello per cui è stato pensato: processa blocchi di 2×2 pixel in un colpo solo.
Tutto bene finchè non l’”ho fatto girare“…
Il tempo non si può ingannare
La versione SSE-enabled dell’algoritmo impiegava sulla piattaforma di test 40 ms contro i 34 ms della versione “liscia”. Incredulo, dopo qualche istante di sbigottimento misto a rabbia, mi son messo ad analizzare il codice assembly delle due versioni e dopo un po’ ho capito…
Per quanto io abbia effettivamente ottimizzato il codice di conversioni dei pixel il compilatore era stato in grado di ottimizzare meglio ogni cosa sul codice “liscio”, comprese le “scritture sequenziali” sull’array-frame RGB di destinazione.
Questo perchè, per ovvi motivi, il compilatore non ritocca il codice che l’utente ha ottimizzato con gli instrinsics. E ha ragione!
Incassare lo smacco
Mi sono imposto di non proseguire oltre e accontentarmi del codice “liscio”. Questo perchè è un attimo lasciarsi prendere dall’isteria e dalla rabbia del fallimento.
La risposta iniziale in questi casi consiste nel negare l’accaduto, poi ad accusare il “resto del mondo” (“è il compilatore che fa pena!”, “è l’algoritmo di base che non è così efficiente”, ecc…) e infine al furore isterico dell’accettazione.
Per una volta ho deciso di risparmiarmi queste futili corollari del fallimento e relative ulteriori perdite di tempo:
“Mi sono concesso un tentativo e non è andata come speravo. Pazienza! Ora sarebbe stupido e controproducente dedicare ulteriore tempo a questa impresa. Sì, posso farlo per sfizio personale, nel mio tempo libero: di certo non più a lavoro.”
La parte dura è il sapere di non essere lontani dalla vittoria e mollare la presa per non farsi trascinare dal vortice dell’orgoglio.
Posso dire, senza timore di smentita, che qualche anno fa, mi sarei fissato su quell’obiettivo e mi sarei disperato pur di ottenere l’agognato successo. Ma ora non è più così: ho imparato ad incassare gli smacchi – presunti e reali – e farmeli scivolare dietro le spalle.
In primis perchè molto spesso gli smacchi sono solo scommesse perse con noi stessi e nessun altro: che senso ha farsi del male se tanto la cosa non interessa nessuno al di fuori di noi?
In secondo luogo perchè reagire allo smacco non è sempre la scelta migliore, specie se se ne vuole fare solamente una stupida questione di principio e di orgoglio personale. In altre parole si perde completamente il fine ultimo dell’impresa e lo si sostituisce con quello di lavare l’onta.
Infine, cosa molto più comune di quanto si creda, reagire d’impeto ad uno smacco è il preludio ad una sconfitta totale ed ancor più dolorosa. Caricare a testa bassa è pura follia.
Molto meglio accontentarsi e, per quanto doloroso, accettare di aver perso. Ammetterlo è il secondo passo verso la maturazione, anche professionale.
Questo non toglie che non si possa ritentare, ma a mente serena e senza appunto lasciarsi ossessionare (e ottenebrare la mente) dal “successo”.
Che ne pensate?
Hola!
in alcune occasioni la cosa migliore è veramente gettare la spugna, magari non in modo definitivo, ma continuare a caricare a testa bassa è inutile
Beh, ributtaci un occhio perche’ il codice che hai linkato e’ brutto da far paura.
Ad esempio la mania di fare aritmetica sui puntatori quando basta una semplice indicizzazione crea un sacco di confusione per una possibile ottimizzazione parallela (anche se facendo i conti in seriale si risparmiano due addizioni per ciclo interno, che andrebbero ad incrementare i puntatori di riferimento).
A volte partire da una base piu’ pulita aiuta…
@Joel: esattamente. Solo che non sempre è così facile…
@NeXuS: sì, non è quello che uso. A dire il vero ne ho presi e “convertiti” due distinti, ma i risultati son stati i medesimi. Non ho rinunciato a migliorarli, ma non intendo perderci il sonno…
Beh, fai presto a parlare di fallimento.
A meno che non ci siano risultati prevedibili ‘sulla carta’, il processo di ottimizzazione di un codice può portare spesso a vicoli ciechi e sorprese negative. Quello che hai fatto è stato mettere in pratica una teoria senza essere certo del risultato (occorreva conoscere in dettaglio tutte le possibili ottimizzazioni del compilatore per prevedere la differenza di prestazioni).
Non hai fallito: hai fatto un’esplorazione in una direzione e hai ottenuto un risultato negativo.
Forse quella non è la direzione da prendere.
L’importante non è arrivarci per forza. L’importante è capire dove sta il problema, avere sicurezza sull’esattezza dei risultati e avere la capacità di comprendere quanto ‘spendere’ in una direzione prima che il tentativo sia troppo costoso rispetto al risultato.
Buon lavoro !
@Stefano: vedi, fallimenti e successi sono punti di vista. Ho imparato cose nuove nonostante non mi siano state d’aiuto stavolta e in senso pratico. Ho guadagnato oltre l’insuccesso “esteriore“. Impara l’arte e mettila da parte…
“Se la scienza ci insegna qualcosa, ci insegna ad accettare i nostri fallimenti, come i nostri successi, con calma, dignità e classe…”
@Cavok: citazione geniale, corretta e perfettamente in tema da uno dei miei film demenziali preferiti. Grazie!
jp… è perché siamo lontani, altrimenti avremmo usato il mio sistema per venirne a capo: tu mi spiegavi per filo e per segno il codice, io facevo finta di seguirti mugugnando ogni tanto… e tu avresti sistemato tutto al meglio
@contezero74: LOL! Alle volte, mi servirebbe. Mi registri i tuoi mugugni propiziatori in un MP3?
@Cavok: Cazzarola che citazione. Davvero, quando l’ho letta ci ho messo un attimo, ma poi mi sono rotolato in terra per mezz’ora!
@Contezero: Chissa’ perche’ mi ricorda tanto il metodo che usava lo GnattaMao con me…
@Nexus: si probabile
direi che funziona (quasi) sempre… l’ho usata con successo anche in ST… e con mia moglie (per lavoro, of course)
@NeXuS, jp: lo sapevo che non vi avrei colto impreparati, avrei dovuto citarla in lingua originale – sempre con l’aiuto del web, ovviamente
@cavok: la parte più divertente viene dopo, la frase successiva…