Datorarkitekturer med operativsystem ERIK LARSSON
Pipelining Tid SSA P Pipelining FI DI CO FO EI WO FI DI CO FO EI WO FI DI CO FO EI WO FI DI CO FO EI WO Superscalar pipelining FI DI CO FO EI WO FI DI CO FO EI WO FI DI CO FO EI FI DI CO FO EI WO WO
VLIW processorer Very long instruction word (VLIW) processorer använder kompilatorn för detektering av parallellism Kompilatorn analyserar och detekterar operationer som kan exekveras parallellt. Dessa operationer packas in i en large instruktion När en long instruktion hämtats, exekveras alla dess operationer parallellt Ingen hårdvara behövs för run time detektering av parallellism Problemet med litet instruktions fönster är löst; kompilatorn kan vid kompilering använda hela programmet för detektera parallellism.
VLIW processorer Källkod.... Fetch unit Minne FU-1 Maskinkod: Instr. i: op 1 op 2 op 3 op 4 op 1 - op 3 op 4 Decode unit FU-2 FU-3 Register op 1 op 2 - op 4 FU-4 Execution unit
VLIW processorer Antag följande C-program: for (i=959; i>=0; i--) x[i]=x[i]+s; där x är ett fält av flyttal och s är ett flyttal Loop: LDD F0, (R1) F0 <- x[i] (load double) ADF F4, F0, F2 F4 <- F0 + F2; (flyttal) STD (R1), F4 x[i] <- F4 (store double) SBI R1, R1, #8 R1 <- R1 8 BGEZ R1, Loop där R1 håller adress till sista element i x. Övriga element är på lägre adresser och x[0] är på adress 0 Flyttalsregistret F2 håller värdet till s Varje flyttal är 8 bytes
VLIW processorer Minne1 Minne2 Flyttal1 Flyttal2 Heltal/hopp LDD F0,(R1) ADF F4, F0, F2 STD 8(R1), F4 SBI R1, R1, #8 BGEZ R1, Loop En iteration tar 6 klockcykler Hela loopen tar: 960*6=5760 klockcykler Observationer: nästan ingen parallellism, de flesta operationerna för en instruktion är tomma. Har redan minskat R1 så displacement behövs
Loop unrolling Om C-programmet skrivs så här: for (i=959; i>=0; i-=2){ x[i]=x[i]+s; x[i-1]=x[i-1]+s;} så blir sekvensen för en vanlig processor så här: Loop: LDD F0, (R1) F0 <- x[i] (load double) ADF F4, F0, F2 F4 <- F0 + F2; (flyttal) STD (R1), F4 x[i] <- F4 (store double) LDD F0, -8(R1) F0 <- x[i-1] (load double) ADF F4, F0, F2 F4 <- F0 + F2; (flyttal) STD -8(R1), F4 x[i-1] <- F4 (store double) SBI R1, R1, #16 R1 <- R1 16 BGEZ R1, Loop
Loop unrolling Minne1 Minne2 Flyttal1 Flyttal2 Heltal/hopp LDD F0,(R1) LDD F6,-8(R1) ADF F4, F0, F2 ADF F8, F6, F2 SBI R1, R1, #16 STD 16(R1), F4 STD 8(R1), F4 BGEZ R1, Loop Två iterationer tar 6 klockcykler Hela loopen tar: 480*6=2880 klockcykler Observationer: mer parallellism, exekveringstiden mycket lägre, fortfarande många operationer i instruktioner tomma.
Loop unrolling Loop unrolling är en teknik som används av kompilatorer för att öka parallellismen. Utnyttjas av kompilatorer till processorer med möjlighet att exekvera instruktioner parallellt. Loop: LDD F0, (R1) F0 <- x[i] (load double) Unroll ytterligare ett steg: for (i=959; i>=0; i-=3){ x[i]=x[i]+s; x[i-1]=x[i-1]+s; x[i-2]=x[i-2]+s; } ADF F4, F0, F2 F4 <- F0 + F2; (flyttal) STD (R1), F4 x[i] <- F4 (store double) LDD F0, -8(R1) F0 <- x[i-1] (load double) ADF F4, F0, F2 F4 <- F0 + F2; (flyttal) STD -8(R1), F4 x[i-1] <- F4 (store double) LDD F0, -16(R1) F0 <- x[i-2] (load double) ADF F4, F0, F2 F4 <- F0 + F2; (flyttal) STD -16(R1), F4 x[i-2] <- F4 (store double) SBI R1, R1, #24 R1 <- R1 24 BGEZ R1, Loop
Loop unrolling Minne1 Minne2 Flyttal1 Flyttal2 Heltal/hopp LDD F0,(R1) LDD F6,-8(R1) LDD F10,-16(R1) ADF F4, F0, F2 ADF F8, F6, F2 ADF F12, F10, F2 STD (R1), F4 STD 8(R1), F4 SBI R1, R1, #24 STD -8(R1), F4 BGEZ R1, Loop Tre iterationer tar 7 klockcykler Hela loopen tar: 320*7=2240 klockcykler Observationer: mer parallellism, exekveringstiden lägre, fortfarande operationer i instruktioner tomma.
Loop unrolling Om C-programmet skrivs så här: for (i=959; i>=0; i-=8){ x[i]=x[i]+s; x[i-1]=x[i-1]+s; x[i-2]=x[i-2]+s; x[i-3]=x[i-3]+s; x[i-4]=x[i-4]+s; x[i-5]=x[i-5]+s; x[i-6]=x[i-6]+s; x[i-7]=x[i-7]+s; } Blir det inga tomma cykler, men fortfarande några tomma operationer Åtta instruktioner tar 9 cykler; hela loopen tar: 120*9=1080 cykler (jämför med ingen unrolling (tid=5760 cykler)) Se nästa slide för schema
Loop unrolling Minne1 Minne2 Flyttal1 Flyttal2 Heltal/hopp LDD F0,(R1) LDD F10,-16(R1) LDD F6,-8(R1) LDD F14,-24(R1) LDD F10,-32(R1) LDD F10,-40(R1) ADF F4, F0, F2 ADF F8, F6, F2 LDD F10,-48(R1) LDD F10,-56(R1) ADF F12, F10, F2 ADF F16, F14, F2 ADF F20, F18, F2 ADF F24, F22, F2 STD (R1), F4 STD -8(R1), F4 ADF F28, F26, F2 ADF F32, F30, F2 STD -16(R1), F4 STD -24(R1), F4 STD -32(R1), F4 STD -40(R1), F4 SBI R1, R1, #64 STD 16(R1), F4 STD 8(R1), F4 BGEZ R1, Loop
Trace scheduling Trace scheduling är också en kompilatorteknik som syftar till att utnyttja parallellism över villkorliga hopp Problemet är att långa sekvenser av instruktioner måste undersökas för att detektera parallellism Blockgränser måste passeras Trace scheduling baseras på branch prediktering som görs under kompilering Trace scheduling görs i tre steg: Val av trace Schemaläggning av instruktioner Ersättning och kompensation
Trace scheduling Exempel: if (c!= 0) b = a/c; else b=0; h=0; f=g+h För en vanlig processor, skulle instruktions sekvensen se ut så här: LD R0, c RD<-0 (load word) BZ R0, else LD R1, a R1 <-a (load integer) DV R1, R1, R0 R1<-R1/R0 (integer) ST b, R1 b<-r1 (store word) BR Next Else: STI b, #0 b<-0 STI h, #0 h<-0 Next: LD R0, g R0<-g (load word) LD R1, h R1<-h (load word) AD R1, R1, R0 R1<-R1+R0 (integer) ST f.r1 f<-r1 (store word)
Trace scheduling If (c!=0) LD R0, c BZ R0, else) Else: Else: b=0 b=a/c STI b, #0 STI h, #0 LD R1, a DV R1, R1, R0 ST b, R1 h=0 Next: Next: f=g+h LD R0, g LD R1, h AD R1, R1, R0ST f.r1
Trace scheduling Schemaläggning av instruktioner Schemalägger instruktionerna i det valda spåret (trace) så parallellt som möjligt i VLIW processorn Exemplet (if (c!= 0) på VLIW processorn där valda spåret (trace) är schemalagt: Minne1 Minne2 Flyttal1 Flyttal2 Heltal/hopp LD R0,c LD R1, a LD R2, g LD R3, h BZ R0, Else ST b, R1 ST f, R3 DV R1, R1, R0 AD R3, R3, R2 BR End
Trace scheduling Replacement and compensation Store i next skriver över STI i else För att koden (instruktionerna) ska bli rätt oavsett om hopp tas eller inte behövs compensation code. I exemplet:» Att lägga till instruktionerna från Else räcker inte (STI b, #0, STI h, #0) Minne1 Minne2 Flyttal1 Flyttal2 Heltal/hopp LD R0,c LD R1, a LD R2, g LD R3, h BZ R0, Else Tilldelning av DV R1, R1, R0 Next: ST b, R1 h i else tar inte effekt AD R3, R3, R2 ST f, R3 BR End
Trace scheduling Korrekt kod: Minne1 Minne2 Flyttal1 Flyttal2 Heltal/hopp LD R0,c LD R1, a LD R2, g LD R3, h BZ R0, Else DV R1, R1, R0 Next: ST b, R1 AD R3, R3, R2 ST f, R3 BR End Else: STI b, #0 STI h, #0 BR Next STI R3, #0 Compensation code pga LD R3, h har flyttats upp
Branch predication Instruktion 1 Instruktion 2 Instruktion 3 (branch) Kompilatorn tilldelar varje instruktion ett predikate (villkor) <p1> Instruktion 4 <p2> Instruktion 4 <p1> Instruktion 5 <p2> Instruktion 5 <p1> Instruktion 6 <p2> Instruktion 6 FU1 FU2 FU3 Instruktion 1 Instruktion 2 Instruktion 3 <p1> Instruktion 4 <p1> Instruktion 6 <p2> Instruktion 8 Processor kan exekvera alla instruktioner men bara de med sant predikat kommer bli synliga (committed) <p2> Instruktion 7 <p2> Instruktion 9 <p1> Instruktion 5
Speculativ load Load från minnet ska placeras så att fördröjningar minimeras. Istället för att skriva kod så här: ADD R4,R3 R4 <- R4+R3 LOAD R1,X Laddar R1 med data som finns på adress X ADD R2,R1 R2 <- R2+R1 OBS! Fördröjning! SUB R2,R4 R2 <- R2+R4 Kan man ändra till: LOAD R1,X Laddar R1 med data som finns på adress X ADD R4,R3 R4 <- R4+R3 ADD R2,R1 R2 <- R2+R1 SUB R2,R4 R2 <- R2+R4
Speculativ load Vad händer om en LOAD flyttas från ena branch i en if-then-else branch till före (if-then-else) Det gör att LOAD exekveras oavsett vilken väg (branch) som tas. Inget problem om resurser finns tillgängliga Men, om LOAD ger t ex sidfel (page fault) och tar mycket tid (en hel sida måste läsas in från sekundärminnet till primärminnet) Om LOAD inte behövdes (eftersom den alltid exekverades trots att det bara var ena branchen som behövde den), så finns det en risk för att tid slösas bort. Önskemål; hantera exceptions bara när det laddade (loaded) värde verkligen behövs
Speculativ load Med spekulativ load ersätts en load instruktion med två instruktioner: En spekulativ load (LD.s) som gör minnesläsning och kontrollerar om en exception (t ex sidfel) genereras. Ett eventuellt exception signaleras dock inte till operativ systemet En kontroll instruktion (CHK.s) som signalerar om en exception har detekterats av den spekulativa load» Om ingen exception har detekterats, fortsätt som vanligt» Kontroll instruktionen placeras på den plats där den ursprungliga LOAD instruktionen var Med spekulativ load kan är LOAD instruktioner flyttas mellan branch gränser. Exceptions hanteras bara när load värdet behövs.
Speculativ load Instruktion 1 Instruktion 2 Instruktion 3 (branch) <p1> Instruktion 4 <p1> Instruktion 5 <p1> Instruktion 6 <p2> Instruktion 4 <p2> Load <p2> Instruktion 6 Denna instruktionen kan ta lång tid och fördröja exekveringen FU1 FU2 FU3 Instruktion 1 Instruktion 2 Instruktion 3 <p1> Instruktion 4 <p1> Instruktion 6 <p2> Instruktion 8 <p2> Instruktion 7 <p2> Instruktion 9 <p1> Instruktion 5
Speculativ load Instruktion 1 Spekulativt load Load görs om det inte blir någon exception Instruktion 3 (branch) <p1> Instruktion 4 <p1> Instruktion 5 <p1> Instruktion 6 <p2> Instruktion 4 <p2> Spekulativ check <p2> Instruktion 6 Exception hantering görs här om det behövs FU1 FU2 FU3 Instruktion 1 Instruktion 2 Instruktion 3 <p1> Instruktion 4 <p1> Instruktion 6 <p2> Instruktion 8 <p2> Instruktion 7 <p2> Instruktion 9 <p1> Instruktion 5
Sammanfattning För att möta det konstanta behov av prestanda så har super scalar processorer blivit väldigt komplexa VLIW processorer undviker komplexitet genom att låta kompilatorn hantera detektering av parallellism Operationsnivå parallellism ges direkt av instruktionerna i VLIW processorer I och med att en VLIW processor inte behöver hårdvara för att hantera parallellism kan VLIW processorer ha fler funktionella enheter för att öka möjligheter till parallellism. Dock, leder det till att behoven ökar för fler register och mer bandbredd för kommunikation
Sammanfattning För att hålla alla funktionella enheter aktiva, måste kompilatorer för VLIW vara offensiva i sökandet efter parallellism. Tekniker som används är: Loop unrolling; Trace scheduling (villkorliga hopp); Branch predication Speculativ load