Documente Academic
Documente Profesional
Documente Cultură
Arhitecturi Paralele - Curs
Arhitecturi Paralele - Curs
O idee interesantă bazată pe descentralizarea resurselor are în vedere implementarea mai multor aşa numite "Instruction Windows"
(IW) - un fel de buffer-e de prefetch şi staţii de rezervare multiple. Lansarea în execuţie a instrucţiunilor se face pe baza determinării
celor independente din fiecare IW. Desigur că trebuie determinate şi dependenţele inter - IW- uri. Ideea principală constă în execuţia
paralelă a mai multor secvenţe de program aflate în IW - uri diferite, bazat pe mai multe unităţi funcţionale (multithreading). În esenţă
un procesor cu execuţii multiple ale instrucţiunilor este compus din două mecanisme decuplate: mecanismul de aducere (fetch) a
instrucţiunilor, pe post de producător şi respectiv mecanismul de execuţie a instrucţiunilor, pe post de consumator. Separarea între
cele două mecanisme (arhitectură decuplată) se face prin buffer-ele de instrucţiuni şi staţiile de rezervare, ca în figura 1.2.
Instrucţiunile de ramificaţie şi predictoarele hardware aferente acţionează printr-un mecanism de reacţie între consumator şi
producător.Astfel, în cazul unei predicţii eronate, buffer-ul de prefetch trebuie să fie golit măcar parţial iar adresa de acces la cache-ul
de instrucţiuni trebuie şi ea modificată în concordanţă cu adresa la care se face saltul.
1
Pe baze statistice se arată că un basic-block conţine, pe programele de uz general, doar 4-6 instrucţiuni în medie, ceea ce înseamnă că
rata de fetch a instrucţiunilor este limitată la cca. 6, aducerea simultană a mai multor instrucţiuni fiind inutilă (fetch bottleneck sau
Flynn’s bottleneck ).
Desigur, această limitare fundamentală ar avea consecinţe defavorabile şi asupra consumatorului,care ar limita principial şi rata medie
de execuţie a instrucţiunilor (IR - Issue Rate) la această valoare. Progresele semnificative în algoritmii de lansare în execuţie impun
însă depăşirea acestei bariere. În acest sens, cercetările actuale insistă pe îmbunătăţirea mecanismelor de aducere a instrucţiunilor prin
următoarele tehnici:
• predicţia simultană a mai multor ramificaţii / tact rezultând deci rate IR sporite
• posibilitatea accesării şi aducerii simultane a mai multor basic- block-uri din cache, chiar dacă acestea sunt nealiniate, prin utilizarea
unor cache-uri multiport
• păstrarea unei latenţe reduse a procesului de aducere a instrucţiunilor, în contradicţie cu cele 2 cerinţe anterioare
Alţi factori care determină limitarea ratei de fetch a instrucţiunilor (FR- Fetch Rate) sunt:
lărgimea de bandă limitată a interfeţei procesor - cache, miss-urile în cache, predicţiile eronate ale
ramificaţiilor etc.
În contextul următoarei generaţii arhitecturale de microprocesoare de înaltă performanţă, cea de a 4-a, se întrevede de asemenea
implementarea unor mecanisme de aducere de tip out of order a instrucţiunilor, în plus faţă de cele deja existente în execuţia
instrucţiunilor. Aceste instrucţiuni fiind independente de condiţia de salt, pot fi chiar lansate în execuţie. Când predicţia se va fi
realizat sau pur şi simplu când adresa destinaţie a ramificaţiei va fi cunoscută, procesorul va relua aducerea instrucţiunilor de la adresa
destinaţie a ramificaţiei. Aşadar o rafinare a conceptelor de execuţie speculativă a instrucţiunilor maşină, bazată pe predicţie sau pe
concepte avansate de tip multiflow.
Evident, execuţia instrucţiunilor se va face out of order, pe baza dezvoltării unor algoritmi de tip Tomasulo, mult mai agresivi însă.
Staţiile de rezervare aferente unităţilor de execuţie, vor trebui să aibă capacităţi de peste 2000 de instrucţiuni.
Pentru a evita falsele dependenţe de date (WAR – Write After Read, WAW – Write After Write),procesoarele vor avea mecanisme de
redenumire dinamică a regiştrilor logici. Desigur, tehnicile de scheduling static vor trebui îmbunătăţite radical pentru a putea oferi
acestor structuri hardware complexe suficient paralelism Se estimează atingerea unor rate medii de procesare de 12-14 instr. /tact,
considerând că se pot lansa în execuţie maximum 32 instr. / tact. La ora actuală, cele mai avansate procesoare, cu un potenţial teoretic
de 6 instr. / tact, ating în realitate doar 1.2-2.3 instr. /tact .Aceste rate mari de procesare, impun execuţia paralelă a cca. 8 instrucţiuni
Load/ Store.
Aceasta implică un cache de date primar de tip multiport şi unul secundar, de capacitate mai mare dar cu porturi mai puţine. Miss-
urile pe primul nivel, vor accesa cel de al 2-lea nivel. Pentru a nu afecta perioada de tact a procesorului, este posibil ca memoria cache
din primul nivel să fie multiplicată fizic în vederea accesărilor paralele. Aşadar paralelismul la nivel de instrucţiuni (ILP - Instruction
Level Parallelism) va fi înlocuit cu unul mai masiv, constituit la nivelul thread-urilor unei aplicaţii (TLP - Thread Level Parallelism).
În acest scop, arhitectura va trebui să conţină mai multe unităţi de procesare a trace-urilor, interconectate. La aceasta, se adaugă o
unitate de control "high-level", în vederea partiţionării programului în thread-uri de instrucţiuni independente. Se poate ajunge astfel
la o rată de procesare de mai multe trace-uri / tact faţă de instrucţiuni / tact, metrica de performanţă obişnuită a procesoarelor
superscalare actuale. E posibil ca aceste TLP-uri să acopere "semanticgap"- ul existent între paralelismul la nivel de instrucţiuni şi
respectiv cel situat la nivelul programelor, mult mai masiv .
1.1 Probleme actuale în predicţia branch-urilor
Domeniul predicţiei branch-urilor în arhitecturile superscalare este unul deosebit de necesar, mai ales din punct de vedere al execuţiei
speculative şi paralele a instrucţiunilor aparţinând mai multor basic-block-uri. Saltul se predicţionează în general pe durata fazei de
aducere a instrucţiunilor, permiţând astfel structurilor pipeline de procesare a instrucţiunilor să-şi continue activităţile fără aşteptări.
Fără predictoare de ramificaţii performanţa microprocesoarelor superscalare ar scădea dramatic având în vedere că practic la 1-2 cicli
de execuţie apare câte un branch. Solutia de tip BTB - Branch Target Buffers a apărut în 1984 (implementată la Intel Pentium
I abia în 1995) şi a ajuns la apogeu din punct de vedere al performanţelor comunicate în 1993,obţinând acurateţi ale predicţiilor de
cca. 93% pe benchmark-urile SPEC int . În literatura de specialitate recentă se propune o structură de predicţie numită "target cache"
special dedicată salturilor indirecte prin registru. În acest caz predicţia adresei de salt nu se mai face pe baza ultimei adrese ţintă a
saltului indirect, ca în schemele de predicţie clasice, ci pe baza alegerii uneia dintre ultimele adrese ţintă ale respectivului salt,
memorate în structură. Aşadar, în acest caz structura de predicţie, memorează pe parcursul execuţiei programului pentru fiecare salt
indirect ultimele N adrese ţintă. Predicţia se va face deci în acest caz pe baza urmatoarelor informaţii: PC-ul saltului, istoria acestuia,
precum şi ultimele N adrese ţintă înregistrate în structura cache de predicţie. O linie din acest cache conţine ultimele N adrese ţintă ale
saltului împreună cu tag-ul aferent, semnificând un context (comprimat) de activare a unei anumite adrese ţintă. Aceste două/trei surse
de informaţie binară sunt în general prelucrate prin intermediul unei funcţii de dispersie (XOR), rezultând indexul de adresare în
cache şi tag-ul (contextul) de verificare aferent. După ce adresa ţintă a saltului devine efectiv cunoscută, se va introduce în linia
corespunzătoare din cache împreună cu tag-ul aferent, reprezentând de fapt contextul de apariţie al acelui salt indirect. Principiul
predicţiei este în acest caz simplu: la acelaşi pattern de context, aceeaşi adresă ţintă. Prin astfel de scheme, măsurat pe benchmark-
urile SPEC int'95, acurateţea predicţiei salturilor indirecte creşte şi ca urmare, câstigul global asupra timpului de execuţie este de cca
4.3% - 9%. Problema salturilor indirecte este de mare actualitate, cu precădere în contextul programelor obiectuale, legat mai ales de
implementarea polimorfismelor. În acest caz, adresele de început ale diferitelor obiecte vizate sunt înscrise dinamic în registrul de
indirectare al saltului care implementează polimorfismul. Practic, aici problema predicţiei direcţiei saltului este înlocuită cu una mult
mai dificilă, anume cu aceea a predicţiei valorii adresei acestuia.
Tot în cadrul problematicii predicţiei ramificaţiilor de program, se menţionează faptul că autorul acestei lucrări a propus pentru prima
dată o idee complet nouă, constând într-un predictor neural (PN), destinat predicţiei dinamice a branch-urilor, bazat pe un anumit tip
de reţea neurală (LVQ- Learning Vector Quantization, MLP – Multilayer Perceptron cu algoritm de învăţare de tip backpropagation
etc.) [Vin99, Gal93]. Ulterior, ideea a fost preluată şi dezvoltată şi de alţi cercetători (spre ex. Daniel Jimenez de la Universitatea
Austin, Texas). Această abordare face o legatură, aparent surprinzătoare, între domeniul procesoarelor avansate şi cel al recunoaşterii
formelor utilizând reţele neurale. Ulterior am dezvoltat predictorul neural de branch-uri în sensul înglobării sale în compilator, prin
urmare propunând un predictor static de ramificaţii.
Scopul final constă aici într-o "pre-predicţie" realizată prin compilator, care eventual să ajute mai apoi procesele de predicţie dinamică
(run-time). Fireşte că utilitatea unui asemenea predictor static este una deosebită având în vedere dezideratul optimizării globale a
programelor, unde se doreşte să se determine trace-urile de program cele mai mari consumatoare de timp, pentru a fi optimizate cu
deosebire.
PN se bazează în esenţă, în prima sa abordare, pe utilizarea unei reţele neuronale (RN) de tip feedforward cu un strat ascuns, pe post
de predictor global al tuturor salturilor din program. În RN de predicţie vor intra, ca şi în predictoarele clasice, cele 3 informaţii cvasi-
ortogonale de predicţie (PC pe i biţi, HRl pe l biţi şi HRg pe k biţi), eventual împreună cu PC-urile aferente fiecărui bit din HRg
pentru ca informaţia de predicţie să fie una mai completă .Pe baza procesării acestor informaţii binare, reţeaua va predicţiona printr-un
bit de ieşire, comportamentul viitor al saltului (taken / not taken).
Memoria TC este accesată cu adresa de început a basic-block-ului A, în paralel cu predictorul multiplu de salturi (vezi figura 1.7).
Acesta, spre deosebire de un predictor simplu, predicţionează nu doar adresa de început a următorului basic- block ce trebuie executat
ci toate cele (M-1) adrese de început aferente următoarelor (M-1) basic- block-uri care urmează după blocul A. Cei (M-1) biţi generaţi
de către predictorul multiplu (taken/ not taken) selectează spre logica de execuţie doar acele blocuri din linia TC care sunt
predicţionate că se vor executa (în cazul acesta doar blocurile A şi B întrucât predictorul a selectat că se vor executa blocurile ABD, în
timp ce în linia TC erau memorate blocurile ABC).
O linie din TC conţine în principiu:
- N instrucţiuni în formă decodificată, fiecare având specificat blocul căreia îi aparţine.
- cele 2M-1 posibile adrese destinaţie aferente celor M blocuri stocate în linia TC.
- un câmp care codifică numărul şi "direcţiile" salturilor memorate în linia TC.
Înainte de a fi memorate în TC, instrucţiunile pot fi pre-decodificate în scopul înscrierii în TC a unor informaţii legate de
dependenţele de date ce caracterizează instrucţiunile din linia TC curentă. Aceste informaţii vor facilita procese precum bypassing-ul
datelor între unităţile de execuţie, redenumirea dinamică a regiştrilor cauzatori de dependenţe WAR (Write After Read) sau WAW
(Write After Write) între instrucţiuni etc., utile în vederea procesării out of order a instrucţiunilor. O linie din TC poate avea diferite
grade de asociativitate în sensul în care ea poate conţine mai multe pattern-uri de blocuri, toate având desigur aceeaşi adresă de
început (A), ca în figura 1.8.
Aşadar, în acest caz, segmentele începând de la aceeaşi adresă (A), sunt memorate în aceeaşi linie asociativă din TC. Ca şi în
structurile TC neasociative, verificarea validităţii liniei selectate se face prin compararea (căutarea) după tag. Deosebirea de esenţă
constă în faptul că aici este necesară selectarea - în conformitate cu pattern-ul generat de către predictorul multiplu - trace-ului cel
mai lung dintre cele conţinute în linia respectivă. Este posibil ca această selecţie complexă să dureze mai mult decât în cazul
neasociativ şi prin urmare să se repercuteze negativ asupra duratei procesului de aducere a instrucţiunilor (fetch). Avantajul principal
însă, după cum se observă şi în figura 1.8,constă în faptul că este probabil să se furnizeze procesorului un număr de blocuri "mai lung"
decât un TC simplu. Astfel de exemplu, dacă pattern-ul real de blocuri executate este ABD, structura TC îl va furniza fără probleme,
în schimb o structură TC neasociativă ce conţine doar pattern-ul ABC, evident va furniza în această situaţie doar blocurile AB.
Pe măsură ce un grup de instrucţiuni este procesat, el este încărcat într-o aşa-numită "fill unit"(FU-unitate de pregătire). Rolul FU este
de a asambla instrucţiunile dinamice, pe măsură ce acestea sunt executate, într-un trace-segment. Segmentele astfel obţinute sunt
memorate în TC. După cum am mai subliniat, este posibil ca înainte de scrierea segmentului în TC, FU să analizeze instrucţiunile din
cadrul unui segment spre a marca explicit dependenţele dintre ele. Acest lucru va uşura mai apoi lansarea în execuţie a acestor
instrucţiuni întrucât ele vor fi aduse din TC şi introduse direct în staţiile de rezervare aferente unităţilor funcţionale. Unitatea FU se
ocupă deci de colectarea instrucţiunilor lansate în execuţie, asamblarea lor într-un grup de N instrucţiuni (sau M blocuri) şi înscrierea
unui asemenea grup într-o anumită linie din TC. Există desigur cazuri când FU poate crea copii multiple ale unor blocuri în TC.
Această redundanţă informaţională poate implica degradări ale performanţei, dar pe de altă parte, lipsa redundanţei ar degrada
valoarea ratei de fetch a instrucţiunilor deci şi performanţa globală.
Se poate deci afirma că un TC exploatează reutilizarea eficientă a secvenţelor dinamice de instrucţiuni, reprocesate frecvent în baza a
2 motive de principiu: localizarea temporală a trace-ului şi respectiv comportarea predictibilă a salturilor, determinabilă în virtutea
comportării lor anterioare. Aşadar, TC memorează trace-uri în scopul eficientizării execuţiei programului şi nu doar în scopul
eficientizării procesului de aducere a instrucţiunilor. Aceasta, pe motiv că un segment din trace conţine numai instrucţiuni care se vor
executa.
La ora actuală, procesorul Intel Pentium IV reprezintă primul procesor comercial care înlocuieşte nivelul L1 de cache clasic cu un
Execution Trace Cache. De asemenea, alte caracteristici arhitecturale pentru respectivul procesor constituie: integrarea a 42 milioane
de tranzistori, un pipeline ce poate funcţiona pe 20 de nivele, expedierea simultană spre execuţie a 4 instrucţiuni per perioadă de tact
procesor, o magistrală ce va funcţiona la frecvenţa de 400 MHz, rata de transfer la memorie ajungând astfel la 3,2 Gb/s .
O extensie a reutilizării dinamice a instrucţiunilor, focalizată însă pe rezolvarea limitărilor de execuţie ale procesoarelor superscalare
(issue bottleneck), este prezentată în capitolul 2 al acestei cărţi, împreună cu legăturile între conceptul de reutilizare şi respectiv acela
de predicţie a valorilor instrucţiunilor, acesta din urmă atât de util în implementarea execuţiilor speculative.
Se defineşte şi se analizează conceptul localităţii (vecinătăţii) valorilor şi se dovedeşte că gradul de vecinătate este unul
semnificativ. Pe această bază apare ca naturală ideea predicţiei valorilor instrucţiunilor şi se prezintă, în urma unei bogate cercetări
bibliografice a articolelor ştiinţifice importante din ultimii ani, în mod sistematizat, schemele importante de predicţie a valorilor.
Predicţia valorilor instrucţiunilor determină execuţia speculativă a acestor instrucţiuni, grăbindu-se astfel procesarea în mod
semnificativ. În fond, problema predicţiei valorilor reprezintă o generalizare a celei a predicţiei branch-urilor, prezentată succint chiar
în cadrul acestui capitol. În esenţă, punctul comun al arhitecturilor cu reutilizare dinamică a instrucţiunilor şi respectiv celor cu
predicţie a valorilor, constă în reducerea timpului de execuţie aferent căii critice a programului, cea care consumă cel mai mult timp.
Revoluţionarismul acestor tehnici constă în faptul că ele acţionează eficient asupra celei mai importante limitări a procesării
numerice actuale şi anume aceea dată de secvenţialitatea intrinsecă a instrucţiunilor programului. Evoluţionarismul tehnicilor
prezentate succint constă în faptul că ele se grefează cu naturaleţe în cadrul modelului superscalar, dominant la ora actuală în
domeniul microprocesoarelor comerciale de uz general şi nu afectează cerinţele de compatibilitate cerute de “tradiţie”.
Practic procesarea multithread a migrat prin aceste microprocesoare, din software înspre hardware. Procesarea speculativă este
prezentă şi aici pentru că pe această bază se determină în mod dinamic firele speculative de execuţie din cadrul programului. Aceste
fire sunt executate apoi multiplexat în timp, de către o microarhitectură care trebuie să fie adaptată special la acest tip de procesare.
Performanţele obţinute în urma simulărilor sunt remarcabile. Se arată că predicţia valorilor instrucţiunilor poate fi integrată în mod
sinergic în cadrul acestor microarhitecturi de tip multithread.
2.1 Reutilizarea dinamică a instrucţiunilor
Iată deci cum un concept fundamental şi fecund în actuala inginerie a calculatoarelor, anume acela de reutilizare, migrează practic din
software şi înspre hardware (vertical migration). O instrucţiune dinamică este reutilizabilă dacă ea operează asupra aceloraşi intrări
şi produce aceleaşi rezultate precum o instanţă anterioară a aceleiaşi instrucţiuni. Ideea de bază este că dacă o secvenţă de instrucţiuni
se reia în acelaşi “context de intrare”, atunci execuţia sa nu mai are sens, fiind suficientă o simplă actualizare a “contextului de ieşire”,
în concordanţă cu unul precedent memorat în anumite structuri de date hardware. Se reduce astfel numărul de instrucţiuni executate
dinamic, putându-se acţiona direct chiar asupra dependenţelor de date între instrucţiuni. Aşadar, instrucţiunile reutilizate nu se vor mai
executa din nou, ci pur şi simplu contextul procesorului va fi actualizat în conformitate cu acţiunea acestor instrucţiuni, bazat pe
istoria lor memorată.
Mai puţin de 20% din numărul instrucţiunilor statice care sunt repetate generează peste 90% dintre instrucţiunilor dinamice repetate.
În medie armonică, măsurat pe benchmark-urile SPEC ’95 (Standard Performance Evaluation Corporation), 26% dintre instrucţiunile
dinamice sunt reutilizabile.
Pentru o mai bună înţelegere a fenomenului de repetiţie a instrucţiunilor, execuţia dinamică a programelor este analizată pe trei
niveluri: global, al funcţiei şi respectiv local (în interiorul funcţiei). În analiza globală, pattern-urile de date utilizate în programe
sunt reţinute ca entităţi întregi şi determinate sursele de repetiţie ale instrucţiunilor (intrări externe, iniţializări globale de date sau
valori interne ale programelor). Întrucât repetiţia instrucţiunilor se datorează în mare măsură ultimelor două surse de repetiţie, se
impune concluzia în virtutea căreia fenomenul de repetiţie este mai mult o proprietate a modului în care calculul este exprimat prin
program şi mai puţin o proprietate a datelor de intrare. Concluziile generate în urma analizei la nivel de funcţie,sunt că de foarte
multe ori funcţiile sunt invocate repetat cu exact aceleaşi valori ale parametrilor de intrare şi că relativ puţine apeluri de funcţii nu au
argumente repetate. Chiar şi în cazul unor apeluri repetate ale unei funcţii cu parametrii de intrare diferiţi, procentajul de instrucţiuni
dinamice reutilizabile poate fi unul semnificativ. La nivelul analizei locale, instrucţiunile funcţiilor/procedurilor sunt clasificate în
funcţie de sursa valorilor folosite (ex: argumentele funcţiei, date globale, valori returnate de alte funcţii etc.) şi în funcţie de sarcina
realizată (ex: salvare - restaurare regiştri, prolog - epilog, calcul adrese globale etc.). Majoritatea repetiţiei instrucţiunilor se datorează
valorilor globale sau argumentelor funcţiei dar şi funcţiilor prolog şi epilog.
Bazat în principal pe premisele anterior expuse, cercetătorii americani A. Sodani şi G. Sohi dezvoltă 3 scheme de reutilizare dinamică
a instrucţiunilor, primele două la nivel de instrucţiune iar ultima, la nivel de lanţ de instrucţiuni dependente RAW (Read After Write).
Instrucţiunile deja executate, se memorează într-un mic cache numit buffer de reutilizare (Reuse Buffer - RB). Acesta poate fi adresat
cu PC-ul (Program Counter) pe timpul fazei de aducere a instrucţiunilor (IF –Instruction Fetch) având şi un mecanism pentru
invalidarea selectivă a unor intrări bazat pe acţiunile anumitor evenimente. Desigur că acest RB trebuie să permită şi un mecanism de
testare a reutilizabilităţii instrucţiunii selectate. Testul de reutilizare verifică dacă informaţia accesată din RB reprezintă un rezultat
reutilizabil sau nu. Detaliile de implementare ale testului depind de fiecare schemă de reutilizare folosită. De asemenea, trebuie tratate
două aspecte privind managementul RB:
stabilirea instrucţiunii care va fi plasată în buffer şi menţinerea consistenţei buffer-ului de reutilizare.
Considerând un procesor superscalar care poate aduce, decodifica şi executa maximum 4 instrucţiuni / ciclu,
secvenţa anterioară se procesează ca în cele două figuri următoare (figurile 2.5, 2.6).
Rezumând, se desprind câteva avantaje introduse de tehnica de reutilizare dinamică a instrucţiunilor şi anume:
• Scurtcircuitarea unor nivele din structura pipe de către instrucţiunile reutilizate, reducând presiunea asupra resurselor (staţii de
rezervare, unităţi funcţionale, porturi ale cache-urilor de date etc.) necesare altor instrucţiuni aflate în aşteptare.
• La reutilizarea unei instrucţiuni rezultatul său devine cunoscut mai devreme decât în situaţia în care s-ar procesa normal, permiţând
în consecinţă altor instrucţiuni dependente de aceste rezultate să fie executate mai rapid.
• Reduce penalitatea datorată predicţiei eronate a adreselor destinaţie în cazul instrucţiunilor de salt, prin reutilizarea, fie şi parţială,
codului succesor punctului de convergenţă (squash reuse).
• Comprimarea dependenţelor de date determină îmbunătăţirea timpului de execuţie al instrucţiunilor crescând gradul de paralelism al
arhitecturii.
• Procentajul de reutilizare al instrucţiunilor dinamice, calculat pe benchmark-urile SPEC ’95, este semnificativ, ajungându-se la
valori maxime de 76%.
• Accelerarea obţinută faţă de modelul superscalar pe aceleaşi programe de test nu este la fel de pronunţată ca şi procentajul de
reutilizare (medii de 7-15%), valoarea maximă atinsă fiind de 43%.
Tehnica Load Value Prediction, spre deosebire de alte tehnici speculative, cum ar fi mecanismul de aducere anticipată a
instrucţiunilor sau predicţia ramificaţiilor de program, reduce şi nu creşte necesarul de lărgime de bandă al memoriei. De asemenea,
disponibilitatea foarte devreme (la începutul fazei de aducere a instrucţiunii IF) a indecşilor de accesare a tabelelor LVPT şi LCT
(accesul la respectivele tabele putând fi pipeline-izat peste două sau mai multe niveluri), complexitatea relativ redusă în proiectare şi
realizarea de tabele relativ mari fără a afecta perioada de tact a procesorului, sunt caracteristici care fac tehnica LVP atractivă pentru
proiectanţii de viitoare microarhitecturi. Primele cercetări au arătat o creştere de performanţă medie de cca. 6% datorată implementării
acestei tehnici.
A. “Last Value Predictors”
Un tip relativ întâlnit de astfel de predictoare sunt aşa numitele “last value predictors”,caracterizate prin faptul că predicţionează noua
valoare ca fiind aceiaşi cu ultima valoare produsă de către instrucţiunea respectivă.
B. Predictoare incrementale
În acest caz, considerând că vn-1 şi vn-2 sunt cele mai recente valori produse, noua valoare vn va fi calculată după formula de
recurenţă: vn = vn-1 + (vn-1 - vn-2), unde (vn-1 - vn-2) este pasul secvenţei.
Desigur că pasul ar putea fi şi variabil, nu neapărat constant tot timpul (constant doar pe anumite intervale de timp). În această idee se
propun şi în acest caz scheme de actualizare a pasului bazate pe histerezis. Astfel, pasul memorat în tabelele de predicţie este
modificat numai atunci când numărătorul saturat asociat, memorează o valoare situată peste un prag stabilit. Fireşte, acest numărător
este incrementat/decrementat în cazul unei predicţii corecte/incorecte, respectiv. În figura următoare (Fig.2.12) se prezintă o structură
tipică de predictor incremental (pas constant).
O acţiune importantă în cadrul unui predictor incremental este constituită de detecţia pasului.
Prima dată când o instrucţiune va fi procesată, va rezulta un miss în tabela de predicţie VHT şi evident că nu se va face nici o
predicţie. Când o instrucţiune produce un rezultat atunci: (1) rezultatul este memorat în câmpul “Val” din VHT şi (2) automatul trece
în starea iniţială “Init” (vezi figura 2.13). Atât timp cât eventuale următoare instanţe succesive ale aceleiaşi instrucţiuni produc acelaşi
rezultat (pas=0), automatul va ramâne în starea iniţială. Dacă însă o instanţă următoare a instrucţiunii produce un rezultat (R1) diferit
de precedentul, atunci se calculează pasul S1=R1-Val → VHT şi R1 → VHT. Totodată automatul trece în starea intermediară
“Tranziţie”. Dacă în această stare apare o nouă instanţă dinamică a instrucţiunii, nu se va genera nici o predicţie.
În schimb S2=R2-Val → VHT şi R2 → VHT. Dacă S1 = S2 atunci automatul trece în starea “Stabil”, altfel rămâne în starea
“Tranziţie”. În starea stabil, valoarea prezisă = R2 +S2. Cu alte cuvinte, acest automat de stare implementează un anumit “grad de
încredere” asociat predicţiei. Predicţia nu se face decât în cazul în care pragul de încredere (Confidence Threshold) depăşeşte o
anumită valoare, apriori determinată.
2.2.2.2 Predictoare contextuale
O clasă importantă a predictoarelor contextuale sunt cele care implementează algoritmul “Prediction byPartial Matching” (PPM),
care conţine un set de predictoare markoviene ca în figura următoare (Figura 2.14). Ideea originară aparţine lui Trevor Mudge cel care
a folosit-o pentru prima dată în scopul dezvoltării unor predictoare de branch-uri care generalizau binecunoscutele predictoare de tip
adaptiv pe două niveluri (Two Level Adaptive Branch Predictors) şi care sunt reprezentate în capitolul precedent, figura 1.4
Desigur că valoarea predicţionată este aceea care a urmat cu cea mai mare frecvenţă contextului considerat. După cum se observă în
figura 2.14, ea este funcţie şi de contextul considerat, un context mai “bogat” (“lung”) conducând adeseori la o acurateţe mai ridicată
a predicţiei (nu întotdeauna însă: după cum am mai subliniat, câteodată contextul se poate comporta ca “zgomot”.
În se definesc două caracteristici importante în înţelegerea comportării unui predictor.Prima, este perioada de învăţare (Learning Time
- LT), care reprezintă numărul de valori din secvenţa de intrare, generate înaintea primei predicţii corecte. A 2-a este dată de gradul de
învăţare sau acurateţea predicţiei (Learning Degree - LD), care reprezintă procentajul de predicţii corecte generate după perioada de
învăţare a predictorului.
Se poate observa, că adunând creşterea adusă de predictorul incremental cu creşterea adusă de predictorul contextual, se obţine o
valoare mai mare decât creşterea adusă de predictorul hibrid,indiferent de tipul adresei utilizate. Acest lucru se poate datora şi faptului
că predictorul hibrid implementat acordă întotdeauna prioritate predictorului contextual, cel incremental fiind folosit doar atunci când
acesta nu poate genera o predicţie. Evident, soluţia aceasta este lipsită de flexibilitate. O posibilă soluţie mai bună pentru rezolvarea
acestei probleme ar fi să se implementeze două automate în fiecare locaţie a tabelei de predicţie, unul pentru predictorul incremental şi
încă unul pentru cel contextual. Va avea prioritate predictorul al cărui numărător asociat (grad de încredere) are valoarea mai mare, la
un moment dat. În cazul în care cele două valori sunt egale, predictorului contextual i se acordă prioritate.
De asemenea s-a propus ideea novatoare a predicţiei valorilor la nivel de registru general,arătându-se pe bază de simulare că există
anumiţi regiştri preferenţiali, cu un grad ridicat de vecinătate a valorilor şi implicit cu posibilităţi mari de predicţionare a acestora.
Altfel spus, se propun predictoare globale orientate pe resursă şi nu pe instrucţiune ca până acum, cu implicaţii evident favorabile
asupra costurilor, complexităţii şi capacităţilor de memorare aferente predictorului.
4. Microprocesoare multithread. Abordarea multithread pentru acoperirea latenţelor
. Un “procesor multithread’” (PMT) deţine abilitatea de a procesa instrucţiuni provenite din thread-uri (“fire de execuţie”) diferite,
facilitând astfel execuţia programelor “multifir”. Un thread reprezintă o secvenţă atomică de program, concurentă, recunoscută de
către sistemele de operare (SO). Aceste SO permit mai multor fire de execuţie să ruleze, alocându-le resursele necesare în acest scop.
După cum se va arăta în continuare, firele se activează-dezactivează pe parcursul execuţiei programelor, comunicând între ele şi
sincronizându-şi activităţile. Această tehnică se mai numeşte uneori, în mod inexact, şi multitasking, deoarece programul se poate
ocupa cvasi-simultan de mai multe sarcini. PMT gestionează o listă a threadurilor active şi decide într-o manieră dinamică asupra
instrucţiunilor pe care să le lanseze în execuţie. Coexistenţa mai multor thread-uri active permite exploatarea unui tip de paralelism
numit “Thread Level Parallelism” (TLP. Instrucţiunile din thread-uri diferite, fiind independente între ele, se pot executa în paralel
ceea ce implică grade superioare de utilizare ale resurselor precum şi mascarea latenţelor unor instrucţiuni. În acest ultim sens, de
asemenea,gestiunea branch-urilor este simplificată, latenţa acestora putând fi (măcar parţial) acoperită de instrucţiuni aparţinând unor
thread-uri diferite şi deci independente de condiţia de salt. De asemenea, efectul defavorabil al miss-urilor în cache-uri poate fi
contracarat prin acest multithreading (dacă un thread generează un miss în cache de ex., CPU-ul - Central Processing Unit - poate
continua procesele de aducere ale instrucţiunilor din cadrul celorlalte thread-uri).
Aşadar “groapa” semantică între conceptele de procesare multithreading a aplicaţiilor HLL şi procesorul hardware convenţional este
umplută tocmai de aceste “microprocesoare multithread “ Deşi multithreading-ul îmbunătăţeşte performanţa globală, se cuvine a se
remarca faptul că viteza de procesare a unui anumit thread în sine, nu se îmbunătăţeşte prin această tehnică. Mai mult,este de aşteptat
chiar ca viteza de procesare pentru fiecare thread în parte să se degradeze întrucât resursele CPU trebuiesc partajate între toate thread-
urile active din structurile pipeline. Cu alte cuvinte, acest TLP se pretează a fi exploatat, fireşte, în modurile de lucru ale sistemelor de
operare de tip multiprogramare sau/şi multithread. Partajarea multiplelor resurse hardware în vederea implementării mai multor
“contexte” de procesare aferente fiecărui thread, implică probleme dificile în cadrul unui PMT (mecanisme de aducere a mai multor
instrucţiuni de la adrese diferite şi necontigue, structuri de predicţie multiple, lansarea în execuţie a mai multor instrucţiuni aparţinând
unor thread-uri distincte etc). Simularea şi optimizarea unor arhitecturi PMT devin extrem de sofisticate, clasicele benchmark-uri în
sine, nemaifiind aici de mare ajutor. Trebuie lucrat în medii software bazate pe multiprogramare ceea ce nu este deloc uşor de
implementat şi mai ales de simulat şi evaluat.
Latenţa memoriei principale este o problema esenţială în sistemele de calcul actuale. În cadrul unui sistem multimicroprocesor cu
memorie partajată (“DSM - Data Shared Memory”), procesoarele sunt conectate la modulele fizice de memorie printr-o reţea de
interconectare, mai mult sau mai puţin complexă (unibus, crossbar, interconectări dinamice multinivel etc)Dacă un anumit procesor
doreşte să citească o anumită locaţie din spaţiul logic unic de adresare el va lansa o cerere de acces (mesaj). Aceasta se va propaga de
la procesor la modulul fizic de memorie prin intermediul reţelei de interconectare. Modulul de memorie va furniza data
procesorului după un timp de citire propriu, intrinsec circuitului, prin intermediul aceleiaşi reţele de interconectare. Intervalul de timp
dintre cererea procesorului şi recepţionarea datei de către acesta se numeşte latenţă. În cazul sistemelor actuale aceasta devine tot mai
mult o problemă datorită creşterii vitezei microprocesoarelor cu cca. 58 % pe an în timp ce timpul de acces al memoriilor de tip
DRAM scade cu doar 7 % pe an (după alţi autori acesta scade doar cu cca. 3-4% pe an, în timp ce densitatea de integrare a acestor
memorii creşte cu 40-60% pe an.De exemplu, pe un multiprocesor Alpha Server 4100 SMP având 4 procesoare Alpha 21164 la 300
MHz, latenţele la citire sunt:
• 7 tacte (deci cca. 23 ns) în cazul unui miss pe nivelul 1 de cache (L1) şi hit pe nivelul 2 de cache (L2).
• 21 tacte în cazul unui miss pe nivelul L2 şi hit pe L3 (situat pe placa de bază).
• 80 tacte pentru miss în întreaga ierarhia de cache-uri şi accesarea DRAM-ului (memoria principală).
• 125 de tacte pentru un miss care a fost servit din cache-ul altui procesor (se adaugă aici şi latenţa reţelei de interconectare).
Una dintre strategiile arhitecturale relativ recente de a contracara problema latenţelor mari ale sistemelor de memorie o constituie
microprocesoarele multithread dedicate (MMT). În principiu, multitheading-ul a migrat aici din nivelul înalt al sistemelor de
operare şi aplicaţiilor HLL (High Level Languages), pe verticală, în cadrul firmware-ului şi hardware-ului microprocesoarelor
moderne. Printr-o definiţie succintă şi intuitivă, un MMT diferă de un microprocesor convenţional de tip “monofir” ("single
threaded") prin faptul că facilitează procesarea simultană a mai multor instrucţiuni aparţinând unor thread-uri ("fire de execuţie")
diferite, care însă sunt toate candidate înspre a fi executate de către procesor. Similar cu procesoarele convenţionale "monofir", starea
unui MMT constă în contextul momentan al regiştrilor procesorului respectiv memoriei; diferenţa specifică rezidă în faptul că există
în principiu mai multe perechi (PC - Program Counter şi SP –Stack Pointer) şi seturi de regiştri generali, permiţându-se astfel
diferenţierea contextelor momentane aferente thread-urilor în curs de execuţie. Iată deci, într-un mod succint şi principial,cum aceste
caracteristici specifice ale MMT-urilor facilitează procesarea multithread la nivelul sistemului de operare şi aplicaţiilor HLL.
O altă noţiune importantă este aceea de fir de execuţie blocant respectiv neblocant ("blocking or non-blocking"). Noţiunea se referă la
blocarea fluxurilor de instrucţiuni în cadrul structurilor pipeline de procesare a acestora, structuri indispensabile procesoarelor
multithreading dedicate de care ne ocupăm aici. Un fir blocant poate stagna structura pipeline de procesare pe un procesor
convenţional "monofir", în cazul apariţiei unor hazarduri specifice (structurale, de date, de ramificaţie, latenţe etc.) În schimb pe o
arhitectură MMT aceste blocări pot fi evitate prin activarea unui alt thread realizată prin comutarea de context (context switching).
Comutarea de context într-un procesor "monofir", în general consumă timp (salvări/restaurări de contexte în/din memorie), astfel încât
mascarea unui blocaj datorat unui miss în cache este practic compromisă, cel puţin parţial.
Firele neblocante sunt datorate componentei scheduler (reorganizator sau optimizator de cod) din cadrul compilatorului. Acesta
partiţionează programul în mici thread-uri (microthreads), activarea unuia făcându-se numai când toate datele devin disponibile.
Aceleaşi mecanisme hardware trebuie utilizate pentru a sincroniza comunicaţiile interprocese între firele aflate în stare de aşteptare.
Principala cerinţă pentru un MMT o constituie abilitatea de a gestiona 2 sau mai multe fire în paralel şi un mecanism care să permită
comutarea acestora. Fireşte, această comutare este de dorit să fie cât mai rapidă (0÷3 tacte CPU). Ea este facilitată, după cum am mai
menţionat, de mai multe PC-uri, SP-uri şi seturi de regiştri logici, asociate firelor de execuţie.
În principiu, după modul în care un fir intră şi respectiv iese în/din execuţie, există 3 modalităţi distincte de procesoare
multithreading:
• prin întreţeserea instrucţiunilor în fiecare ciclu ("cycle by cycle interleaving"), adică în fiecare ciclu CPU o instrucţiune dintr-un alt
thread este adusă şi lansată în structura pipeline.
• prin întreţeserea blocurilor de instrucţiuni, adică instrucţiunile dintr-un thread sunt,executate până când apare un hazard ce produce o
latenţă. Acest eveniment implică o comutare de context, deci sunt activate instrucţiunile din alt bloc (“block interleaving”).
• multithreading simultan ("simultaneous multithreading"), care constă într-o combinare a multithreading-ului cu procesarea
superscalară. Instrucţiunile - aparţinând sau nu unor fire de execuţie diferite - sunt lansate înspre unităţile funcţionale aferente
procesorului superscalar în mod simultan, în vederea ocupării optimale a resurselor acestuia.
De menţionat că o problemă criticabilă a MMT-urilor constă în faptul că în general procesează mai puţin performant decât un
procesor superscalar un program de tip "monofir".
1: a i , j , i 1,27 , j 1,3
S1
S2
S3
27: