Liukuhihnaprosessori¶
Osaamistavoitteet: Suorittimen liukuhihnatoteutuksen periaatteet sekä käskyjen ja datan riippuvuuksien aiheuttamista ongelmatilanteista selviäminen. Liukuhihnan suorituskyky.
Aiemmin esittelimme sekventiaalisen prosessorin, jossa jokainen käsky suoritetaan yhden kellojakson aikana. Tässä on kuitenkin haittana se, että koko käskyn suoritusaika määrittää kellojakson pituuden. Esimerkiksi von Neumann-arkkitehtuureissa pitää tehdä (teoriassa) kaksi erillistä muistiosoitusta per käsky: käsky ja data. Lisäksi sekventiaalisen prosessorin eri vaiheita toteuttavat osajärjestelmät ovat suurimman osan kellojaksosta tekemättä mitään.
Prosessorin toiminnan tehostamiseksi on esitetty osajärjestelmien liukuhihnoittamista (engl. pipeline), jossa osajärjestelmät ovat kokoajan käytössä, suorittaen peräkkäisten käskyjen eri vaiheita. Kun käskyt etenevät vaiheesta toiseen, niitä seuraava käsky etenee nykyisen käskyn edelliseen vaiheeseen. Liukuhihnoitusta käytetään nykyisin paljon sulautettujen järjestelmien tai signaalikäsittely (DSP)-prosessoreissa.
Kuvassa alla esimerkki liukuhihnasta y86-prosessorissa. Kirjaimet viittaavat käskyn suorituksen eri vaiheisiin. Huomataan, ettei PC update-vaihetta enää ole, josta lisää hetken päästä.
Nyt kolme käskyä sekventiaalisessa prosessorissa veisivät 15 (3 x 5) aikayksikköä. Liukuhihnalla ne saadaan suoritetuksi 7:ssa aikayksikössä. Aika merkittävä parannus ohjelman suoritusaikaan!
Mutta ongelmaksi tulee, kun liukuhihnan idea viedään mikroarkkitehtuuriin asti, että osajärjestelmät käyttäisivät eri käskyjen eri vaiheissa samoja signaaleja. Esimerkiksi y86:sessa Fetch-vaiheen valC-signaali (vakioarvo) menee käskystä riippuen Execute-vaiheeseen saakka. Seuraava käsky Fetch-vaiheessa voi myös tarvita valC-rekisteriä vakioarvon välittämiseen. Samoin Decode-vaiheessa asetetaan jokaiselle käskylle sen input- ja output-rekisterit.
Likuhihnatoteutuksissa tämä ongelma hoituu asettamalla liukuhihnarekistereitä vaiheiden väliin, joihin joka vaiheessa saadut output-arvot tallentuvat ja joita käytetään seuraavan vaiheen sisääntuloina synkronoidusti. Näin välitulokset seuraavat käskyä vaihe vaiheelta ilman, että ne sotkeutuisivat muiden käskyjen vastaaviin signaaleihin. Nyt useaa eri käskyä voidaan suorittaa samanaikaisesti samassa mikroarkkitehtuurissa. Kuvassa alla jokaista vaihetta edeltää sen oma liukuhihnarekisteri.
Ihan vielä emme kaivaudu y86:sen liukuhihnatoteutukseen, vaan katsotaan ensin millaisia vaikutuksia liukuhihnoituksella on yleisesti ohjelmien suoritukseen. Käskyjen peräkkäinen suoritus samassa mikroarkkitehtuurissa kun ei ole ihan ongelmatonta.
Hasardit¶
Liukuhihnatoteutuksissa yleinen ongelma on, että eri käskyjen välillä on riippuvuuksia. Kun riippuvuuksia on käskyjen operadien välillä, puhutaan data-hasardista (engl. data hazard). Esimerkiksi toisen käskyn output voi olla toisen input. Kun riippuvuuksia on käskyjen välillä, puhutaan kontrolli-hasardista (engl. control hazard). (Käytämme parempien suomenkielisten termien puitteissa väännöksiä englanninkielestä.) Seuraavaksi katsotaan tarkemmin millaisia riippuvuuksia tyypillisesti on ja miten niistä selvitään.
Datankäsittelyn hasardit¶
Tarkastellaan esimerkkikoodia, jossa ei sekventiaalisella prosessorilla suoritettaessa ole mitään ihmeellistä.
irmovq $10,%rdx # rdx=10
irmovq $3,%rax # rax=3
addq %rdx,%rax # rax=rax+rdx
halt
Kun koodi ajetaan liukuhihnaprosessorissa, kohtaamme ongelman. Kaksi ensimmäistä käskyä eivät ehdi Write back-vaiheeseen, jossa niiden arvot kirjoitettaisiin kohderekistereihin, ennenkuin kolmas käsky tarvitsee niiden arvoja Decode-vaiheessa.
(Kuvassa vihreä väri tarkoittaa, että milloin arvo on saatavilla ja punainen milloin sitä tarvittaisiin.)
Data-hasardien selvittämiseksi on onneksi käytössä useita keinoja.
Viivyttäminen¶
Käskyn suoritusta voidaan viivyttää (engl. delay) lisäämällä väliin
nop
-käskyjä, kunnes inputit ovat saatavilla. nop-käsky on tässä kätevä, koska se ei muuta suorittimen rekisterien sisältöjä mitenkään. Lisäämällä väliin 3 nop-käskyä, saadaan kahden käskyn Write Back-vaiheet suoritettua ennenkuin arvoja tarvitaan kolmannen käskyn Decode-vaiheessa.
Stalling¶
Tässä käsky jää kontrollilogiikan ohjaamana suorittamaan Decode-vaihetta kunnes sen inputit ovat saatavilla. Tämä voidaan tehdä kontrollilogiikalla jäädyttämällä PC-rekisterin arvo ja lisäämällä ohjelmaan väliin bubbleja, jotka samoin kuin nop, eivät muuta rekisterien arvoja. Erona on se, ettei bubble ole käsky. (Emme voi palata aiempaan kellojaksoon, jossa bubblea vastaavan käskyn Fetch-vaihe olisi suoritettavana.)
Kuvassa siis
addq
-käskyn suorituksessa havaitaan etteivät inputit ole vielä saatavilla kellojaksolla 4, joten jäädytetään PC ja lisätään väliin bubble:ja alkaen kellojaksosta 5. Seurauksena on, että kaikki muutkin tätä seuraavat käskyt jäävät suorittamaan sen hetkistä vaihettaan. Forwarding¶
Haittana aiemmissa keinoissa on, että lisäämällä väliin tyhjiä käskyjä suorittimen suorituskyky ei ole optimaalinen, vaan kellojaksoja hukataan.
Forwarding (tai bypassing) pyrkii viiveestä eroon siten, että kontrollilogiikka yhdistää edellisten käskyjen välitulokset nykyisen käskyn input-signaaleihin. Toisinsanoen, jos käskyn inputtia ei ole vielä saatavilla, tarkistetaan olisiko tulos jossain liukuhihnalla jo laskettu!
Tietenkin tämä voidaan tehdä vain saman kellonjakson aikana tarjolla oleville signaaleille.
Kuvassa siis ensimmäisen ja toisen käskyn välitulokset kellojaksosta 4 on kytketty
addq
-käskyn Decode-vaiheen input-signaaleiksi. Koska input-arvoja käytetään vasta Execute-vaiheessa, ne ehditään tässä kohti lukea. Verrattuna muihin keinoihin Forwarding edellyttää vaativampaa kontrollilogiikan toteutusta.
Load / Use-hasardi¶
Kun käsky tekee muistiosoituksia hakeakseen inputin rekisterikutsujen sijaan, voi syntyä load/use-hasardi.
Nyt
addq
-kutsulle ei saada asetettua molempia operandeja kellojaksossa 7. Neljännen käskyn (irmovq) output on saatavilla, mutta viidennen käskyn output saadaan vasta Memory-vaiheen jälkeen. Emme siis voi käyttää Forwarding:ia, koska inputtien pitäisi olla saatavilla kellojaksolla 7. Nyt
mrmovq
-käskyn output on tarjolla vasta kellojaksolla 8, josta emme voi siirtyä ajassa taaksepäin kellojaksoon 7. Tilanne ratkaistaan yhdistämällä Stalling ja Forwarding. Eli
addq
-käsky suorittaisi Decode-vaihettaan, kunnes molemmat inputit on saatavilla Forwarding:iä varten. Liukuhihnan kontrollivirheet¶
Kontrolli-hasardi (engl. control hazard) tarkoittaa sitä, että käskyjen välillä on riippuvuuksia (engl. control dependency). Riippuvuus tässä tarkoittaa, että käskyn tulos vaikuttaa siihen, mistä koodin suoritusta jatketaan. Eli mikä on seuraavan käskyn muistiosoite.
Aliohjelmahasardi¶
Tarkastellaan
ret
-kutsusta johtuvaa mahdollista hasardia koodiesimerkin kautta.call funktio irmovq $10,%rdx halt funktio: irmovq $3, %&rcx ret
Alla ohjelman suoritus liukuhihnalla.
Nyt, aliohjelmaan hypätään jokatapauksessa. Mutta, paluuosoite on tiedossa vasta
ret
-käskyn Write back-vaiheessa. Ratkaisu tässäkin on lisätä väliin
nop
-käskyjä, kunnes voidaan hyödyntää Forwarding:ia Fetch-vaiheeseen. Ehdollinen hyppy¶
Ehdollinen hyppy on suorittimissa toteutettu kahdella tavalla ennakoivasti. Voidaan ajatella, että ehdollinen hyppy toteutuu aina tai hyppy ei toteudu. Ongelma molemmissa on se, että liukuhihnalle pyritään hakemaan etukäteen käskyjä, mutta riippuen ehdon tuloksesta saatetaan noutaa vääriä käskyjä, jos tulos on eri kuin ennustettu.
Koodiesimerkki ehdollisesta hypystä y86:sessa.
0000: xorq %rax,%rax 0002: jne target # Oletus: hyppy toteutuu aina! 000b: irmovq $1,%rax 0015: halt 0016: target: 0016: irmopq $2,%rdx 0020: irmovq $3,%rbx 002a: ret
Ja koodin suoritus alla, punaiset käskyt on siis haettu ennustetusta seuraavasta osoitteesta.
Nyt, oikea hyppyosoite selviää vasta käskyn Execute-vaiheessa.
Ratkaisuna on ensin poistaa liukuhihnalta väärät käskyt ja sitten lisätä bubble:ja tilalle. Kuvassa harmaiden vaiheiden aikana suoritin ei voi tehdä mitään hyödyllistä muissa käskyissä eikä viedä väliin uusia
nop
-käskyjä. Käskyjen uudelleenjärjestely¶
Joskus on mahdollista suorittimen (tai ohjelmoijan..) muokata tai muuttaa lennosta ohjelman suoritusta niin, että
nop
-käskyjen tai bubble:n tilalta suoritettaisiinkin ohjelman käskyjä, joissa ei ole riippuvuuksia jumiutuneisiin käskyihin. Kuten arvata saattaa, tämä vaatiikin jo varsin edistynyttä kontrolliosaa.. y86-liukuhihnatoteutus¶
Ok, hasardeista selvittyämme ymmärrämme (ja osaamme ehkä arvostaa..) y86-liukuhihnatoteutusta.
1. Fetch¶
Materiaalin ensimmäisessä kuvassa huomattiin, ettei liukuhihnasuorittimessa ole PC update-vaihetta. Nyt, toteutuksessa se siiretään Fetch-vaiheeseen, jotta seuraavan käskyn osoite haettaisiin mahdollisimman myöhään. Siitäkin huolimatta, liukuhihnaprosessoreissa tarvitaan muistiosoitteen ennustamista (engl. branch prediction) suorituskyvyn maksimoimiseksi.
Tätä varten tarvitaan liukuhihnarekisteri
F:pred_PC
, jonne arvaamme seuraavan käskyn muistiosoitteen:- Jos käsky ei ole hyppy, seuraavan käskyn osoite on
valP
:ssa. - Koska oletamme, että ehto toteutuu aina, rekisteriin on tallentunut edellisessä käskyssä annettu osoite signaaleista
valC
taivalP
. - Jos ehto ei toteudu, seuraava osoite saadaan edellisen käskyn signaaleista
valA
taivalM
joko Memory- tai Write Back-vaiheista (valitaan vastaavan icode:n ja cnd:n perusteella).
Etuliite
M_
tarkoittaa, että osoite tulee Memory-vaiheen käskyn signaalista ja W_
että osoite tulee Write back-vaiheesta. 2. Decode¶
Decode-vaihe on muuten sama kuin sekventiaalisessa prosessorissa, mutta kontrollilogiikassa on enemmän vaihtoehtoja signaaleille
valA
, valB
, joka mahdollistaa aiempien käskyjen tulosten vaiheista Execute, Memory ja Write back käyttämisen nykyisen käskyn operandeina. Kuvassa x_
viittaa johonkin aiempaan vaiheeseen E,M tai W. Ok, mutta mistä tiedetään mikä näistä signaaleista valitaan? y86-liukuhihnaprosessorissa on asetettu prioriteetti eri signaaleille ja sen mukaan valitaan arvo käskystä, jonka oma vaihe on lähinnä omaa vaihetta.
Esimerkiksi, jos tarjolla on arvot vaiheista Execute tai Memory, valitaan Execute-vaiheen käskyn signaalit, koska se on lähinnä omaa vaihetta Decode. Alla oleva koodi ei toimisi oikein, jos näin ei tehtäisi.
irmovq $10,%rdx # Vaihe M: -> M_valE = 10
irmovq $3,%rdx # Vaihe E: -> E_valE = 3
rrmovq %rdx,%rax # Vaihe D: valA <- E_valE = 3
3. Execute¶
Tämä vaihe on identtinen sekventiaalisen prosessorin kanssa. Huomataan kuitenkin, että ulos lähtee enemmän signaaleja, joita voidaan käyttää seuraavien käskyjen operandeina takaisinkytkennän kautta.
4. Memory¶
Vaihe on myös identtinen sekventiaalisen prosessorin kanssa ja ulos lähtee enemmän signaaleja seuraavien käskyjen operandeiksi.
5. Write back¶
Vaihe on myös identtinen sekventiaalisen prosessorin kanssa ja ulos lähtee enemmän signaaleja seuraavien käskyjen operandeiksi.
Liukuhihnan suorituskyky¶
Liukuhihnaprosessorin suorituskyvylle voimme esittää kaksi laskennallista parametriä:
- Latenssi (engl. latency), joka kertoo käskyn suoritusajan, yksikkö nykyään pikosekunti (ps)
- Suoritusteho (engl. throughput), eli suoritettujen käskyjen määrä sekunnissa, yksikkö IPS (instructions per second)
Voimme laatia erilaisia liukuhihnaratkaisuja, joissa maksimoidaan liukuhihnan suoritustehoa. Mikroarkkitehtuurissa voidaan jakaa käskyn suoritus niin moneen vaiheeseen (teoriassa) kun on tarpeen ja viedä väliin niin monta liukuhintarekisteriä kun vaiheita tarvitaan. Nykyisissä suoritintoteutuksissa esimerkiksi on 15 vaihetta!
Alla esimerkkivertailu sekventiaalisen ja liukuhihnaprosessorin suoritustehon erosta.
1. Sekventiaalinen prosessori
- Latenssi: käskyn suoritusaika (300ps) + tuloksen kirjoittaminen rekisteriin (20ps) = 320ps
- Suoritusteho:
1 ---------- = 3,125 GIPS (giga-IPS) 320*10^-12
2. Liukuhihnaprosessori, jossa suoritus jaettu kolmeen vaiheeseen.
- Latenssi: vaiheen 3 x (suoritusaika (kesto 100ps) + tuloksen kirjoittaminen liukuhihnarekisteriin (20ps)) = 360ps
- Suoritusteho:
1 ---------- = 8,333 GIPS 120*10^-12
Nyt,
8,333 GIPS ---------- = 2,67 3,125 GIPS
Tuloksista nähdään, että liukuhihnaprosessori oli huomattavasti suorituskykyisempi koko ohjelman suorituksen kannalta, vaikka käskyjen latenssit ovatkin hieman isompia:
360 / 320 = 1.125
.Lopuksi¶
Liukuhihnatoteutuksista on siis merkittävästi hyötyä suorittimen suorituskyvylle, mutta hintana on vaativampi kontrolliosan toteutus.
Suorituskykyä tarkastelemme vielä lisää myöhemmässä materiaalissa.
Anna palautetta
Kommentteja materiaalista?