28.5 C versus Asamblare

DSP-urile sunt programate în aceleași limbaje ca și alte aplicații științifice și inginerești, de obicei, de asamblare sau C. Programele scrise în asamblare se pot executa mai rapid, în timp ce programele scrise în C sunt mai ușor de dezvoltat și menținut. În aplicațiile tradiționale, cum ar fi programele care rulează pe computere personale și mainframe, C este aproape întotdeauna prima alegere. Dacă asamblarea este utilizată la toate, este limitată la subrutine scurte care trebuie să ruleze cu viteza maximă. Acest lucru este arătat grafic în fig. 28-9a; pentru fiecare programator tradițional care lucrează în asamblare, există aproximativ zece care folosesc C.

Dar, programele DSP diferă de sarcinile software tradiționale în două aspecte importante. În primul rând, programele sunt de obicei mult mai scurte, să zicem, o sută de linii față de zece mii de linii. În al doilea rând, viteza de execuție este adesea o parte critică a aplicației. La urma urmei, de aceea cineva folosește un DSP în primul rând pentru viteza sa orbitoare. Acești doi factori motivează mulți ingineri software să treacă de la C la asamblare pentru a programa procesoare de semnal digital. Acest lucru este ilustrat în (b); aproape la fel de mulți programatori DSP folosesc asamblarea ca utilizarea lui C.

Figura (c) ia acest lucru în continuare prin analizarea veniturilor produse de produsele DSP. Pentru fiecare dolar realizat cu un DSP programat în C, se fac doi dolari cu un DSP programat în asamblare. Motivul pentru aceasta este simplu; banii se fac prin depășirea concurenței. Din punct de vedere pur de performanță, ca viteza de execuție și costul de producție, asamblarea are aproape întotdeauna avantaj față de C. De exemplu, codul C, uzual, necesită o memorie mai mare decât asamblarea, rezultând hardware mai scump. Dar, piața DSP este în continuă schimbare. Odată cu creșterea pieței, producătorii vor răspunde proiectând DSP-uri optimizate pentru programarea în C. De exemplu, C este mult mai eficient când există un set larg de registre de uz general și un spațiu de memorie unificat. Aceste îmbunătățiri viitoare vor micșora diferența în timpul de execuție dintre C și asamblare și vor permite ca C să fie utilizat în mai multe aplicații.

Figura 28-9 Programarea în C versus Asamblare.

Cum se arată în (a), numai în jur de 10% de programatori tradiționali (aceia care lucrează pe computere personale sau mainframe-uri) utilizează asamblarea. Totuși, cum se arată în (b), asamblarea este mult mai comună în procesoarele DSP. Aceasta deoarece programele DSP trebuie operate cât de rapid posibil, și sunt uzual destul de scurte. Figura (c) arată că asamblarea este chiar mai comună în produsele care generează un profit înalt.

Pentru a înțelege mai bine această decizie între C și asamblare, să analizăm o sarcină tipică DSP programată în fiecare limbaj. Exemplul pe care îl vom folosi este calculul produsului punct al celor două șiruri, x[ ] și y[ ]. Aceasta este o operație matematică simplă, înmulțim fiecare coeficient într-un șir cu coeficientul corespunzător în celălalt șir și suma produselor, adică x[0] × y[0] + x[1] × y[1] + x[2] × y[2] + .... Acest lucru ar trebui să pară foarte familiar; este operația fundamentală într-un filtru FIR. Adică, fiecare eșantion din semnalul de ieșire se găsește prin înmulțirea eșantioanelor stocate din semnalul de intrare (într-un singur șir) cu coeficienții filtrului (în celălalt șir) și prin însumarea produselor.

Tabelul 28-2 arată cum se calculează produsul punct într-un program C. În liniile 001-004 definim cele două șiruri, x [ ] și y[ ], pentru a avea 20 de elemente lungime. De asemenea, definim rezultatul, variabila care păstrează produsul calculat la finalizarea programului. Linia 011 controlează cele 20 de bucle necesare pentru calcul, folosind variabila n ca numărător de buclă. Singura declarație din cadrul buclei este linia 012, care multiplică coeficienții corespunzători din cele două șiruri și adaugă produsul la variabila acumulatorului, s. (Dacă nu sunteți familiarizați cu C, instrucțiunea: s+ = x[n] * y[n] înseamnă: s = s + x[n] * y[n]). După buclă, valoarea în acumulator, s, este transferată la variabila de ieșire, result, în linia 013.

Tabelul 28-2 Produsul punct în C.

Acest program calculează produsul punct a două matrici x[ ] și y[ ], și stochează rezultatul în variabila result.

Un avantaj cheie al utilizării unui limbaj de nivel înalt (cum ar fi C, Fortran sau Basic) este că programatorul nu are nevoie să înțeleagă arhitectura microprocesorului utilizat; cunoașterea arhitecturii este lăsată la compilator. De exemplu, acest scurt program C folosește mai multe variabile: n, s, result, plus șirurile: x[ ] și y[ ]. Toate aceste variabile trebuie să fie atribuite la "home" în hardware pentru a urmări valoarea lor. În funcție de microprocesor, aceste locații de stocare pot fi registrele de date cu uz general, locațiile din memoria principală sau registrele speciale dedicate anumitor funcții. Totuși, persoana care scrie un program de nivel înalt știe puțin sau deloc despre această gestionare a memoriei; această sarcină a fost delegată inginerului software care a scris compilatorul. Problema este că acești doi oameni nu s-au întâlnit; ei comunică doar printr-un set de reguli predefinite. Limbajele de nivel înalt sunt mai ușoare decât asamblarea, deoarece dai jumătate din muncă altcuiva. Totuși, ele sunt mai puțin eficiente deoarece nu sunteți destul de siguri cum se desfășoară activitatea delegată.

În comparație, Tabelul 28-3 prezintă programul produsului punct scris în asamblare pentru SHARC DSP. Limbajul de asamblare pentru DSP-urile Analog Devices (ambele lor dispozitive SHARC 16 biți cu virgulă fixă și 32 de biți) sunt cunoscute pentru sintaxa lor simplă algebrică. Deoarece nu vom trece prin toate detaliile, aici este operațiunea generală. Observați că totul se referă la hardware; nu există variabile abstracte în acest cod, doar registrele de date și locațiile de memorie.

Fiecare punct și virgulă reprezintă un ciclu de tact. Șirurile x[ ] și y[ ] sunt ținute în buffere circulare din memoria principală. În liniile 001 și 002, registrele i4 și i12 sunt indicate ca locații de pornire ale acestor șiruri. Apoi, executăm 20 de cicluri de buclă, așa cum sunt controlate de linia 004. Formatul acestei declarații profită de capacitatea zero-overhead looping a DSP-lui SHARC. Cu alte cuvinte, toate variabilele necesare pentru a controla bucla sunt păstrate în registre hardware dedicate care operează în paralel cu alte operații care se desfășoară în interiorul microprocesorului. În acest caz, registrul: lcntr (contorul buclei) este încărcat cu o valoare inițială de 20, și decrementează de fiecare dată când se execută bucla. Bucla este terminată când lcntr atinge o valoare de zero (indicată de declarația: lce, pentru "contor de buclă expirat"). Bucla cuprinde liniile 004 până la 008, așa cum este controlată de declarația (pc, 4). Adică, bucla se termină cu patru rânduri după contorul de program curent.

Tabelul 28-3 Produsul punct în Asamblare (neoptimizat).

Acest program calculează produsul punct al două matrici x[ ] și y[ ], și stochează rezultatul în variabila result. Acesta este codul de asamblare pentru Analog Devices SHARC DSP. Vezi textul pentru detalii.

Tabelul 28-4 Produsul punct în Asamblare (optimizat).

Aceasta este o versiune optimizată a programului din tabelul 28-2, destinat pentru a avantaja arhitectura paralelă a SHARC.

În interiorul buclei, linia 005 încarcă valoarea din x[ ] în registrul de date f2, în timp ce linia 006 încarcă valoarea de la y[ ] în registrul de date f4. Simbolurile "dm" și "pm" indică faptul că valorile sunt preluate prin magistrala "memorie de date" și, respectiv, "memoria program". Variabilele: i4, m6, i12 și m14 sunt registre în generatoarele de adrese de date care gestionează buffer-ele circulare care dețin x[ ] și y[ ]. Cele două valori din f2 și f4 sunt înmulțite în linia 007, iar produsul este stocat în registrul de date f8. În linia 008, produsul din f8 este adăugat la acumulator, registrul de date f12. După terminarea buclei, acumulatorul din f12 este transferat în memorie.

Acest program calculează corect produsul dot, dar nu profită de arhitectura SHARC paralelă. Tabelul 28-4 arată acest program rescris într-o formă foarte optimizată, multe operații fiind efectuate în paralel. Mai întâi, observați că linia 007 execută numai 18 bucle, mai degrabă decât 20. Rețineți că această buclă conține numai o singură linie (008), dar că această linie conține mai multe instrucțiuni. Strategia este de a face bucla cât mai eficientă posibil, în acest caz, o singură linie care poate fi executată într-un singur ciclu de tact. Pentru a face acest lucru, trebuie să avem o cantitate mică de cod care să "amorseze" registrele pe prima buclă (liniile 004 și 005) și o altă secțiune mică de cod pentru a termina ultima buclă (linii 010 și 011).

Pentru a înțelege cum lucrează aceasta, studiem linia 008, singura declarație din interiorul buclei. În această singură declarație, patru operații sunt efectuate în paralel: (1) valoarea pentru x[ ] este mutată dintr-un buffer circular în memoria programului și plasată în f2; (2) valoarea pentru y[ ] este mutată dintr-un tampon circular în memoria de date și plasată în f4; (3) valorile anterioare ale f2 și f4 sunt înmulțite și plasate în f8; și (4) valoarea anterioară în f8 este adăugată la acumulator în f12.

De exemplu, a cincea oară când linia 008 este executată, x[7] și y[7] sunt extrase din memorie și stocate în f2 și f4. În același timp, valorile pentru x[6] și y[6] (care au fost în f2 și f4 la începutul acestui ciclu) sunt multiplicate și plasate în f8. În plus, valoarea lui x[5] × y[5] (care a fost în f8 la începutul acestui ciclu) se adaugă la valoarea lui f12.

Să comparăm numărul de cicluri de tact cerute de programele neoptimizat și optimizat. Rețineți că există 20 de bucle, fiind necesare patru acțiuni în fiecare buclă. Programul neoptimizat necesită 80 de cicluri de tact pentru a efectua acțiunile din interiorul buclelor, plus 5 cicluri de tact de overhead, un total de 85 cicluri de tact. În comparație, programul optimizat efectuează 18 bucle în 18 cicluri de tact, dar necesită 11 cicluri de tact de overhead pentru a amorsa registrele și a finaliza ultima buclă. Acest lucru are ca rezultat un timp total de execuție de 29 de cicluri de tact, sau aproximativ trei ori mai rapid decât metoda forței brute.

Aici este marea întrebare: Cât de repede se execută programul C în raport cu codul de asamblare? Când se compilează programul din Tabelul 28-2, codul executabil se compară cu exemplul de asamblare eficient sau ineficient Răspunsul este că compilatorul generează codul eficient. Dar, este important să realizăm că produsul dot este un exemplu foarte simplu. Compilatorul are un timp mult mai dificil de a produce cod optimizat atunci când programul devine mai complicat, cum ar fi bucle multiple imbricate și salturi dezordonate în subrutine. Dacă faci ceva simplu, aștepți ca compilatorul să vă ofere o soluție aproape optimă. Dacă faceți ceva ciudat sau complicat, așteptați ca un program de asamblare să se execute semnificativ mai repede decât unul scris în C. În cel mai rău caz, gândiți-vă la un factor de 2-3. După cum s-a menționat anterior, eficiența lui C față de asamblare depinde în mare măsură de utilizarea DSP particulare. Arhitecturile cu virgulă mobilă pot fi programate, în general, mai eficient decât dispozitivele cu virgulă fixă atunci când se folosesc limbaje de nivel înalt, cum ar fi C.

Figura 28-10 Asamblare versus C.

Programele în C sunt mai flexibile și mai rapid de dezvoltat. În comparație, programele în Asamblare au adesea performanțe mai bune; ele rulează mai repede și utilizează mai puțină memorie, rezultând un cost mai scăzut.

Există, de asemenea, o modalitate de a obține cele mai bune din ambele lumi: scrieți programul în C, dar folosiți asamblare pentru secțiunile critice care trebuie executate rapid. Acesta este un motiv pentru care C este atât de popular în știință și inginerie. El operează ca un limbaj de nivel înalt, dar vă permite și să manipulați direct hardware-ul dacă doriți. Chiar dacă intenționați să programați numai în C, probabil că veți avea nevoie de câteva cunoștințe despre arhitectura DSP și setul de instrucțiuni de asamblare. De exemplu, uită-te înapoi la liniile 002 și 003 din Tabelul 28-2, programul de produs dot în C. "dm" înseamnă că x[ ] va fi stocat în memoria de date, în timp ce "pm" indică faptul că y[ ] va locui în memoria programului. Chiar dacă programul este scris într-un limbaj de nivel înalt, este necesară o cunoaștere de bază a hardware-ului pentru a obține cea mai bună performanță de la dispozitiv.

Ce limbaj este cel mai bun pentru aplicația dvs.? Depinde de ce este mai important pentru tine. Dacă aveți nevoie de flexibilitate și dezvoltare rapidă, alegeți C. Pe de altă parte, utilizați asamblare dacă aveți nevoie de cea mai bună performanță posibilă. Așa cum este ilustrat în figura 28-10, acesta este un compromis pe care sunteți obligat să îl faceți. Iată câteva lucruri pe care ar trebui să le luați în considerare.

    • Cât de complicat este programul? Dacă este mare și complicat, probabil că veți dori să utilizați C. Dacă este mic și simplu, asamblarea poate fi o alegere bună.

    • Împingeți la viteza maximă a DSP-ului? Dacă da, asamblarea vă va oferi ultima picătură de performanță de la dispozitiv. Pentru aplicații mai puțin exigente, asamblarea are puține avantaje și ar trebui să luați în considerare utilizarea lui C.

    • Câți programatori vor lucra împreună? Dacă proiectul este suficient de mare pentru mai mult de un programator, orientați-vă spre C și utilizați asamblarea in-line numai pentru segmentele critice de timp.

    • Ce este mai important, costul produsului sau costul de dezvoltare? Dacă este costul produsului, alegeți asamblarea; dacă este costul de dezvoltare, alegeți C.

    • Care este experiența ta? Dacă aveți experiență în asamblare (pe alte microprocesoare), alegeți asamblare pentru DSP. Dacă ați lucrat anterior în C, alegeți C pentru DSP.

    • Ce sugerează producătorul DSP să folosiți?

Acest ultim element este foarte important. Să presupunem că cereți unui producător DSP ce limbaj să utilizezi și vă spun: "Fie C, fie asamblare poate fi folosit, dar recomandăm C." Ar fi bine să asculți sfatul! Ceea ce spun ei cu adevarat este: "DSP-ul nostru este atât de dificil de programat in asamblare, încât veți avea nevoie de 6 luni de antrenament pentru a-l folosi." Pe de altă parte, unele DSP-uri sunt ușor de programat în asamblare. De exemplu, produsele Analog Devices se află în această categorie. Doar întrebați inginerii lor; ei sunt foarte mândri de acest lucru.

Una dintre cele mai bune modalități de a lua decizii cu privire la produsele DSP și software este de a vorbi cu inginerii care le-au folosit. Cereți producătorilor referințe de companii care utilizează produsele lor sau căutați pe web pentru persoane la care puteți trimite e-mail. Nu fiţi timizi; inginerii iubesc să-și dea opiniile asupra produselor pe care le-au folosit. Vor fi flatați că ați întrebat.

Secțiunea următoare: Cât de rapide sunt DSP-urile?