calcolatori 23

Upload: vittorio-troise

Post on 21-Feb-2018

218 views

Category:

Documents


0 download

TRANSCRIPT

  • 7/24/2019 Calcolatori 23

    1/7

    Torniamo un attimo sul loop unrolling per vedere come gestire una problematica.

    Abbiamo un vettore che prevede 100 elementi. Facciamo uno srotolamento di questo vettore per 4

    volte e, facendolo per 4 volte, avremo che il loop srotolato richieder 25 iterazioni.Se il vettore

    avesse 101 elementi, come faccio a srotolarlo per 4?Questo un problema normale, nel senso

    che nessuno ci garantisce che il programma principale ha, un numero di iterazioni, che pu essere

    gestito secondo uno srotolamento, che potrebbe anche non esistere. Potrebbe anche capitare diavere un loop parametricoNel codice abbiamo un loop che, il compilatore si trova a tradurre, di

    un'iterazione che va da 1 a N. Nel compilare un FOR da 1 a N, il compilatore non sa quanto vale N

    al momento dellesecuzione, n per quanto bisogna srotolare. Quest operazione abbastanza

    semplice da gestire perch, il compilatore, quando trova un loop di N generiche iterazioni e se ha

    deciso di srotolarlo per 4, per esempio, crea uncodice compilatodove la 1 parte si chiama:

    gestione del residuoe poi c' una 2 parte che contiene il nostro loop srotolato. Riassumendo, il

    nostro problema originale prevede N iterazioni; lo vogliamo srotolare per 4, ma non sappiamo

    quanto vale N. Il compilatore, come 1 cosa, nel tradurre il codice, considera N e calcola il residuo

    di N rispetto al fattore di srotolamento 4. N una variabile che pu valere un numero qualsiasi ad

    ogni accesso nel programma, ma quando parto per eseguire literazione nota. Esseno nota,

    quando inizializzo il valore sul loop, posso fare il resto della divisione di N/4. 4 una potenza del 2

    e quindi devo considerare gli ultimi 2k bit ( se k log in base 2 di 4). Dato N, se prendo gli ultimi 2

    bit, abbiamo il resto della divisione N/4.

    La 1 cosa che il sistema fa calcolare l'operazione attraverso una maschera. Supponiamo che

    R6 contenga N.

    ANDI R5 R6 3// Sto mettendo in R5 il residuo. Devo fare un loop non srotolato di R5 iterazioni, se

    quello fosse 103, il residuo verrebbe proprio 3. Facciamo il loop di 3 iterazioni non srotolato e

    resteranno 100 che potremo srotolare.

    Contemporaneamente faccio la divisione di R6 per 4 per saperequante volte questo loopsrotolato dovr essere eseguito.

    DIVI R4 R6 4// sto mettendo in R4 N/4

    Sappiamo che il vettore su cui dobbiamo operare, parte da un certo indirizzo e si sviluppa da

    quell'indirizzo + R6 che contiene N *8 (se abbiamo un vettore di dati da 8 byte).

    Calcoliamo R6*8 ( R3 la dimensione del vettore in byte su cui decido di operare per poi

    spostarmi su questo vettore sempre di 8):

    SLLI R3 R6 3//se il loop gestisce un unico vettore, avremo che il vettore partir da un certo

    indirizzo e si svilupper da quellindirizzo per la dimensione di R3.

    Se dobbiamo gestire diversi vettori, per esempio 2 sorgenti e 1 risultato, abbiamo un altro indirizzo

    di inizio, ma sempre R3 funger da puntatore ai vettori. In generale va bene qualsiasi numero di

    vettori, purch sianoomogeneiElementisono tuttida 8 byte, possiamo usare un solo

    puntatore. Non ha senso usare pi puntatori, non tanto perch spreco 2 registri, ma perch per

    ogni iterazione dovr decrementare 2 registri. E pi intelligente ragionare con un unico puntatore

    che viene gestito. Avremo, quindi, nel corpo del loop unistruzione che andr a puntare ad un certo

    vettore a partire da 1000 con R3, unaltra istruzione per prelever un operando da un vettore,

    sempre considerando R3, e via dicendo. Sottrarremo solo ad R3, 8 nel caso di loop non srotolato e

    8x4 nel caso della gestione del puntatore nella parte srotolata. Questa parte che gestisce il loop

    non srotolato conviene gestirla sulla parteterminaledel vettore perch, se ho il vettore di 103

    elementi, conviene gestire gli ultimi 3 come residuo e gli altri 25 gruppi da 4 come gestione delloop srotolato. Se il vettore inizia da 1000, il nostro prelievo delloperando, che fa parte di questa

    gestione del residuo, sar 1000-8 + R3. Se il vettore inizia da 1000, il 2 1008, ecc. Lultimo

  • 7/24/2019 Calcolatori 23

    2/7

    1000-8+R3 ( se ha 1 solo elemento quindi R3=8, 1000-8+8indirizzo ultimo elemento del vettore.

    Quando abbiamo offset indirizzo del 1 elemento, lultimo non 1000 + dimensione, perch se lo

    facciamo in questa maniera puntiamo al 1 elemento dopo che il vettore stato completato. Per

    lultimo la formula giusta 1000-8 + dimensione)

    SLLI R7 R4 5// dimensione in termini di byte della parte srotolabile del vettoreIl loop per la gestione del residuo sar un loop di R5 iterazioni, ma potrebbe anche essere un loop

    mai eseguito. Aggiungiamo uno scalare al vettore V[ ]F8+ V[1000]

    Codice per la gestione del residuo:

    L.D F2 992 R3// Load dellultimo elemento del vettore. Prelevo dalla memoria loggetto per

    caricarlo in F2

    ADD.D F2 F2 F8

    S.D F2 992 R3

    A prescindere dalle operazioni, adesso dobbiamo decrementare il puntatore e l'indice di questa

    operazione:

    ADDI R3 R3-8

    ADDI R5 R5-1

    BNEZ R5 -6//Fintanto che R5 diverso da 0 dobbiamo ritornare al loop gestione del residuo.

    Questa cosa giusta, ma errata dal punto di vista che literazione eseguita, almeno, una volta. Ci

    siamo calcolati questi valori, poi partiamo con il loop appena scritto (che preleva l'ultimo elemento

    del vettore) e, fintanto che R5 non viene decrementato escompare il resto,eseguiamo

    literazione. La 1 iterazione del programma verr effettuata anche se R5 = 0. Se R5 0 ( ovvero

    se N multiplo di 4) il loop appena scritto non finir; (diventa -1. Poi -2 fno allunder ow) e

    non ammissibile. Per questa ragione, prima di partire con il loop per la gestione del residuo,

    consigliato calcolare leventualit che il loop debba essere eseguito.Posso quindi fare cheBEQZ R5 5,salto dove inizia la parte della gestione del loop srotolato; se

    invece R5 non uguale a 0, non salto da nessuna parte e eseguo il loop( possibile caso).

    Ragioniamo sul codice gestito con 2 decrementi: R3 che mi deve sempre puntare allelemento da

    processare alla prossima iterazione, sia R5 che mi serve per contare. Potremmo utilizzare questa

    cosa impostando solo il decremento di R3 e facendo una condizione di uscita del loop su R3. Cio

    dato R3, che la dimensione del vettore, possiamo calcolarci un R7 (numero non ancora usato)

    che sar la dimensione della parte del vettore multipla di 4. Tipo: il vettore di 1003 elementi, la

    dimensione del vettore 8024. La dimensione del vettore multiplo di 4 1000 elementi*8 e 24 la

    dimensione del vettore residuo. Posso lavorare solo con R3, che decremento di volta per volta, e

    saltare fin tanto che R3 non uguale a R7Evito la gestione di un altro eventuale decremento

    ( dove R7 uguale a 8* (N/4) con N/4 numero intero non fp). Posso impostare l ritorno sulla BNE

    di R3 con il registro che di puntatore allultimo elemento non del vettore ma di quella parte del

    vettore che sarebbe multipla di 4. Per fare questo calcoliamo R7, che possiamo calcolare

    prendendo R4 che sarebbe il numero di elementi del vettore multipli di 4, prendere R4 e

    moltiplicarlo x4 x8. Posso fareSLLI R7 R4 32// ho messo in R7 la dimensione in byte della parte

    srotolabile del vettoreIl numero di elementi del vettore multiplo di 4 ( 17 elementi? Il numero

    16).

    Avendo in R7 questa dimensione in termini di byte della parte del vettore, fin tanto che R3

    decrementando di 8 non diventa uguale a R7, salto indietro. QuindieliminoADDI R5 R5-1E impostoBNE R3 R7-6al posto diBEQZ R5. Alla fine avremo che R3 punta allultimo elemento

    del vettore srotolato.

  • 7/24/2019 Calcolatori 23

    3/7

    Inizia ora la parte del loop che durer R4 ( dove abbiamo messo la parte intera delliterazione)

    iterazioni. Avendo R4 iterazioni inizieremo a scrivere un codice e poi avremo un'istruzione per

    saltare all'altra iterazione. Attenzione per che questo loop che scriveremo, potr essere non

    eseguito se per esempio volessi srotolarlo per un numero maggiore di volte rispetto gli elementi

    che ho. Quindi, prima di scrivere il codice, dobbiamo verificare che effettivamente ci sia da fare

    almeno uniterazione.Come?R4 non deve essere uguale a 0. Se avessi avuto N=2, R4 era ugualea 0. Come abbiamo fatto BEQZ R5, faremoBEQZ R4per saltare dopo il loop che scriveremo ora.

    Ritorniamo a quello che abbiamo scritto prima, la schedulazione fatta non il massimo quindi

    posso fare una serie di cambiamenti per ridurre gli stalli che staranno fra la LOAD e ADD, tra ADD

    e STORE e tra la ADD e la BNE pi leventualit, dopo BNE di fare unistruzione che verr abortita.

    LADDI R3 R3-8la posso spostare dopo la L.D, ottenendo 2 scopi: riempire uno stallo e

    allontanarla dalla BNE. Devo modificare la STORE perch stata decurtata di 8

    S.D F2 1000 R3. Invece per eliminare lo stallo tra la ADD e la BNE, prendo la S.D e la metto dopo

    la BNE in modo tale da far si che sia presente unistruzione che vada sempre eseguita e non verr

    abortita mai, dato che qualcosa che verr sempre eseguita. Ci porta a2 vantaggi: istruzione

    che non verr mai abortita e allontaniamo la STORE dalla ADD cos che tra la ADD, e la STORE

    che deve usare la ADD, avremo 1 stallo in meno.

    Quindi il programma un po pi ordinato sar:

    1. LOAD 2. Decremento 3. ADD 4. BNE 5. STOREQuindi cambia anche l'offset della BNE che

    diventa da-6 a -4

    Abbiamo visto la gestione del loop per eseguire il calcolo sul residuo, scriviamo ora il codice che

    gestisce il loop di una parte srotolata:

    L.D F2 992 R3// preleviamo il nostro operando

    ADD.D F2 F2 F8

    S.D F2 992 R3

    F4 984// stessa cosa altre 3 volte, in tutto deve essere fatta 4 volte. Non mi conviene usare

    F2. Uso altri registri come F4 F6 F10. Ho scritto solo questo ma in realtper ognuna di queste3

    sto facendo una LOAD, una ADD e una STORE.

    F6 976

    F10 968

    ADDI R3 R3-32// decremento R3 8* numero fasi srotolato (4)

    BNEZ R3 ( immediato che calcoleremo)

    Questa non schedulata, quindi la scheduleremo in maniera da avere le istruzioni per riempire

    alcuni buchi.

    L.D F2 992 R3

    L.D F4 984 R3

    L.D F6 976 R3

    L.D F10 968 R3

    ADD.D F2 F2 F8

    ADD.D F4 F4 F8ADD.D F6 F6 F8

    ADD.D F10 F10 F8

  • 7/24/2019 Calcolatori 23

    4/7

    S.D F2 992 R3

    S.D F4 984 R3

    S.D F6 976 R3

    S.D F10 968 R3

    ADDI R3 R3-32

    BNEZ R3 -14Il codice funziona, vediamo se si pu fare una schedulazione migliore.

    Abbiamo una serie di stalli che sono quelli dati dalle latenze dalle ADD, STORE, ecc. Cosa

    dobbiamo fare inserire un'istruzione dopo la BNEZ. Di certo non possiamo prendere l ADDI

    prima di lei perch la BNZ lavora proprio su R3. Se sposto S.D F10 968 R3, potrebbe andare

    meglio e qualcosa deve cambiare nel suo immediato. Sposto l ADDI R3 R3-32, allontanandola

    dalla BNEZ, dopo tutte le ADD.D per allontanare ancora di pi le ADD dalle STORE regalando un

    colpo di clock a tutto il sistema che calcola il risultato prima di scriverlo in memoria. Sto creando

    una latenza di 4 istruzioni tra la ADD che produce un registro e la STORE che lo vuole usare.

    Sto anche allontanando questa ADD da BNEZ, in maniera che R3 calcolato prima del confronto.

    Quindi S.D diventa F2 1024 R3, S.D F4 1016 R3, S.D F6 1008 R3 e infine F10 1000 R3.

    Devo anche ricalcolare limmediato della BNEZ che da 14 diventa 13.

    Vediamo un'altra tecnica che lapipeline da programma. Supponiamo di avere un loop:

    AX+Y=Z

    L.D x 1000 R1// R1 inizializzato a 8*N

    L.D y 2000 R2// gi anticipato la LOAD per evitare uno stallo

    MULT.D x' x A// abbiamo una latenza che ci impedisce di avviare subito la somma

    ADD.D z x' y// altra latenza per arrivare alla STORE successivaS.D z 3000 R1

    ADDI R1 R1-8

    BNEZ R1-7

    Su questo codice si pu fare una schedulazione allontana la ADD R1 R1-8 dalla BNEZ e

    posizionandola tra la MULT e ADDRiempio cos una latenza della MULT. Se faccio questo, 3000

    diventa 3008 e inserisco unistruzione dopo la BNEZ, dove la miglior candidata la STORE. Quindi

    BNEZ R1-7 diventa R1-6Loop canonico non srotolato.

    Abbiamo che ognuna di queste istruzioni si trova a operare sui dati prodotti dalle istruzioni

    precedenti. E vero che tra unistruzione e laltra devo aspettare che una produca il risultato, ma se

    invece di riempire questi stalli con altre istruzioni come abbiamo fatto con lo srotolamento del loop (

    loop con molte istruzioni in modo da avere delle istruzioni da mettere al posto degli stalli); la

    filosofia che voglio perseguire : se gli stalli vengono riempiti non da istruzioni del loop ma da

    Istruzioni che fanno parte, dal punto di vista logico, da altre istruzioni del loop. Ovvero?

    Supponiamo che quando sto producendo questo prodotto, il risultato del prodotto dovr essere

    sommato alla y ma nelliterazione successiva del loop. Cio, quando sto eseguendo la ADD, sto

    facendo la somma del registro che stato prodotto, dallistruzione, nelliterazione precedente. E

    come se ogni volta che eseguo un'istruzione del loop, listruzione si trova a operare con operandi

    che provengono da istruzioni di iterazioni precedenti. E' come se avessi una pipeline del

    programma in cui ogni istruzione che parte in pipeline con quella successiva del loop ma la quale agganciata a quella precedente. Posso immaginare una specie di diagramma:

  • 7/24/2019 Calcolatori 23

    5/7

    Abbiamo strutturato la pipeline e abbiamo un transitorio di svuotamento e uno di riempimento.

    Vediamo come viene scritto il codice:

    Una prima parte transitoria:

    L.D X

    L.D Y

    L.D X

    L.D Y

    ADD

    LD

    LD

    ADD

    MULT

    Adesso parte il loop vero e proprio, questo viene organizzato in loop N-3 volte.

    LD

    LD

    ADD

    MULTSTORE

    Dopo questo loop abbiamo

    ADD

    MULT

    STORE

    MULT

    STORE

    STORE

    La struttura completamente differente dal loop unrolling, un transitorio di riempimento, un loop

    che gira N-3 dove 3 sono le 3 iterazioni e infine un transitorio di riempimento dove si completano a

    vicenda ( 3 2 1).

    VEDERE DISPENSE DI CALCOLATORI A PARTIRE DA PAGINA 91, SONO LE SLIDE

    PROIETTATE IN CLASSE CON SPIEGAZIONI.

  • 7/24/2019 Calcolatori 23

    6/7

    Possiamo avere un CPI minore di 1?In maniera teorica possibile supponendo di poter prelevare

    pi di unistruzione per colpo di clock. Se ne riesco a prelevare 1 sola, sono limitato a non poter

    eseguire pi di unistruzione per colpo di clock. Facciamo finta di avere una memoria con banda

    pi importante,riesco a fare un CPI minore di 1?Si, ma bisogna capire che cosa dobbiamo

    gestire. Nel momento in cui abbiamo la possibilit di prelevare pi di unistruzione per colpo diclock, dobbiamo anche eseguirle e devo far si di non avere conflitti di dati tra le istruzioni. Se sto

    eseguendo contemporaneamente 2 istruzioni, e una vuole usare il risultato dellaltra, chiaro che

    questa cosa non deve avvenire. La prima cosa poter prelevare, prendo 2 istruzioni:

    i EX

    i+1 EX

    Queste 2 istruzioni le posso eseguire contemporaneamente, se non ho conflitti di dati e strutturale.

    Nel senso che, se sto facendo la EX di una e dellaltra, non possono poter usare entrambe lALU, o

    se lo devono usare, devono esserci necessariamente 2 ALU.

    Ci viene in mente una certa cosa vista quando abbiamo definito il processore fp. Abbiamo una

    parte di hardware completamente distinta dalla parte di hardware a virgola fissa. Le unit di calcolo

    sono diverse e anche i registri erano registri diversi. Se preleviamo 2 istruzioni e queste sono una

    che opera in virgola fissa, e una che opera a virgola mobile, abbiamo risolto, in 1 colpo, solo i

    problemi enunciati poco fa(un risultato in virgola mobile non viene usato da quello a virgola fissa e

    viceversa, tanto vero che sono su 2 banchi di registri differenti). C un eventuale conflitto solo se

    entrambe vogliono fare accesso in memoriaQuesto avviene solo con operazioni di tipo

    LOAD/STORE, ovvero non detto che tutte le istruzioni siano necessariamente di LOAD o STORE.

    Immagino una situazione in cui un processore, se riesce a leggere 2 istruzioni dalla memoria, e

    casualmente queste 2 istruzioni sono una virgola intera e una in virgola mobile, le 2 istruzioni

    possono vivere simultaneamente senza dover stare a litigare n sui dati n sulle risorse di calcolo.Se il compilatore stato bravo a scrivere il programma, che ha una serie di istruzioni ( alternanza

    fissa mobile), e riesco ad andare in memoria, non a leggere un'istruzione ma a leggerne 2, trovo

    allinterno del processore 2 istruzioni che, senza aver fatto cose complicate( come raddoppiare l

    ALU), possono essere eseguite contemporaneamentesenza conflitti di dato e strutturale.

    Questo processore si chiama processoresuperscalareOgni colpo di clock opera su qualcosa

    che pi di uno scalare. E' chiaro che il compilatore non sempre riesce a fare un lavoro di questo

    genere.

    Un processore superscalare, quindi, parte prelevando le 2 istruzioni, faimmediatamenteun

    controllo per vedere se sono una fissa e una mobile, se lo sono vengono eseguite in

    contemporanea. Se sono dello stesso tipo, far partire la 1 e laltra la terr posteggiata a partire

    dal prossimo colpo di clock.Lefficienza di questo tipo di macchina si potr quantificare in

    funzione del programma che la deve eseguire. Nel momento in cui ho un programma, in cui il

    compilatore riuscito a schedulare in coppie di istruzioni di diverso tipo, siamo apposto; altrimenti

    avr prelevato una coppia dalla memoria, ma il prossimo fetch viene stallato perch ho ancora da

    eseguire lavvio della 2 istruzione. Vediamo come questo schema pu essere ancora pi

    potenziato.

    Dal punto di vista del conflitto strutturale, abbiamo un ulteriore margine di lavoro: vero che

    all'interno abbiamo una dicotomia fra unit che gestiscono dati interi e fp, ma si detto che

    all'interno della parte di processore del fp, le unit di calcolo sono separate e indipendenti (esisteanche una certa ridondanza di queste unitPi di una). Supponendo di avere a che fare con

    istruzioni che non hanno conflitto di dati, posso pensare di avviare contemporaneamente una intera

  • 7/24/2019 Calcolatori 23

    7/7

    e 2o3 in virgola mobile, che per non devono far uso di dati dipendenti ma non devono usare la

    stessa unit funzionale. Possiamo immaginare un sistema che prelevi un certo numero di istruzioni

    o, se vogliamo, un istruzione che conterr al suo interno le informazioni di pi di unistruzione,

    quella che viene chiamata unistruzione molto lunga, ( a very long instruction word) e gestire un

    processore che prelevi unistruzione che sia unavliwe, da questi, se le istruzioni che sono

    contenute in questa vliw non sono conflittevoli tra loro, allora le posso eseguirecontemporaneamente. Per fare questo bisogna capire, per, come gestire e organizzare queste

    istruzioni. Lidea avere un gruppo distruzioni che fa uso di unit strutturali diverse da quelle altre

    istruzioniSe in un modo riesco ad averlo, posso pensare di avviare unistruzione che al suo

    interno abbia pi di unistruzione.