Toru (arvutustehnika)

**Toru** ehk **konveier** (inglise keeles *pipeline*) on kogum jadamisi ühendatud andmete töötlemise elemente, kus ühe elemendi väljund on järgmise elemendi sisendiks. Tihti tehakse sealjuures operatsioone samaaegselt.

Konveierite kasutamine on tänapäeva maailmas laialdaselt levinud. Kõige lihtsamaks näiteks võib siia tuua autotööstuse, kus konveierliini peal tehakse iga protsess oma tööjaama juures. Ühes kohas lisatakse juurde mootor, sealt edasi lisatakse rattad jne. Selle eeliseks on see, et kui ühele autole saab mingi detail lisatud saab selle mööda liini edasi saata ning asuda tööle uue auto kallal.

Arvutitehnikaga seotud torud:

# Tavalised RISC torud, mis on kasutusel kesktöötlemisseadmetes ja muudes mikroprotsessorites. Neid kasutatakse peamiselt selleks, et täitsa samaaegselt mitmeid käske ühes vooluringis, mis on omakorda jaotatud etappideks, kus iga etapp töötleb mingit kindlat osa ette antud juhisest, andes samal ajal edasi osalisi tulemusi järgmisse staadiumi.
# Graafilised torud, mis on kasutusel graafikaprotsessorites (GPU-des). Sarnaselt kesktöötlemisseadmete torudega on graafilised torud jaotatud etappideks, mille alusel käske täidetakse. Üldiselt on selliste torude eesmärgiks töödelda eelnevalt loodud 3D-mudeli põhjal pilt kuvarile. Kuna selliste etappide teostus sõltub suuremas osas sellest, mis tarkvara ja riistvara kasutatakse, ei ole olemas ka ühte universaalset graafilist toru, mis oleks sobiv kõikideks juhtudeks. 
# Tarkvara torud, mis koosnevad üksteise järel paiknevatest arvuti täidetavatest käskudest (käsud, töötavad programmid, ülesanded, lõimed jne).
# HTTP torud, kus kasutatakse tehnikat mitme HTTP-taotluse saatmiseks läbi ühe ja sama TCP ühenduse. Selle tõttu ei pea ootama kui eelnev taotlus lõpetatakse vaid saab saata uue peale.

## Tööpõhimõte

Arvutis, kus on kasutusel torud liiguvad käsud läbi kesktöötlemisseadmete etappide kaupa. Tavaliselt on sellistes arvutites *pipeline* registrid, kuhu pärast igat etappi salvestatakse informatsiooni täidetud käskudest ning tehtud arvutustest selleks, et saaks minna edasi järgmise sammu juurde. 

Selline korraldus laseb CPU-l täita käske ühe takti jooksul. On levinud, et paaris arvulised etapid töötavad ristkülik signaali ühes ääres ning paaritu arvulised etapid signaali teises ääres. Selline korraldus võimaldab suuremat kesktöötlemisseadme läbilaskmisvõimet kindlal taktsagedusel, kuid võib suurendada ka latentsust. 

Käsu töötlemise saab jagada näiteks viieks etapiks:
* käsu lugemine (IF)
* käsu dekodeerimine (ID)
* käsu täitmine (EX)
* mälu poole pöördumine (MEM)
* tulemuste salvestamine (WB)
Sellise jaotuse tulemusena saame protsessoris korraga täita viit käsku, kuigi iga käsu täitmine on igal ajahetkel erinevas etapis.
Ühte käsku loeme, sellele eelnenud käsku dekodeerime, veel varasemat käsku täidame ja sellele eelnenud käsu tulemusi kirjutame salvestuskohta (mällu, ...).

Iga etapp täidetakse ära ühe takti jooksul. Eri etappide täitmisele kuluv aeg on erinev.
Kui mõne etapiga saadakse kiiremini ühele poole, on seade ülejäänud aja ooterežiimis. Seega on toru efektiivne ligikaudu ühepikkuste etappide korral.
Kriitiline operatsioon on mälust lugemine:
* Põhimälust lugemine on tavaliselt ligikaudu 10 korda aegavõtvam kui protsessorisisesed operatsioonid.
* Protsessoriga samal kiibil paiknev vahemälu suudab töötada enam-vähem sama kiirusega kui protsessor ise.
Seetõttu on iga põhimälust lugemise ajal protsessori töö teatud ajaks seisatud (ingl *processor stalls*).

Käsu lugemine (ingl *instruction fetch*) toimub olenevalt selle asukohast *program counter*i kaudu, mis näitab, kusmaal programm parasjagu on. Sellest võib mõelda kui registrist, mis hoiab endas käsu aadressi. See suubub *PC predictorisse,* mis omakorda saadab selle käsu lugemise etappi. Samal ajal suurendab see oma väärtust 4 võrra (tuleneb sellest, et käsud on 4 baiti pikad) ning ennustab järgmise käsu aadressi. Kui tegemist on hargnemise või hüppega, siis *PC predictori* ennustus on vale. Uuemad arvutid kasutavad juba rohkem arenenuid süsteeme selle probleemiga tegelemiseks. 

Käsu dekodeerimise (*instruction decode*) ülesandeks on saada aru, mida tuleb torus teha. Üldiselt kõige vähem aeganõudvam etapp.

Käsu täitmise (Execute) etapis toimub arvudega töötlemine. Tüüpiliselt on selles elemendis aritmeetika-loogikaplokk, mis viib läbi boolean operatsioone (*and, or, not, nand, nor* jne) ning samuti teeb liitmis- ja lahutamistehteid. 

## Ajalugu

Torude kasutamine algas 1970. aastate lõpus enamasti superarvutites, näiteks *vector*-protsessorites, ja *array*-protsessorites. Üks esimesi superarvuteid oli Cyper series, mille ehitas Control Data Corporation. Selle peamine arhitekt oli Seymour Cray. Tema tegi XMP-superarvutid, mis kasutasid torusid korrutamis-, liitmis- ja lahutamisfunktsioonideks. Hiljem lõi selline ettevõte nagu Star technologies paralleelismi, kus töötasid mitu toru käsku samaaegselt. Torusid sai peale superarvutite kasutada ka iga muud tüüpi arvutites. 

## Riskid

* Strukturaalne risk – tekib näiteks siis, kui kaks seadet soovivad sama sammu ajal kasutada sama riistvararessurssi.
* Andmerisk – kahe järjestikuse käsu puhul, mida torus täidetakse, ei pruugi esimese käsu tulemus veel olla kättesaadav enne järgmise käsu alustamist.
* Käsurisk – hargnemise käsk kutsub toruga arvutis esile seisaku, kuna enne hargnemist on juba alustatud ilma hargnemiseta täitmisele tulevate käskude sisselugemist, dekodeerimist või täitmist.

Andmeriskide (*data hazards*) vältimiseks on võimalik kasutada kahte lahendust.

Esimeseks lahenduseks on kasutada *bypassi* ehk eesti keeles šunti. Seda tuntakse ka veel teise nime all, milleks on argumendi edastamine *(operand forwarding).*

 SUB r3,r4 -> r10     ; Writes r3 - r4 to r10
 AND r10,r3 -> r11    ; Writes r10 & r3 to r11

Joonisel on näidatud, kuidas liiguvad käsud kasutades *operand forwarding* tehnikat läbi toru.

Tavalises torus, kus ei kasutata bypass-tehnikat, loetakse käsud sisse järgmiselt:

Kolmanda takti ajal arvutab käsk SUB (lahutamine) registrisse r10 uue väärtuse. Sama takti ajal dekodeeritakse käsk AND ning loetakse registrist sisse r10-ne väärtus. Kuigi eelnevalt läbi viidud SUB käsk ei ole veel kirjutatud r10-sse. Alles viienda takti ajal kirjutatakse uus väärtus sisse, mistõttu on eelnevalt registrist loetud väärtus vale. 

Selle olukorra vältimiseks tuleb andmed, mis arvutati SUB käsu poolt, viia tagasi käsu täitmise (EX) etappi. Lahendusena saab kasutada mõnda *bypass* multiplekserit, mis asetatakse käsu dekodeerimise etappi lõppu, mille väljundid suunatakse ALU sisenditesse. Iga multiplekser valib kolme võimaluse vahel nagu on näidatud joonisel. 

Dekodeerimise etapi loogika seob omavahel andmeid, mis on sisse kirjutatud käsu lugemisel ja dekodeerimisel. Neid võrreldakse registritega, mis on loetud käsu lugemise ja dekodeerimise ajal ( ehk eristab sisse loetud ning kirjutatud andmeid) ning võrdleb neid omavahel. Selle tulemusena valib multiplekser kõige hiljutisemad andmed ning toimub nende edastamine. Sellised *bypass* multiplekserid võimaldavad läbi viia lihtsaid käske vaid ALU, multipleksri ja flip-flopi latentsusega.

Toru ei tee üksiku käsu täitmist kiiremaks. Suureneb täidetud käskude arv ajaühikus. Teoreetiline ülempiir on “üks käsk iga sammu ajal”, praktikas ei ole see saavutatav.

Teiseks võimaluseks riskide vältimiseks on toru blokeerida (*pipeline interlock*).

Kasutame illustreerimiseks järgmist näidet.
 LD  adr    -> r10
 AND r10,r3 -> r11

Andmed, mis loetakse sisse aadressilt **adr**, on valed seni, kuni pole täidetud andmete salvestamise käsk. Selle aja möödudes on AND käsk läbinud juba ALU ja kuna andmeid saab saata ainult ajas edasi, siis esimesel pildid toodud lahendus on vale. Selle lahendamiseks saab ühe takti sisse viia viivituse, mis siis viivitaks AND käsku ühe takti võrra. Nüüd loetakse alguses kasu dekodeerimise ajal sisse ikkagi valed andmed, aga kuna oleme viivitanud töö tsüklit ühe takti võrra, siis annab see aega multiplekseril lugeda sisse mälust uued ning õiged andmed. Käsu täitmise (EX), lugemise (IF) ning tulemuste salvestamise (WB) etapid näevad seda kui no-operation käsku (NOP).

Käsuriskide lahendamiseks on neli võimalikku moodust.

Hargnemine vähetõenäoline – sellises olukorras võetakse käsk kindlasti pärast hargnemist käsu vahemälust (*instruction cache*), aga täidetakse ainult sellisel juhul kui hargnemist ei võeta. Kui hargnemist ei toimu püsib toru nii öelda täis. Hargnemise esinemise korral märgitakse käsk kui no-operation ning võimalus täita käsk ühe takti jooksul kaob.

Hargnemine tõenäoline – sellises olukorras võetakse käsk kindlasti pärast hargnemist käsu vahemälust, aga täidetakse ainult sellisel juhul kui hargnemine toimub. Kompilaator saab alati täita ajastuspesa sellises harus ja kuna suurema tõenäosusega ka hargnemine toimub, siis on sel juhul väiksem IPC karistus kui eelmisel variandil.

Ajastuspesa – sellises olukorras võetakse käsk kindlasti pärast hargnemist, käsu vahemälust ja alati täidetakse see isegi siis kui hargnemist ei toimu. 

Hargnemiste ennustamine – paralleelselt sellega, et otsitakse käske, proovitakse arvata kas käsk on hargnemine või hüpe ning leitakse ka sihtmärk. Kui oletus on vale, kustutatakse valesti valitud sihtmärk.

## Harud

Kui toru puutub kokku harudega (*branches*), siis sellega koos käivad ka riskid. Kui tavaliselt on ette antud üks käsklus, siis harudega võib esineda neid rohkem kui üks, mistõttu tekivad ka erinevad probleemid. Kui protsessor suudab haruga tegeleda ühe takti jooksul, siis probleeme ei teki, aga kui ei suuda siis see jääbki käske võtma järjestikku. 

Ajatuspesa (*branch delay slot*) – aadress, mis asub vahetult hargnemiskäsu järel. Neid võib olla rohkem kui üks. Hargnemisele järgnevad käsud loetakse alati protsessorisse (selletõttu et antud hetkel ei ole veel teada kas hargnemine leiab aset). Selliste hargnemiste täitmine ei põhjusta protsessorile täiendavat ajakulu.

Tingimuslik hargnemine. Siin sõltub hargnemine või mittehargnemine tingimuslippude väärtusest, mida me ei pruugi enne teiste käskude täitmise lõpetamist teada. Kõige lihtsam oleks siinkohal eeldada, et hargnemist ei toimu. 

## Viited