Sunteți pe pagina 1din 15

1.

Introducere în problematica microarhitecturilor cu execuţie


speculativă
Specialiştii opinează că 65% din această creştere explozivă a performanţei se datorează progreselor arhitecturale şi doar 35% celor
tehnologice.
Din punct de vedere arhitectural şi în special după modul de prelucrare a instrucţiunilor, se consideră că până la ora actuală au existat
3 generaţii de (micro)procesoare comerciale de uz general, după cum urmează:
 generaţia I caracterizată în principal prin execuţia secvenţială a fazelor (ciclilor maşină) aferente instrucţiunilor - maşină.
Pionierii acestei generaţii sunt desigur inventatorii calculatorului numeric, inginerii Eckert şi Mauchly, alături de cel care ulterior a
teoretizat şi a îmbogăţit conceptul, în persoana marelui om de ştiinţă american John von Neumann
 generaţia a II-a de procesoare, care exploata în principal paralelismul temporal aferent instrucţiunilor maşină prin
suprapunerea fazelor acestora (pipelining, overlapping). Primul reprezentant comercial a fost sistemul CDC-6600 (Control Data
Company - 1964) proiectat de către cel mai mare creator de calculatoare de înaltă performanţă şi totodată unul dintre pionierii
supercalculatoarelor, Seymour Cray. Încă de la începuturile anilor '80, microprocesoarele RISC(Reduced Instruction Set Computers)
scalare au reprezentat această generaţie (J. Cocke de la IBM, D. Patterson de la Univ. Berkeley şi J. Hennessy de la Univ. Stanford,
fiind doar trei dintre pionierii promotori ai acestor idei). În plus, aceste microprocesoare au optimizat setul de instrucţiuni maşină în
vederea implementării optimale a aplicaţiilor de nivel înalt.
 generaţia a III-a, cea curentă, este caracterizată de procesarea mai multor instrucţiuni independente simultan, prin exploatarea
unui paralelism spaţial la nivelul diverselor unităţi funcţionale de procesare (Multiple Instruction Issues). Execuţia instrucţiunilor se
face out of order, utilizând deci tehnici de reorganizare, dinamică sau statică, a instrucţiunilor în vederea minimizării timpului global
de execuţie [Hen02]. Pionierul comercial – un adevărat arhetip al acestei generaţii - a fost sistemul anilor '60 IBM-360/91 (printre
proiectanţi Anderson, Sparacio, Tomasulo,Goldschmidt, Earle etc.), care, prin algoritmul lui Tomasulo implementat în unitatea de
virgulă mobilă, a pus bazele execuţiei out of order a instrucţiunilor. De remarcat totuşi că procesul de dispatch (lansare a
instrucţiunilor din buffer-ul de prefetch în staţiile de rezervare) era de tip in order. La ora actuală generaţia aceasta este reprezentată
prin microprocesoarele superscalare în special, dar şi prin cele care optimizează prin compilator fluxul de instrucţiuni, adică cele de
tip VLIW (Very Long Instruction Word), EPIC (Explicitly Parallel Instruction Computers) etc.
De câţiva ani, în laboratoarele de cercetare se întrezăresc câteva soluţii privind caracteristicile majore ale următoarei generaţii
arhitecturale, cea de a IV-a, Din multitudinea realizărilor existente, le-am ales pe acelea care mi s-au părut că îmbină două
caracteristici oarecum opuse: revoluţionarismul (performanţa agresivă) cu evoluţionismul (compatibilitatea cu paradigma actuală a
microprocesorului de uz general). Dintre acestea, cele bazate pe reutilizarea dinamică a instrucţiunilor precum şi cele bazate pe
predicţia valorilor şi deci pe o execuţie super-speculativă a instrucţiunilor. Astăzi, accentul principal nu se mai pune pe implementarea
hardware, ci pe proiectarea arhitecturii. Se porneşte de la o arhitectură de bază, care este modificată şi îmbunătăţită dinamic, prin
simulări laborioase pe benchmark-uri reprezentative (pentru procesoarele de uz general Stanford, SPEC etc.).

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

1.2 Execuţia speculativă şi predicativă a instrucţiunilor


Există în esenţă 2 tipuri de speculaţii: de control (când o instrucţiune aflată în program după un salt condiţionat se execută înaintea
acestuia cu influenţă benefică asupra timpului de execuţie) şi respectiv de date (spre exemplu, când o instrucţiune tip “load” situată în
program după o instrucţiune tip “store” se execută înaintea acesteia, cu beneficii asupra timpului de execuţie datorate mascării latenţei
instrucţiunii de încărcare). Pentru a exemplifica în mod concret o speculaţie de control se consideră secvenţa de program aferent
procesorului pe 64 de biţi Intel Ithanium:
În continuare se prezintă un exemplu sugestiv în legătură cu avantajele/dezavantajele execuţiei predicative a instrucţiunilor în
cadrul arhitecturii Intel Ithanium. Se consideră secvenţa “if – then – else” ca mai jos :
if (r4)
r3 = r2 + r1 ; 2 cicli
else
r3 = r2 ∗ r1
utilizare r3 ; ; 18 cicli
S-a considerat deci că ramificaţia “if” are latenţa de 2 cicli procesor iar ramificaţia “else” de 18 cicli procesor. Secvenţa va fi
compilată prin instrucţiuni gardate, eliminându-se instrucţiunile de ramificaţie, ca mai jos:
cmpne p1,p2 = r4,r0 ;0/0 compară pe diferit
(p1) add r3 =r2,r1 ;1/1 adunare întregi
(p2) setf f1=r1 ;1/1 conversie întreg – flotant
(p2) setf f2=r2 ;1/1
(p2) xma.l f3=f1,f2 ;9/2 înmulţire f1xf2 (flotant)
(p2) getf r3=f3 ;15/3 conversie flotant – întreg
(p2) utilizare r3 ;17/4
În comentariu, imediat după semnul “;”, sunt scrise 2 cifre: prima semifică numărul ciclului în care instrucţiunea respectivă va fi
lansată în execuţie dacă variabila de gardă p1 = 1 (“true”) iar a 2–a acelaşi lucru în cazul contrar p1 = 0 (implicit p2 = 1). Considerând
acum că instrucţiunea “setf”durează 8 cicli, “getf” 2 cicli, “xma” 6 cicli şi că o predicţie incorectă a branch-ului costă procesorul 10
cicli (pt. restaurarea stării), se pot analiza 2 cazuri complementare sugestive.
Cazul I
Se presupune că ramura “if” se execută 70% din timp iar acurateţea predicţiei branch-ului din codul iniţial (nepredicativ) este 90%.
Timpul de execuţie al secvenţei iniţiale este:
(2 cicli x 70%) + (18 cicli x 30%) + (10 cicli x 10%) = 7.8 cicli
Timpul de execuţie al secvenţei compilate prin predicare este:
(5 cicli x 70%) + (18 cicli x 30%) = 8.9 cicli
În acest caz execuţia predicativă este neeficientă.
Cazul II
Se presupune că ramura “if” se execută 30% din timp şi că acurateţea predicţiei branch-ului
este acum de doar 70%. Timpul de execuţie al secvenţei iniţiale este:
(2 cicli x 30%) + (18 cicli x 70%( + (10 cicli x 30%) = 16.2 cicli
Timpul de execuţie al secvenţei compilate prin predicare este:
(5 cicli x 30%) + (18 cicli x 70%) = 14.1 cicli
În acest al 2-lea caz execuţia predicativă este mai eficientă, micşorând timpul mediu de execuţie cu mai mult de 2 cicli.După cum se
poate observa, instrucţiunile din al 2-lea basic-block (ADD şi SUB) au migrat în primul. Astfel, ele se execută speculativ cu
repercursiuni favorabile asupra timpului de execuţie. Totodată, migrarea instrucţiunilor spre basicblock-uri precedente, conduce la
mărirea acestor basic-block-uri şi deci la creşterea gradului de paralelism prin posibila execuţie paralelă a unor instrucţiuni aparţinând,
iniţial, unor basic-block-uri
diferite. De remarcat că o instrucţiune poate migra teoretic peste k basic-block-uri precedente prin gardarea sa cu un produs de tip ŞI
logic peste k variabile de gardă (astfel, optimizatoarele de cod determină instrucţiunile să migreze “în sus”, precum bulele de sifon, ca
să folosesc o comparaţie plastică). Astfel de tehnici de optimizare statică – numite în literatură şi percolation scheduling techniques -
caracterizează microprocesoarele generaţiei următoare în ciuda unor dezavantaje legate de evaluările gărzilor instrucţiunilor
condiţionate, prezentate în
SUB R1, R2, R3 SUB R1, R2, R3
LT B8, R1, #10 LT B8, R1, #10
BT B8, Adr FB8 ADD R7,R8, R1; speculativă
ADD R7, R8, R1 FB8 SUB R10, R7, R4; speculativă
SUB R10, R7, R4 BT B8, Adr
1.3 Reutilizarea instrucţiunilor prin procesoare de tip trace-cache
O paradigmă interesantă, situată în prelungirea conceptului de superscalaritate şi care poate constitui o soluţie inedită faţă de limitările
anterior menţionate ale arhitecturilor superscalare, de tip fetch bottleneck, o constituie trace-procesorul, adică un procesor
superscalar având o memorie trace-cache (TC). Ca şi cache-urile de instrucţiuni (IC), TC este accesată cu adresa de început a noului
bloc de instrucţiuni ce trebuie executat, în paralel cu IC. În caz de miss în TC, instrucţiunea va fi adusă din IC sau - în caz de miss şi
aici - din memoria principală. Spre deosebire însă de IC, TC memorează instrucţiuni contigue din punct de vedere al secvenţei lor de
execuţie, în locaţii contigue de memorie. O linie din TC memorează un segment de instrucţiuni executate dinamic şi secvenţial în
program (trace-segment). Evident, un trace poate conţine mai multe basic-block-uri (unităţi secvenţiale de program). Aşadar, o linie
TC poate conţine N instrucţiuni sau M basicblock-uri, N>M, înscrise pe parcursul execuţiei lor.

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%.

2.2 Localitatea (vecinătatea) şi predicţia valorii


O altă tehnică hardware, şi ea relativ recent dezvoltată (1996, 1997), care urmăreşte, oarecum similar cu tehnica anterioară,
exploatarea redundanţei existente în programe prin comprimarea dinamică a dependenţelor de date, o reprezintă predicţia valorilor
instrucţiunilor (Value Prediction). Deşi ambele tehnici urmăresc reducerea timpului de execuţie al programelor prin eliminarea
constrângerilor legate de dependenţele de date ale fluxului de instrucţiuni, există totuşi diferenţe, chiar majore privind unele aspecte,
legate de modul de interacţiune al fiecărei tehnici în parte cu celelalte caracteristici microarhitecturale. De asemenea, se pun probleme
relativ la modul de determinare speculativă (predicţia valorii) sau non-speculativă (reutilizarea instrucţiunilor) a
redundanţei în programele de uz general, avantajele şi dezavantajele implicate de fiecare tehnică, cantitatea de redundanţă captată de
fiecare tehnică în parte etc. Înainte de a face cunoscute şi a înţelege diferenţele existente între predicţia valorilor şi reutilizarea
instrucţiunilor se vor descrie câteva caracteristici şi aspecte legate de conceptul de localitate a valorii şi respectiv predicţie a valorilor.
Localitatea (“vecinătatea“) valorii reprezintă o a treia dimensiune a conceptului de localitate (pe lângă cea temporală şi respectiv
spaţială, frecvent întâlnite în programele de uz general), descriind probabilitatea statistică de referire a unei valori anterior folosite şi
stocată în aceeaşi locaţie de memorie sau registru. Conceptul de localitate a valorii – introdus în premieră de M. Lipasti et co. în
[Lip96a] – este strâns legat de calculul redundant (repetarea execuţiei unei operaţii cu aceiaşi operanzi). Diferenţa de esenţă între
localităţile temporale şi spaţiale şi respectiv localitatea valorilor constă în faptul că primele două sunt focalizate pe adrese, în timp ce
ultima este centrată pe rezultatele produse. Mai precis, localitatea temporală se referă la probabilitatea ca o
anumită adresă – conţinând o “instrucţiune” sau o “dată” – să fie referită din nou în viitorul “apropiat”, în timp ce localitatea valorii
se referă la faptul că rezultatul unei instrucţiuni care este din nou procesată, să se repete. Exploatarea localităţilor spaţiale şi
temporale se face în principal prin sisteme ierarhizate de memorii cache care reduc latenţa memoriilor principale, în timp ce
localitatea valorilor implică predicţia acestora în vederea execuţiilor speculative a instrucţiunilor.
Având în vedere semnificaţia şi tradiţia noţiunii de “vecinătate” în literatura românească dedicată analizei matematice şi teoriei
mulţimilor, exprimăm şi noi opinia că aceasta ar fi poate mai potrivită decât termenul de “localitate” care s-a cam impus din păcate la
noi, datorită traducerii – nu tocmai potrivite în acest context – cuvântului englezesc “locality” .Convingerea că "vecinătatea valorilor"
există, are la bază rezultate statistice obţinute prin simulare la nivel de execuţie a instrucţiunilor pe benchmark-urile SPEC ’95.
Conceptul de localizare a valorilor se referă practic la o corelaţie dinamică între numele unei resurse (registru, locaţie de memorie,
port I/O) şi valoarea stocată în acea resursă.
Dacă memoriile cache convenţionale se bazează pe localitatea temporală şi spaţială a datelor pentru a reduce timpul mediu de acces la
memoria principală, tehnica LVP exploatează vecinătatea valorii prin predicţia acestei valori, reducând atât timpul mediu de acces la
memorie cât şi necesarul de lărgime de bandă al memoriei (se efectuează mai puţine accese la memoria centrală), asigurând astfel un
câştig de performanţă considerabil. Desigur că toate aceste avantaje se obţin simultan cu reducerea considerabilă a presiunii asupra
memoriilor cache. Ca şi consecinţă a predicţiei valorilor se reduc şi efectele defavorabile ale dependenţelor RAW, prin reducerea
aşteptărilor instrucţiunilor dependente ulterioare. Dacă instrucţiunile predicţionate se află pe calea critică a programului, cea mai mare
consumatoare de timp, execuţia acesteia se comprimă în mod considerabil.
Predicţia dinamică a valorilor instrucţiunilor reprezintă o tehnică relativ recentă, care permite execuţia speculativă a instrucţiunilor
dependente RAW, prin predicţia rezultatelor acestora, reducându-se astfel în mod semnificativ latenţa de execuţie a căii critice a
programului.
Localitatea valorilor este justificată de câteva observaţii empirice desprinse din programele de uz general, medii şi sisteme de operare
diverse:
• Redundanţa datelor – seturile de intrări de date pentru programele de uz general suferă mici modificări (Ex: matrici rare, fişiere text
cu spaţii goale şi oricum cu multe simboluri repetitive).
• Verificarea erorilor – tehnica LVP poate fi benefică în gestionarea tabelelor de erori ale compilatoarelor, în cazul apariţiei unor erori
repetate.
• Constante în program – deseori este mult mai eficient ca programele să încarce constante situate în structuri de date din memorie,
ceea ce este exploatat favorabil prin tehnica LVP.
• Calcululul adreselor instrucţiunilor de salt – în situaţia instrucţiunilor case (switch în C)compilatorul trebuie să genereze cod care
încarcă într-un registru adresa de bază pentru branch,care este o constantă (predicţia adreselor destinaţie pentru instrucţiunile de salt).
• Apelul funcţiilor virtuale – în acest caz compilatorul trebuie să genereze cod care încarcă un pointer de funcţie, care este o constantă
în momentul rulării.
Iată deci că problematica predicţiei (în latină prae – înainte, dicere – a spune) în microprocesoarele avansate, tinde să devină
una generală şi ca urmare implementată pe baza unor principii teoretice mai generale şi mai elevate. Aceasta are drept scop principal
şi imediat, execuţia speculativă agresivă a instrucţiunilor, cu beneficii evidente în creşterea gradului mediu de paralelism.

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.

2.2.2.4 Predictoare hibride


Din acest motiv a apărut ca naturală ideea predicţiei hibride, adică două sau mai multe predictoare de valori să conlucreze în mod
dinamic în procesul de predicţie. Spre exemplificare, în se prezintă un interesant predictor hibrid compus dintr-un predictor pe 2
niveluri şi respectiv un predictor incremental (v. Fig. 2.20). Ideea de bază este simplă: dacă predictorul pe 2 niveluri generează o
predicţie, aceasta este cea generată de către predictorul hibrid, în caz contrar, se selectează valoarea generată de către predictorul
incremental (dacă acesta generează vreuna).
2.3 Câteva concluzii
După prezentarea celor două tehnici de exploatare a redundanţei din program prin eliminarea dependenţelor de date, IR (instruction
reuse) şi VP (value prediction), evidenţiem succint diferenţele existente precum şi modalităţile diferite de abordare a problemei
reducerii căii critice de execuţie a instrucţiunilor într-un program.
VP este o tehnică speculativă care predicţionează rezultatele instrucţiunilor bazat pe rezultatele cunoscute anterior, efectuează
operaţiile folosind valorile prezise iar execuţia speculativă este confirmată la un moment ulterior - late validation - când valorile
corecte devin disponibile,deci după execuţia instrucţiunii. În cazul unei predicţii corecte, calea critică este redusă întrucât
instrucţiunile care s-ar executa secvenţial în mod normal, pot fi executate în paralel. În caz contrar, instrucţiunile executate cu intrări
eronate trebuiesc reexecutate, cu penalizările corespunzătoare de timp. Utilizarea predicţiei şi speculaţiei necesită mecanisme
dedicate pentru:
• Verificarea corectitudinii predicţiei
• Refacerea contextului CPU în urma unei predicţii eronate (recovery)
• Informarea următoarelor secvenţe de instrucţiuni dependente asupra valorilor speculative pe care acestea le vor folosi. Aceste valori
sunt memorate într-o structură specială numită Prediction Register File .
IR este o tehnică nespeculativă care recunoaşte un lanţ de instrucţiuni dependente executat anterior şi nu-l mai execută din nou - early
validation - actualizând doar diferite date (rezultatele) în tabelele hardware aferente. Astfel, IR comprimă un lanţ de instrucţiuni din
calea critică de execuţie a programului, ca şi VP de altfel, dar într-un mod diferit.Figura 2.22 prezintă comparativ implementarea în
structura pipeline a unei microarhitecturi a celor două mecanisme: (a) - Value Prediction şi respectiv (b) - Instruction Reuse.
VP captează mai multă redundanţă din program decât IR. Deoarece IR validează rezultatele devreme în structura pipe, bazat pe
intrări, pot apare următoarele situaţii dezavantajoase: dacă intrările unei instrucţiuni nu sunt disponibile în momentul realizării testului
de reutilizare atunci respectiva instrucţiune nu va fi reutilizată. Din păcate, o instrucţiune care produce un rezultat identic cu unul
anterior, dar cu intrări diferite (operaţii logice, instrucţiuni Load etc.), nu va fi reutilizată. În schimb, VP poate realiza predicţii corecte
în ambele situaţii prezentate mai sus întrucât valorile prezise nu depind de disponibilitatea intrărilor în structura pipe şi nici de
importanţa faptului ca ele să fie sau nu identice cu instanţele anterioare ale aceleiaşi instrucţiuni.
Cele două tehnici interacţionează diferit cu mecanismul de predicţie a branch-urilor. IR reduce penalitatea datorată unei predicţii
greşite a salturilor din două motive. În primul rând, când un branch (salt condiţionat) predicţionat greşit este reutilizat, predicţia
eronată este detectată mai devreme (în faza de decodificare) decât s-ar realiza dacă saltul s-ar executa. Al doilea motiv îl constituie
posibila convergenţă a codului în programe. Astfel prin posibila reutilizare a codului existent după punctul de convergenţă a căilor de
execuţie, şi care în cazul superscalar este evacuat în cazul unei predicţii eronate în mod ironic, tehnica IR îmbunătăţeşte timpul de
execuţie al programelor. Pe de altă parte, VP poate creşte penalitatea introdusă de un branch greşit predicţionat din următoarele
motive: cauzează predicţii eronate suplimentare şi prin întârzierea rezolvării saltului respectiv (aşteptându-se ca operanzii săi să
devină disponibili - după calculul şi verificarea acestora).
VP şi IR influenţează concurenţa asupra resurselor prin schimbarea atât a pattern-ului în care resursele sunt folosite cât şi a cererii
efectuate. Prin comprimarea dependenţelor de date, cele două tehnici determină execuţia mai devreme a instrucţiunilor. Întrucât o
instrucţiune reutilizată nu se execută, IR tinde să reducă concurenţa la resurse. Sunt eliberate astfel resurse şi puse la dispoziţia altor
instrucţiuni concurente. VP determină creşterea cererii de resurse întrucât instrucţiunile care se execută cu operanzi eronaţi se vor
reexecuta. Execuţia acestor instrucţiuni poate fi reluată de mai multe ori dacă valorile sunt predicţionate greşit în mod repetat.
Impactul tehnicilor IR şi VP asupra latenţei de execuţie a instrucţiunilor este de asemenea antagonist. IR scade latenţa de execuţie a
operaţiilor individuale de la mai mulţi cicli la doar un singur ciclu (latenţa de reutilizare a unei instrucţiuni). În schimb, VP nu
scurtcircuitează execuţia - instrucţiunile trebuind să se execute pentru a verifica ulterior predicţia. Timpul total de execuţie a unei
instrucţiuni va fi limitat astfel de latenţa sa de execuţie şi verificare. Câştigul aici constă în faptul că se deblochează instrucţiuni
dependente prin procesul de predicţie a valorii. În opinia mea, ambele tehnici sunt extrem de atractive pentru microprocesoarele
generaţiei următoare pentru că, deşi sunt foarte performante acţionând asupra unor secvenţialităţi considerate imuabile, se grefează
favorabil pe actuala paradigmă superscalară a microprocesoarelor de uz general.

3.5 Câteva concluzii


Evaluările arhitecturilor propuse s-au realizat folosind un simulator de tip execution-driven,instrument indispensabil oricărei
investigaţii constructive în domeniul arhitecturilor de calcul. Am arătat modul principial de dezvoltare a unui asemenea simulator,
provenit din modificarea unuia aparţinând setului SimpleScalar. S-a ajuns la concluzia că cele mai performante tabele de predicţie
sunt cele asociative şi că dimensiunea optimă a acestora, în contextul utilizat, este de 512 locaţii. Sa continuat apoi cu evaluarea
arhitecturilor care folosesc istoria valorilor: predictoare incrementale, contextuale şi respectiv hibride. S-a determinat prin simulare, că
dimensiunea optimă a ferestrei de căutare pentru predictoarele contextuale este de 4 valori (ultimele produse). S-a prezentat apoi o
comparaţie a celor patru tehnici de predicţie utilizate (figurile 3.19 şi 3.20). Cu cele mai
performante predictoare, cele hibride, s-a obţinut o acurateţe medie a predicţiei de 65% atunci când s-a folosit adresa instrucţiunii şi
respectiv o acurateţe de 69% atunci când s-a folosit adresa datei.
Predictoarele hibride sunt sinergice şi exploatează cel mai eficient conceptul de localitate a valorilor.
În tabelul următor (Tab. 3.6) se prezintă rezultatele obţinute cu cele patru tipuri de predictoare studiate, în sensul acurateţilor de
predicţie. Aceste valori reprezintă mediile aritmetice ale acurateţilor de predicţie.
Tabelul 3.7 prezintă creşterea relativă a performanţei obţinută cu predictorul incremental,contextual şi cel hibrid faţă de predictorul de
tip “last value”.

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".

4.1 Modelul cu întreţesere la nivel de ciclu


În cadrul acestui model, numit în literatura de specialitate şi "fine-grain multithreading",procesorul comută pe un alt thread după
fiecare aducere de instrucţiune. În principiu, prin multiplexarea instrucţiunilor aparţinând unor thread-uri diferite, pot fi anulate
hazardurile RAW (Read After Write), hazardurile de ramificaţie, latenţele etc. De asemenea, comutarea de context are în acest caz
latenţă nulă. Modelul necesită un număr de thread-uri cel puţin egal cu numărul de nivele ale structurii pipeline, pentru a fi certă
anularea hazardurilor mai sus-amintite. De remarcat totuşi că multiplexările instrucţiunilor din cadrul mai multor thread-uri limitează
viteza de procesare a unui singur thread. Există în esentă 2 modalităţi de a limita această deficienţă :
a) O tehnică statică integrată în scheduler care permite lansarea succesivă în structura pipe a unor instrucţiuni din cadrul aceluiaşi
thread, dacă acestea nu sunt dependente de instrucţiuni anterioare aflate în curs de procesare în structură. Această tehnică implică
modificarea structuriiISA (Instruction Set Architecture) a microprocesorului în sensul adăugării câtorva biţi fiecărui format de
instrucţiune. Aceşti biţi vor informa câte instrucţiuni din acelaşi thread o vor urmasuccesiv pe cea lansată în structură. Fireşte, tehnica
aceasta (numită "dependence lookahead") se pretează a fi utilizată atunci când numărul de thread-uri este insuficient.
b) O tehnică hardware, prin care se adaugă structurii pipeline o logică suplimentară de detecţie şi rezolvare a hazardurilor, atunci când
se procesează un singur thread, deci un hibrid între un CPU clasic şi unul de tip multithreading cu întreţesere.
4.2 Modelul cu întreţeserea blocurilor
Acest model numit şi "coarse grain multithreading", execută un singur thread până în momentul în care apare un eveniment ce
declanşează comutarea de context. Uzual, asemenea evenimente apar atunci când fluxul de instrucţiuni din structura pipeline se
blochează datorită unei operaţii având o latenţă relativ mare. Comparând cu modelul anterior, se remarcă faptul că în acest caz este
necesar un număr mai mic de thread-uri distincte; de asemenea performanţa procesării unui singur thread este comparabilă cu cea
obtenabilă pe un procesor clasic (superscalar) echivalent d. p.d. v. logic (al ISA – Instruction Set Architecture).
Există principial 2 modalităţi de comutare a thread-urilor în cadrul acestui model: statică şi respectiv dinamică. În continuare se
vor explicita sumar fiecare dintre aceste tehnici de comutare a blocurilor.
a. Comutare statică
În acest caz comutarea este dictată prin program (compilator) printr-o instrucţiune special dedicată. În principiu, timpul de comutare
este aici de un tact având în vedere că după aducerea şi decodificarea instrucţiunii care comută thread-ul, aceasta trebuie evacuată din
structura pipe. În schimb, performanţa în cazul "monofir" este drastic diminuată în acest caz.
Totuşi, această modalitate este eficientă în cazul blocurilor cu branch-uri dificil de predicţionat (ex.branch-uri indirecte generate de
polimorfismele din programarea obiectuală etc.)
b. Comutarea dinamică
În acest caz comutarea blocurilor se declanşează datorită unui eveniment dinamic, apărut deci pe parcursul procesării hardware. În
general, comutarea dinamică implică timpi mai mari de comutare decât cea statică datorită necesităţii evacuării tuturor instrucţiunilor
din structura pipe,anterioare stagiului care a declanşat comutarea. Şi aici, ca şi în cazul comutării statice, putem avea câteva modalităţi
distincte de comutare. Astfel se poate comuta blocul pe un miss în cache. Din păcate acesta este detectat relativ târziu în structura
pipeline, implicând astfel timpi de comutare considerabili. O altă modalitate poate comuta pe activarea unui semnal specific ("switch-
onsignal"),dată de o întrerupere, derută ori recepţionarea unui mesaj. În fine, se întâlnesc şi modele de comutare hibride de gen
"conditional switch", în care comutarea se realizează printr-o instrucţiune specială (caracter static), însă numai în cazul în care aceasta
întâlneşte un anumit context dinamic (instanţă hardware). Astfel de exemplu o instrucţiune tip "switch" poate fi introdusă de către
compilator după un grup de instrucţiuni LOAD/STORE. Dacă grupul a generat "miss"-uri comutarea se face, altfel nu.
Principiul predicţiei într-un proces Markov
Considerând un proces Markov de ordinul unu aflat în starea S i (t ) , se predicţionează starea următoare ca fiind S l (t  1)
dacă Max{ai , j } t  ai ,l , unde i, j , l  {1,2,..., N }.
Observaţie: Probabilităţile a ij se modifică în fiecare instanţă, în acord cu predicţia. Astfel, dacă predicţia este corectă ( S l )
atunci ai,l creşte corespunzător iar celelalte probabilităţi de tranziţie scad corespunzător; altfel (starea următoare e S m , m≠l) atunci
a i , m creşte corespunzător iar celelalte probabilităţi scad corespunzător. Prin urmare modelul se antrenează continuu în vederea
predicţiei dinamice.
Exemplu:
Se consideră următoarea secvenţă de stări observabile:
S1 , S1 , S1 , S 2 , S 3 , S1 , S1 , S1 , S 2 , S 3 , S1 , S1 , S1 ...
Se pune problema ce stare urmează?
Un model Markov de ordinul unu, pentru a da răspunsul va trebui să calculeze probabilităţile a11 , a12 şi a13 . Din
secvenţă rezultă că frecvenţele aferente sunt:
3 1
f11  6 , f 12  2 , f 13  0 deci a11  , a12  , a13  0 .
4 4
Prin urmare, predicţia este S1 (greşit, conform corelaţiei stabilite în secvenţă ar trebui să fie S 2 ). Considerând secvenţa
generată printr-un proces Markov de ordinul 2 se predicţionează tot S1 (analog, se caută în şir ce urmează de fiecare dată după
secvenţa S1 S1 ). Abia un proces Markov de ordinul 3 sau superior va predicţiona corect (S2) întrucât:
P[ S1 S1 , S1 , S1 ]  0
P[ S 2 S1 , S1 , S1 ]  1 (frecvenţa 2)
P[ S 3 S1 , S1 , S1 ]  0
Aşadar în procesul de predicţie printr-un proces Markov simplu, ordinul procesului este esenţial. Cu cât ordinul procesului este
considerat mai mare, cu atât este posibilă detecţia unor corelaţii mai adânci în cadrul secvenţei de stări observabile, crescând deci
“viziunea semantică” asupra procesului. Pe de altă parte depistarea frecventă a unor corelaţii determină creşterea frecvenţelor aferente
(deci, ale gradelor de încredere) şi în cazul unor ulterioare schimbări radicale ale comportamentului procesului, adaptarea la noul
comportament va fi una greoaie, consumatoare de timp şi posibil tardivă.

1: a i , j , i  1,27 , j  1,3

S1
S2
S3
27:

Figura 2. Reprezentarea unui proces Markov cu 3 stări, de ordinul 3


Un proces Markov de ordinul k cu N stări distincte are N k combinaţii posibile de lungime k ale celor N stări. În acest caz,
principiul predicţiei este acelaşi considerând „superstarea” curentă formată din ultimele k stări fiind i, i  {1,2,..., N k } se
calculează Max {a ij }  a il , () j  {1,2,..., N }  S l este starea predicţionată. În cazul în care superscalarea curentă nu este
găsită în memoria memorată a stărilor se poate reduce ordinul predictorului (k-1) şi relua în mod interactiv algoritmul de predicţie; în
caz de nevoie se poate apela chiar şi la un predictor Markov de ordinul 0, implementându-se astfel conceptul de predictor PPM
(Prediction by Partial Matching) complet format din (k+1) predictoare Markov de la ordinul k la ordinul 0. Un proces Markov este
ergodic dacă dintr-o anumită stare poate tranzita în orice altă stare într-un număr finit de paşi ( aij  0 ). Într-un sens mai restrâns,
proprietatea de ergodicitate are loc dacă dintr-o anumită stare se poate tranzita în orice altă stare a modelului într-un singur pas(graf
complet conectat).

S-ar putea să vă placă și