Ottimizzazione Openssl su Itanium 2/OpenVMS Riccardo Giacomelli 8 febbraio 2005 2 Indice I Architettura Itanium 2 0.1 0.2 0.3 II 7 Introduzione Itanium 2 processore RISC 0.1.1 EPIC . . . . . . . . . . . . . . . Architettura generale . . . . . . . . . . . 0.2.1 Execution model . . . . . . . . . 0.2.2 Unitá funzionali e issue rules . . 0.2.3 Bundles e Dipersal Rules . . . . 0.2.4 Pipeline . . . . . . . . . . . . . . 0.2.5 Memory Hierarchy . . . . . . . . Meccanismi innovativi di Itanium . . . . 0.3.1 Registri . . . . . . . . . . . . . . 0.3.2 Register Stack . . . . . . . . . . 0.3.3 Software pipeline . . . . . . . . . 0.3.4 Speculation and Predication . . . o . . . . . . . . . . . . CISC? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Assembler Itanium 9 9 10 11 11 12 15 15 15 15 16 18 18 21 1 Descrizione dell’assembler 1.1 Formato delle istruzioni 1.2 Registri . . . . . . . . . 1.3 Procedure . . . . . . . . 1.4 Explicit Bundle . . . . . Itanium . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 23 23 24 24 2 istruzioni principali 2.1 Add . . . . . . . 2.2 Alloc . . . . . . . 2.3 And . . . . . . . 2.4 Cmp-Cmp4 . . . 2.5 Dep . . . . . . . 2.6 Extr . . . . . . . 2.7 Ld . . . . . . . . 2.8 Nop . . . . . . . 2.9 Mix-Mux . . . . 2.10 Shl . . . . . . . . 2.11 Shr . . . . . . . . 2.12 Shrp . . . . . . . 2.13 Xor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 27 27 28 28 28 28 29 29 29 30 30 30 31 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 . . . . . . . . . . . . . . . . . . . . . . . . . . 4 INDICE III Ottimizzazione degli Algortimi 33 2.14 Introduzione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 3 Linee generali 3.1 Ottimizzazione generale del codice . . . . . . . . 3.1.1 Load-Store . . . . . . . . . . . . . . . . . 3.1.2 Cicli e loop unrolling . . . . . . . . . . . . 3.2 Ottimizzazione dell’assembler . . . . . . . . . . . 3.3 Fasi di Ottimizzazione degli Algoritmi . . . . . . 3.4 Operazioni presenti in tutti gli algoritmi . . . . . 3.4.1 Uso di Costanti . . . . . . . . . . . . . . . 3.4.2 Selezione di bits da registro . . . . . . . . 3.4.3 Look up in tabella . . . . . . . . . . . . . 3.4.4 Uso delle load . . . . . . . . . . . . . . . . 3.4.5 Xor tra una serie di valori . . . . . . . . . 3.4.6 Htonl Ntohl . . . . . . . . . . . . . . . . . 3.4.7 Uso dei bundles per le istrzioni assembler 3.5 Regole Generali per Itanium . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 37 37 38 39 39 40 40 40 41 41 42 42 43 44 4 DES 4.1 Caratteristiche Principali . . . . . . . . . . . . 4.1.1 Permutazioni . . . . . . . . . . . . . . . 4.1.2 32 bit-64 bit . . . . . . . . . . . . . . . 4.1.3 Table look-up . . . . . . . . . . . . . . . 4.2 Ottimzzazione DES in Dettaglio . . . . . . . . 4.2.1 Initial Permutation e Final Permutation 4.2.2 16 Rounds . . . . . . . . . . . . . . . . . 4.2.3 Ottimizzazione in Itanium . . . . . . . . 4.3 Risultati . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 45 45 46 47 48 48 50 51 51 5 RC5-32/12/16 5.1 Caratteristiche Principali . . . . . . . . . 5.1.1 Rotate variabili . . . . . . . . . . . 5.1.2 Add modulo 232 . . . . . . . . . . 5.2 Ottimzzazione RC5-32/12/16 in Dettaglio 5.2.1 RC5-32/12/16 single round . . . . 5.2.2 Ottimizzazioni in Itannium . . . . 5.3 Risultati . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 53 53 53 54 54 55 56 6 SHA-1 6.1 Caratteristiche Principali . . . . . . . . . 6.2 Descrizione dell’algoritmo e ottimizzazione 6.2.1 Descrizione dei round . . . . . . . 6.2.2 Espansione delle word . . . . . . . 6.3 Ottimizzazione SHA-1 nel dettaglio . . . . 6.3.1 Singolo Round . . . . . . . . . . . 6.3.2 Espansione delle word . . . . . . . 6.3.3 Ottimizzazione in Itanium . . . . . 6.4 Risultati . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 57 58 58 58 59 59 59 59 60 INDICE 5 7 AES (Rijndael) 7.1 Caratteristiche Principali . . . . . . 7.1.1 Scelta dell’implementazione . 7.2 Ottimizzazione AES nel dettaglio . . 7.2.1 Ottimizzazione dell’algoritmo 7.3 Ottimizzazione In Itanium . . . . . . 7.4 Risultati . . . . . . . . . . . . . . . . IV . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Appendix 65 65 65 66 69 70 71 73 8 Help Itanium/OpenVMS 8.1 Assembler Itanium . . . 8.2 Compilare il codice . . . 8.3 Linking . . . . . . . . . 8.4 Debugger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 75 75 76 76 9 Summary 9.1 DES . . . . . . . . . 9.1.1 DES Results 9.2 RC5-32/12/16 . . . . 9.3 AES . . . . . . . . . 9.4 SHA-1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 79 79 80 80 82 . . . . . . . . . . 6 INDICE Parte I Architettura Itanium 2 7 0.1. INTRODUZIONE ITANIUM 2 PROCESSORE RISC O CISC? 0.1 9 Introduzione Itanium 2 processore RISC o CISC? In Internet, in molti articoli riguardanti Itanium, questo processore viene definito a volte un RISC a volte un CISC; entrambe le definizioni hanno un qualcosa di vero. Si definiscono RISC, ovvero Reduct Set of Istruction Computing quei processori che possiedono un ridotto set di istruzioni assembler e non hanno nel loro set istruzioni complicate, come ad esempio le operazioni sulle stringhe dell’8086. L’idea su cui si basano i processori RISC é quella di risolvere i problemi complicati e lunghi, invece che con una sola istruzione, in piú passi, suddividendo l’operazione complessa in una serie di piú istruzioni semplici. Disponendo solo di istruzioni semplici, i RISC sono in grado di eseguire molte piú istruzioni al secondo rispetto ai CISC. I processori CISC sono quelli che hanno implementate un vasto numero di istruzioni e dispongono di istruzioni complicate quali, ad esempio, l’istruzione di rotate di un registro o le operazioni su stringhe. Il processore Itanium sicuramente un processore RISC perché dispone di operazioni semplici che durano un colpo di clock. Un esempio concreto é dato dal fatto che l’operazione add esiste solo con operandi contenuti in registri , e quindi per fare add di un operando in memoria bisogna prima caricarlo in registro con una operazione di load. Oppure, altro esempio, non ha supporto per rotate variabile di registro. Allo stesso tempo possiede anche istruzioni complicate: prendiamo ad esempio la extr che permette di selezionare un sottoinsieme di bit da un registro, operazione che di solito era fatta con shift + and con maschera. Basti pensare che su Itanium 2 sono implemetate oltre 150 istruzioni, ognuna della quali ha delle varianti. Per quanto riguarda la compatibilitá con le versioni assembler precedenti si adottano delle soluzioni quali l’utilizzo di pipeline separate per eseguire codice in assembler non Itanium. Oppure si adotta una memoria di microcodice in cui per ogni istruzione complicata c’é la serie di istruzioni semplici corrispondente. Quando si deve eseguire una istruzione complessa si effettua un jump nella memoria di microcodice e si eseguono le istruzioni semplici corrispondenti. Concludendo, nella sua struttura Itanium un processore RISC superscalare in quanto riesce a compiere ben 6 istruzioni per colpo di clock, ma avendo al tempo stesso anche caratteristiche CISC. In realtá Itanium va oltre i processori RISC e CISC. Il termine esatto per definire l’architettura Itanium EPIC. 0.1.1 EPIC Una caratteristica di Itanium che il processore esegue le operazioni nell’ordine in cui gli vengono fornite dal compilatore. Non ha nessun meccanismo di riordino delle istruzioni, non ha alcun meccanismo di ottimizzazione del codice al momento dell’esecuzione. In processori come il Pentium esisteva un pool di istruzioni di cui il processore aveva eseguito il fetch; poteva poi decidere di farne avanzare alcune mentre altre erano in stallo, oppure era in grado di cambiare l’ordine di esecuzione delle istruzioni. Questo meccanismo, se da un lato era utile per cercare di eseguire in modo piú veloce possibile il codice, dall’altro presupponeva l’esistenza di una serie di processi per controllare che l’esecuzione del codice fosse ancora corretta, verificava che tutte le dipendenze fossero state rispettate ecc. L’architettura risultante diventava molto complicata. In 10 Itanium, invece, la strategia é cambiata: il processore esegue in ordine tutte le istruzioni senza cercare di modificarne la sequenza. A questo punto é chiara limportanza fondamentale del compilatore, poiché il codice compilato verrá eseguito cosı́ com’é stato compilato. La scelta di Itanium é quella di dare pieno controllo del processore al programmatore. Le istruzioni sono molto accurate, permettono addirittura di controllare la cache. Tuttavia, proprio per il fatto che si ha pieno controllo sul processore e le istruzioni sono ricche di dettagli, la programmazione in assembler Itanium e la realizzazione di compilatori per Itanium risultano complicate. EPIC significa Explicit Parallel Instruction Computing. Questo paradigma ha portato a una rivoluzione, ovvero a seguire una linea di progetto che lascia solo le procedure semplici ed essenziali nel core, mentre quelle piú complicate vengono demandate ad altri, ovvero al compilatore. Il compilatore prepara il codice, mentre il processore, con un’espressione trovata sul sito Intel, will rock it, ovvero lo eseguirá molto velocemente. Al tempo stesso in Itanium sono implementati nuovi meccanismi che complicano l’architettura . Essi saranno presentati nella sezione seguente. 0.2 Architettura generale La grande capacitá di eseguire istruzioni permette a Itanium di sfruttare al massimo il parallelismo esistente nel codice, a condizione che venga scritto e compilato in modo adeguato. (Piú avanti verrá spiegato meglio come). Le caratteristiche piú importanti di Itanium sono: 1. Architettura interamente a 64 bit 2. Vasto set di istruzioni 3. Gran numero di registri 4. Struttura di accesso alla memoria ottimizzata 5. gran numero di unitfunzionali in grado di eseguire 6 istruzioni per clock 6. Register Stack-meccanismi innovativi per gestire i registri e i passaggi di parametri a funzione 7. Software pipeline-meccanismi innovativi per gestire i loop 8. Cache instructions-istruzioni per controllare direttamente la cache 9. Speculation and Predication-meccanismi di speculazione e predicati innovativi Itanium dispone di un’architettura interamente a 64 bit; sia i registri che i bus che la pipeline e le unitá funzionali hanno tutte parallelismo 64 bit. La vera innovazione consiste nell’avere le unitá funzionali e i registri a 64 bit, perché giá altri processori come il pentium disponevano di un bus a 64 bit per migliorare il bandwidth verso l’esterno del processore, ma non disponevano di unitá funzionali a 64 bit. 0.2. ARCHITETTURA GENERALE 0.2.1 11 Execution model Itanium puó eseguire fino a 6 istruzioni per colpo di clock. Il processore esegue le istruzioni tre a tre. Un gruppo di istruzioni viene denominato Bundle. Il processore esegue quindi due Bundle per colpo di clock. Quando il processore riesce ad eseguire 2 Boundle in un colpo di clock si verifica una situazione di dual issue, ma non sempre riesce ad eseguirne due. In questo caso c’é uno split issue, ovvero i due bundle vengono eseguiti in 2 colpi di clock successivi. In definitiva il processore esegue o 3 o 6 istruzioni per clock. Questo importante perché se anche solo una delle 3 istruzioni nel secondo Bundle non puó essere eseguita per uno dei motivi citati dopo, tutte e tre le istruzioni verranno eseguite al clock successivo. I motivi per cui ci puó essere uno split issue sono: 1. uno stop esplicito é stato incontrato. Lo stop é rappresentato con ;; 2. non ci sono abbastanza risorse per eseguire tutte le istruzioni 3. Le istruzioni non sono state poste nel Bundle seguendo le regole di dispersal Itanium Le regole di dispersal definiscono in che modo le istruzioni debbano essere posizionate nel bundle. Esse saranno spiegate nelle subsection successive. Ritornando a processori come il Pentium, questi avevano un’architettura piú complicata anche perché dovevano occuparsi di controllare i cosidetti hazards, ovvero le situazioni in cui un registro é scritto con un nuovo valore prima che il precedente valore potesse essere letto (WAR-Write after read). Oppure il registro viene letto prima che il valore da leggere sia stato scritto (RAW-Read after write). Un esempio su tutti é dato dall’istruzione che legge un registro pensando di trovare il risultato di un’istruzione precedente, ma la precedente non era ancora terminata. Oppure, altro esempio, dato da hazards (WAW-write after write), cioé quando esistono 2 scritture consecutive sullo stesso registro. Come é stato risolto in Itanium tutto questo? In veritá Itanium non se occupa minimamente: sará il compilatore ad inserire tra le due istruzioni in questione uno stop implicito in modo da rispettare la dipendenza. Se il compilatore non inserisce nessuno stop e si verificano degli hazard il processore, come letteralmente scritto nei manuali intel, si comporterá in modo non deterministico e il risultato non sará prevedibile. 0.2.2 Unitá funzionali e issue rules Itanium 2 dispone di 6 unitá ALU. Queste vengono usate per eseguire le operazioni aritmetiche, compares, multimedia istruction ecc. Dispone di 4 porte verso la cache per l’accesso alla memoria, due per le store, e due per le load. Questo permette di eseguire 2 load e 2 store per colpo di clock. Possiede 2 unitá Integer(I0, I1), e una unitá shift per operazioni quali lo shift right,shift left, ecc. In totale ha 11 porte a cui le istruzioni vengono mandate per essere eseguite:M0, M1, M2, M3, I0, I1, F0, F1, B0, B1 e B2. Le M0-M3 sono le porte per la memoria, ma non sono tutte uguali: M0 e M1 sono le porte per le load, M2 e M3 sono le porte per le store. Inoltre le porte M non vengono solo usate per gli accessi in memoria, ma anche per eseguire operazioni di tipo ALU tipo add, and ecc. Lidea é che poiché le porte di memoria dovevano contenere un’unitá per calcolare l’indirizzo e quindi in grado di svolgere calcoli, quando 12 Instruction Type A1-A5 A6-A8 A I I5 I7 I10 I11, I12 I29 M1 M4, M5 M34 Istrutions add, shladd cmp, cmp4 and xor, or shr, shr. u shl, shl. u shrp extr, extr. u, Dep sxt, zxt, czx ld4, ld8. . st4st8. . alloc Description add, parallel add compare e compare 4 bytes and logico xor, or logico shift right shift left shift right pair usato per rotate fissi extract, Deposit signe extended, zero extended Integer load Integer store allocate frame on reg stack M0 Y Y Y Y M1 Y Y Y Y Y Y M2 Y Y Y Y Y Y Tabella 1: principali istruzioni e porte sulle quali possono essere eseguite queste porte non venivano usate per l’accesso in memoria potevano essere usate per eseguire altre istruzioni. Di conseguenza, nelle porte M sono state inserite delle vere e proprie ALU in grado di calcolare gli indirizzi di memoria quando necessario, ma anche di eseguire operazioni aritmetiche all’occorrenza. Le porte I0 e I1 sono le porte Integer; esse eseguono operazioni quali gli shift o gli xor, oppure istruzioni complicate quali la mix o l’extr. Un limite di Itanium é costituito proprio da queste 2 porte. Nell’ottimizzazione degli algoritmi la maggior parte delle istruzioni era di tipo integer; Itanium, pur avendo tutte queste unitá funzionali, puó eseguire solo 2 istruzioni Integer per colpo di clok, e per le istruzioni piú complicate come l’extr anche solo una per clock. Le porte F0, F1 sono le Floating point, Le porte B0, B1, B2 sono le porte in cui sono eseguite le operazioni di branch. La tabella 1 riporta le principali istruzioni e le porte su cui possono essere eseguite. Il type delle istruzioni definisce se sono istruzioni di tipo ALU, Integer o Memory. All’interno di questi gruppi vi sono sottoinsiemi, per esempio A2, A4, ma le sottodivisioni non sono importanti. Quello che é importante é sapere quali istruzioni appartengono a quale gruppo, e da che unitá funzionale possono essere eseguite. Come si vede dalla tabella 1 le istruzioni ALU possono essere eseguite da qualunque unitá M o I, le istruzioni Integer, sono piú limitate, per esempio la extr puó essere solo eseguita dell’unitá I0. 0.2.3 Bundles e Dipersal Rules Le Dipersal Rules sono le regole che il processore usa per eseguire le istruzioni. Sono molto importanti perché se non vengono rispettate, si avrá uno split issue. Come abbiamo detto prima le istruzioni sono raggruppate in bundles ognuno di tre istruzioni. Ciascuna delle tre posizioni all’interno del bundle é chiamata slot. Ogni bundle ha 3 slot. La posizione in cui un’istruzione é posta determina l’unitá funzionale da cui sará eseguita. c’é un mappaggio preciso tra la posizione delle istruzioni e l’unitá funzionale a cui sará anno assegnate. Questo mappaggio é chiamato dipersal. Essistono molti tipi di Bundles, per esempio MMI, MMF, MII. Le lettere M stanno sempre per memory, A ALU, I Integer, F floating point, B branch. Un Bundle del tipo MMI significa che ha 2 istruzioni memory e una integer. L’ordine in cui sono specificate le istruzioni M3 Y Y Y Y Y I0 Y Y Y Y Y Y Y Y Y I1 Y Y Y Y Y Y Y 0.2. ARCHITETTURA GENERALE MII MLI MMI MFI MMF MIB MBB BBB MMB MFB MMI Y Y Y Y Y Y MLI Y Y 13 Y Y Y Y Y MMI Y Y Y Y Y Y MFI Y Y Y Y Y Y MMF Y Y Y Y Y Y MIB Y Y Y Y Y Y MBB Y Y Y Y Y Y BBB Y Y Y Y Y Y MMB Y Y Y Y Y Y MFB Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Tabella 2: tipi di Bundle che possono andare in dual issue non é casuale ma preciso: prima devono essere scritte le 2 istruzioni memory, poi l’istruzione integer. Scrivendo il codice assembler i Bundle vengono dichiarati con 2 parentesi graffe e il tipo del bundle scritto all’inizio. Per esempio: {. mmi ld4 r32=[r32] ld4[ r33=[r33] shr r35=r35, 2 } Un Bundle di questo tipo é un memory-memory-integer; si puó notare che l’istruzione integer occupa proprio il terzo slot nel bundle. Le dispersal rules sono molte. Le principali per le operazioni ALU e Integer sono: • l’istruzione nel primo slot I dei due Bundles sará eseguita da I0, La seconda da I1. • Se la seconda istruzione I puó essere eseguita solo da I0 ci sará uno stop implicito e l’istruzione sará eseguita nel colpo di clock seguente • Gli slot I non contengono solo istruzioni I ma possono contenere anche istruzioni di tipo ALU. Se 2 istruzioni di tipo I sono giá state assegnate alle due unitá funzionali I0 e I1, e un ulteriore slot I contiene unistruzione ALU, questa verrassegnata ad una unitá M libera. E’ il caso dei Bundles MMI-MMI. Un esempio della prima regola questo: {. mmi ld4 r32=[r32] ld4[ r33=[r33] shr r35=r35, 2 } {. mmi ld4 r32=[r32] ld4[ r33=[r33] xor r35=r35, 2 };; 14 Come detto nella regola citata prima, l’istruzione shr che é la prima istruzione Integer verrá assegnata all’unitá I0, l’istruzione xor che la seconda istruzione Integer verrá assegnata a I1. E’ importante capire che, se nei 2 bundle c’é un’istruzione tipo extr eseguibile solo da I0, questa deve essere posta nel primo slot I, altrimenti si avrá uno split issue. esempio: {. mmi ld4 r32=[r32] ld4[ r33=[r33] shr r35=r35, 2 } {. mmi ld4 r32=[r32] ld4[ r33=[r33] extr r35=r35, 2 };; In questo esempio lo slot I0 occupato dall’istruzione shr; l’istruzione integer successiva cioé extr non puó essere eseguita, e c’é un split issue. Se il codice fosse scritto nel modo seguente: {. mmi ld4 r32=[r32] ld4[ r33=[r33] extr r35=r35, 2 } {. mmi ld4 r32=[r32] ld4[ r33=[r33] shr r35=r35, 2 };; sarebbero eseguite tutte e sei le istruzioni in un colpo di clock. La terza regola diceva che se in uno slot integer c’é un’istruzione ALU, e le 2 unitá I0 e I1 sono gioccupate l’istruzione assegnata ad una unitá M. esempio: {. mii ld4 r32=[r32] extr r35=r35, 2 shr r35=r35, 2 } {. mmi ld4 r32=[r32] st4 [r40]=r40 and r50=r50. r51 };; le due istruzioni integer shr e extr sono assegnate alle unitá integer. Lo slot integer del secondo bundle non puó essere assegnato ad una unitá integer, ma siccome contiene una istruzione ALU verrá assegnata ad una unitá M. La tabella 2 indica quali tipi di bundle possono essere eseguiti insieme in un solo colpo di clock. Non é sufficiente seguire la tebella 2 affinché si abbia dual issue, poiché 0.3. MECCANISMI INNOVATIVI DI ITANIUM 15 é sempre necessario seguire le regole di dispersal. Se non vengono rispettate ci sará split issue anche se si sono usati 2 bundle che nella tabella 2 sembravano poter essere eseguiti in dual issue. Per approfondimenti sulle altre regole di dispersal consultare [1, capitolo 1.1] La regola generale quella di porre prima nel bundle le istruzioni con piú limitazioni. 0.2.4 Pipeline La pipeline Itanium 2 é molto lunga, ha 8 stage. Una pipeline lunga serve soprattutto per migliorare il throughput di istruzioni per colpo di clock. Le varie stage della pipeline non sono state approfondite perché non sono essenziali per la comprensione dell’ottimizzazione su Itanium. 0.2.5 Memory Hierarchy Il processore Itanium ha una cache L1-D di primo livello per i dati, una cache L1-I per le istruzioni, una cache L2 di secondo livello unificata e una cache di terzo livello. Il TLB(Translation lookaside buffer) per convertire gli indirizza da cercare full associative, non verrá approfondito qui. La cosa piú importante sono le dimensioni e le latenze della cache. La cache L1 ha dimensione 16Kbytes, set associative a 4 vie, e una linea di cahe lunga 32 byte. La cache L2 ha dimensione 96Kbytes, 6 way set associative e una linea di 64 bytes. La cache L3 sul processore esaminato ha dimensione 3 Mbytes. La latenza di primo livello lavora alla velocitá del processore; per accedervi basta un colpo di clock, quindi la latenza per accedere alla cache L1 é un colpo di clock. Per accedere alla cache L2 la latenza di 6 colpi di clock per gli interi 9 per i floating point. La latenza per L3 di 21 colpi di clock per gli interi e 24 per i floating point. Se c’é un miss in L1 si controllerin L2 e se c’é ancora miss si controllerá in L3, quindi le latenze per accedere a L3 vanno sommate. Il bus esterno dalla cache L3 alla memoria ha una bandwidth di 2.1 Gb/secondo. Tra L3 e L2, invece, si possono trasmettere 16 bytes/clock. Tra L2 e L1 si possono trasmettere 32 bytes/clock, e tra L1 ed il register file si possono caricare 2 x 8 bytes/clock. Il processore dispone di 4 porte (2 per le load e 2 per le store), quindi possono essere eseguite al massimo 2 load di 8 bytes ciascuna e 2 store di 8 bytes ciascuna. 0.3 0.3.1 Meccanismi innovativi di Itanium Registri Itanium dispone di un ricco set di registri: i registri utilizzabili sono molti, come si vede dalla figura 1. Gli Integer registers sono registri general purpose a 64 bit e vengono utilizzati comunemente per contenere dati, indirizzi ecc. I Predicate registers sono usati per i predicati che sono un meccanismo per velocizzare le condizioni presenti nel codice, tipo if-else. I Branch Registers sono registri per contenere gli indirizzi dei salti e vengono usati per velocizzare i salti presenti nel codice. In realtá vi é un’ulteriore suddivisione nei registri General purpose R1-R128: • R0:sempre uguale a zero • R1:GP(Global Data Pointer) 16 128 Integer Registers 128 Floating Point Registers Instruction Pointer 64 Predicate Registers User Mask 8 Branch Registers 128 Application Registers CPUID Registers Current Frame Pointer Performance Monitor Data Registers Figura 1: set di registri itanium • R2-R3:scratch • R4-R7:riservati • R8-R11:valori di ritorno delle procedure • R12:stack pointer • R13:(Riservato)Thread Pointer • R14-R31:scratch • R32-R128:General registers Gli scratch registers sono quelli usati per scopi general purpose, quando una procedura usa pochi registri vengono usati questi registri. Il registro R0 sempre uguale a zero é uno stratagemma, usato anche in altri processori, per poter ottenere l’indirizzamento assoluto semplicemente usando l’indirizzamento base+displacement e usando come base R0. Cosı́ implementando pochi modi di indirizzamento, se ne possono ottenere altri derivati da questi. I registri R32R128 sono general purpose e possono essere usati dalle procedure che usano piú di 2 o 3 registri e che quindi non hanno abbastanza registri di scratch. 0.3.2 Register Stack Dato il gran numero di registri era necessario un meccanismo per gestirli al meglio, perché non tutte le procedure usano i registri da R32 a R128, ed era 0.3. MECCANISMI INNOVATIVI DI ITANIUM Registri R32 Proc A R40 Local R50 R55 R60 R68 Output Proc B Input Local Proc C Output Input Proc B Proc A 17 Input Local Local Local Output Output Figura 2: chiamate a procedura necessario permettere alla funzione di usufruire solo dei registri necessari. In altri processori la procedura standard prevedeva che o il chiamante o il chiamato (caller o calle) salvassero il valore dei registri nello stack per permettere alla funzione di usarli. Con tutti questi registri diventava un pó complicato e inefficiente questo metodo di procedere. In Itanium si usa un altro metodo chiamato Regiter Stack I registri da R32 a R128 vengono usati come uno stack. Ogni funzione ha a disposizione 3 tipiú di registri: • Input registers • Local registers • output registers Gli input sono i registri usati per i parametri in ingresso, i Local sono quelli usati internamente dalla funzione. I registri di output non sono quelli usati per i valori di ritorno come potrebbe sembrare dal nome, ma sono quelli usati dalla funzione per passare parametri ad altre sottofunzioni. Si ricorda che i registri per passare i valori di ritorno sono R8-R11. I registri di output del chiamante coincidono con i registri di input del chiamato. Bisogna considerare i registri R32-R128 come uno stack ogni funzione alloca sullo stack il numero di registri che gli occorrono. input, local e output sono solo denominazioni diverse per i registri general purpose e, una volta compilato il codice, vengono mappati sui registr r32-r128. Essi servono per facilitare la scrittura del codice. esempio: In figure 1.2 si puó vedere come cambia lo stack regisers con 3 procedure A, B, C che vengono chiamate una dentro l’altra. All’inizio lo stack registers punta al registro R32, la procedura A alloca sullo stack register 8 registri Local, cioé da usare internamente, e 10 registri output per passare i parametri di input 18 ala procedura B. Quando la procedura B viene chiamata, essa consapevole di dover ricevere in input 8 parametri e alloca sullo stack registers 8 registri di input piú 5 registri che userá internamente, e altri 5 registri per poter passare i parametri di input alla procedura C. Si puó notare come i registri di output di A e quelli di input di B coincidano; questo permette di passare i parametri da una funzione all’altra. C, a sua volta, alloca 5 registri di input e 8 registri local. Come si puó notare C non usa registri di output perché non deve chiamare sotto di sé nessun’altra procedura. All’istruzione di return di una funzione il puntatore dello stack registers torna indietro alla funzione precedente. In questo modo si elimina la necessitá di salvare i registri che la funzione userá perché necessario solamente che la funzione allochi con l’istruzione ALLOC dei registri sullo stack. Una domanda che sorge spontanea é : se dopo chiamate a procedura successive magari ricorsive si allocato un numero di registri che vá oltro R128 cosa succede? Itanium se si arriva fino a R128 e sono necessari altri registri usa un meccanismo per salvare i registri e liberare spazio sullo stack dei registri per la funzione successiva. Questa operazione abbastanza costosa in termini di colpi di clock puó durare anche 20 colpi di clock e deve essere quindi evitata accuratamente se le prestazioni sono essenziali. D’altronde il numero di registri é tale da far sı́ che il processore non debba mai salvare i registri per liberare spazio sullo stack. 0.3.3 Software pipeline I meccanismi di software pipeline non sono stati da me approfonditi piú di tanto perché non li ho utilizzati; daró comunque un’idea generale del loro funzionamento perché in alcuni casi, sono meccanismi molto utili e innovativi. Il software pipeline é applicato quando nel software ci sono dei blocchi di codice uguale ripetuti piú volte, come nel caso dell’algoritmo DES in cui ci sono 16 round. L’idea del software pipeline é quella di usare una pipeline applicata al software. Per esempio nel caso del DES ogni round dovrá svolgere delle operazioni specifiche: Espansione E, trasformazione tramite S-boxes, ecc. L’idea é quella di mettere i vari round in pipeline, cioé mentre il primo round sta eseguendo la sua seconda operazione, il secondo esegue la prima e cosı́ via. Tuttavia nel caso di algoritmi crittografici ogni round dipende dal precedente, di conseguenza il secondo round non puó iniziare prima della fine del primo. Per questo motivo questi meccanismi non sono stati utilizzati nell’ottimizzazione. La pipeline viene attuata grazie al register stack, il meccanismo descritto sopra. Si usano dei registri virtuali. Ogni ciclo usa un determinato numero di registri ( ad esempio il primo utilizza i registri dall’R32 al R41, dall’R42 all’R51 ecc). Per esempio per il ciclo uno il virtual register 1 corrisponde al registro R32, per il ciclo 2 il virtual register 2 corrisponde al registro R42. Potendo usare diversi registri i cicli possono essere eseguiti insieme. 0.3.4 Speculation and Predication Prediction Il meccanismo di Prediction serve per ottimizzare le condizioni tipo if-else presenti nel codice. Quando nel codice si trovano delle condizioni, queste vengono predette dalla branch prediction unitá con meccanismi complessi illustrati dopo, 0.3. MECCANISMI INNOVATIVI DI ITANIUM 19 if(R1) R2=R2+R4; else R7=R2-R5; Figura 3: condizione tipo if-else cmp. eq p1,p2=r1,r0 (p1)br. cond else_clause add r2=r2,r4 br end_if else_clause: sub r7=r2-r5 end_if:\linebreak Figura 4: codice assembler if-else cmp.ne p1, p2=r1, r0 (p1)add r1=r2, r4 (p2)sub r7=r2, r5 Figura 5: codice assembler if-else trasformato con predicati ma non detto che vengano predette nel modo esatto. Ogni condizione che non é predetta esattamente, porterá a una perdita di colpi di clock, poiché le istruzioni caricate non sono quelle esatte e si deve eseguire un’altra parte di codice. La perdita é proporzionale alla lunghezza della pipeline, perché quando si ha una prediction errata la pipeline viene svuotata e si perderanno tante piú istruzioni quanta la sua lunghezza. Siccome la pipeline di Itanium é lunga 8 stages il numero distruzioni che vengono perse puó essere significativo. Nello pseudocodice in figura 1. 3 c’é una condizione sul registro R1. Se é vera si esegue il codice R2=R2+R4, altrimenti il codice R7=R2-R5. La condizione sará predetta con i meccanismi sopra descritti. Se la condizione su R1 é predetta in modo erroneo si perdono fino a 10 clock. I prediacati sono un modo per permettere al processore di eseguire o ignorare delle istruzioni. Si basano sull’uso dei predicate registers che sono lunghi 1 solo bit e valgono o TRUE o FALSE. I predicati precedono le istruzioni in questo modo: (p1)R2=R3+R4 L’istruzione R2=R3+R4 verrá eseguita solo se il valore di p1 é TRUE cioé 1, altrimenti l’istruzione verrá considerata come una nop e ignorata. Il codice esempio di prima che tradizionalmente era compilato come in figura 1.4 La prima istruzione di compare paragona R1 con R0 che vale sempre 0, se é vera setta P1=1 e P2=0. L’istruzione successiva br(branch) verreseguita sole se P1 uguale a 1 ciose R1==0. Questo modo di procedere anche nel caso di un codice cosı́ semplice porta a una perdita di colpiú di clock, perché anche 20 se il codice é lungo solo due colpiú di clock considerando che la condizione é predetta in modo erroneo il 30% delle volte si ottiene una lunghezza media di (2 clock+(30%*10clock)=5. Trasformando il codice compiú lato con i predicati come in figura 1.5. In questo modo sono stati eliminati i salti nel codice e il processore semplicemente ignorerá le istruzioni che non devono essere eseguite . Il codice durerá sempre 2 colpi di clock senza avere nessun ritardo causato da un’erronea previsione del branch. Parte II Assembler Itanium 21 Capitolo 1 Descrizione dell’assembler Itanium 1.1 Formato delle istruzioni Le istruzioni vengono scritte nella forma: ISTR R1=R2, R3 R1 é l’operando destinazione, R1 e R2 sono gli operandi sorgente. Le istruzioni Itanium hanno molti modificatori, alcuni per la dimensione delle istruzioni(per esempio load 4 bytes o 8 bytes), altri che definiscono delle ulteriori indicazioni per l’istruzione. I modificatori possono essere trovati o attaccati all’istruzione come per esempio nel caso della load :ld4 o ld8, oppure separati dall’istruzione da un punto, come per esempio ld8.fill.sa. L’assembler Itanium offre una grande varietá di istruzioni con molti modificatori disponibili in modo da offrire al programmatore la possibilitdi controllare anche i comportamenti piú sottili del processore e dell’architettura. Un esempio sono le istruzioni che permettono di controllare la cache. 1.2 Registri Dato l’alto numero di registri i nomi dei registri sono molti, e alcuni registri hanno anche 2 denominazioni. Molti non hanno accesso diretto dal programma, ma vengono usati e modificati solo dal processore. I general purpose registers sono i registri piú usati. Gli application registers sono registri che controllano le funzioni del processore piú svariate, dal mascheramento degli interrupt, alla scelta dell’uso di memory reference big o little endian, al controllo del register stack. Qui verrá soltanto analizzato luso dell’application register ar.pfs; per approfondimenti si rimanda a [2]. Il registro ar.pfs viene usato per controllare il register stack engine, ovvero il meccanismo che controlla il register stack. I Control Registers servono per memorizzare lo stato del processore e i suoi parametri. 23 24 CAPITOLO 1. DESCRIZIONE DELL’ASSEMBLER ITANIUM REGISTERS TYPE General purpose registers Floating-point registers Application registers Control Registers Instruction TLB traslation registers Data TLB translation registers Predicate Registers NOTAZIONE ASSEMBLER r f ar cr itr dta p ACCESSO INDIRETTO Y Y Tabella 1.1: principali registri Itanium 1.3 Procedure Le procedure in assembler Itanium vengono scritte in questo modo: 1 2 3 4 5 6 7 8 9 10 .text .global encrypt .proc funzione_di_encrypt encrypt: alloc loc0=ar.pfs, 3, 20, 0, 0 ... istruzioni ... br. ret.sptk.many b0;; .endp funzione_di_encrypt La riga 2 serve a rendere visibile all’esterno la funzione encrypt, dichiarandola global. La procedura viene aperta dalla riga 3, e viene chiusa dalla riga 10. La riga 4 é l’entry point, il punto a cui si salta quando viene chiamata la procedura. L’istruzione alla riga 5 alloca sullo stack 20 registri local e 3 registri di input. Per ulteriori informazioni sull’istruzione alloc vedi la sezione istruzioni principali. L’istruzione alla riga 9 é l’istruzione di ritorno, i vari modificatori vengono spiegati nella sezioni istruzioni. All’interno della procedura, al posto di usare i registri Rxx, si possono utilizzare i registri In, loc e out; questo facilita la scrittura del codice. Al posto dei puntini del codice precedente potrá quindi trovare istruzioni del tipo: mov xor add mov loc2=in0 loc2=loc2,loc3 loc3,loc2,loc5 out1=loc3 Si ricorda che i registri out non servono per passará e i valori di ritorno, ma solo per passará e i valori in input alle procedure. Per passará e i valori di ritorno si usano i registri r8-r11. 1.4 Explicit Bundle Quando si scrive l’assembler Itanium si puó porre uno stop implicito al processore; questo significa che tutte le istruzioni dopo lo stop verranno eseguite nel colpo di clock successivo. Lo stop espresso con ;; esempio: 1.4. EXPLICIT BUNDLE xor add and sub 25 loc2=loc2, loc3 loc3,loc2,loc5;; loc7=loc20,loc21 loc5=loc4,loc19 Il processore esegue le prime due istruzioni in un colpo di clock, dopodiché incontra lo stop ed esegue le altre 2 istruzioni nel colpo di clock successivo. E’ possibile scrivere l’assembler con Bundle espliciti, ovvero si puó comunicare al processore come le istruzioni verranno codificate ed eseguite. Ogni Bundle composto da tre slot, cio3 istruzioni; il processore puó eseguire al massimo 2 bundle alla volta, ovvero 6 istruzioni. I Bundle vengono indicati con parentesi graffe . All’inizio del Bundle si puó scivere il tipo di Bundle. esempio: {. mmi add loc3, loc2, loc5 add loc7=loc7,loc8 xor loc2=loc2,loc3 } {. mmi ld4 loc9=[loc9] ld4 loc10=[loc10] shr loc11=loc11,3 } Qui sono indicati 2 boundle, entrambi di tipo memory-memory-integer. In ognuno ci sono 3 istruzioni che devono essere nel posto giusto allinterno del Bundle. Ad esempio, le istruzioni Integer vanno all’ultimo slot. Per ulteriori informazioni su come avviene l’esecuzione o i vari tipi di Bundle vedere la sezione Architettura. 26 CAPITOLO 1. DESCRIZIONE DELL’ASSEMBLER ITANIUM Capitolo 2 istruzioni principali 2.1 1 2 3 4 Add add r1=r2,r3 add r1=r2,r3,1 adds r1=imm14,r3 addl r1=imm22,r3 La add ha la forma o add registro registro come in 1, o add registro immediato come in 3. In 2 l’operazione svolta r2+r3+1. Ci sono 2 forme per gli add con immediati, perché Itanium distingue tra immediati di lunghezze diverse. La causa di questo é il formato delle istruzioni. Anche se ogni istruzione viene codificata sempre su 41 bit, i campi cambiano, e in alcune istruzioni é possibile inserire immediati solo di certe dimensioni. Qui si distingue tra immediati di 14 bit o minori, e quelli di al max 22 bit. Se si deve sommare una costante con piú di 22 bit bisogna prima caricare la costante con una movl in un registro. 2.2 Alloc 1 alloc r1=ar.pfs,i,l,o,r La alloc alloca sullo stack register il numero di registri richiesto che risulta la somma di i+l+o. i sta per registri di input, il numero di registri in cui vengono passate le funzioni. l sta per local, il numero di registri usati internamente dalla procedura. o sta per output, il numero di registri usati per passare paramatri alle sottoprocedure. R sta per rotating, controlla il meccanismo di rotazione dei registri. Vedere Register Rotation per approfondimenti. In questo caso r é sempre posto a zero. ar.pfs é l’application register che controlla lo stack engine, ovvero il meccanismo di register stack. esempio: alloc loc1=ar.pfs,2,10,0,0 Questa istruzione alloca sullo stack register 12 registri, di cui i primi due sono usati per ricevere i parametri di ingresso, 10 sará anno usati internamente alla funzione. I registri disponibili per la procedura saranno loc1-loc13. 27 28 CAPITOLO 2. ISTRUZIONI PRINCIPALI 2.3 1 2 And and r1=r2,r3 and r1=imm8,r3 Quando si usano delle costanti, la massima lunghezza della costante é di 8 bit, oltre bisogna caricare la costante in un registro con una movl ed usare la forma 1. 2.4 Cmp-Cmp4 Compare e Compare 4 bytes sono le due istruzioni di compare, compare 4 byte é usata per confrontare i valori a 32 bit. Esistono molti modificatori per queste 2 operazioni. Ci sono modificatori per i valori con segno, senza segno e che determinano come i predicate register verranno settati. La forma base 1 2 cmp.crel p1, p2=r2,r3 cmp4.crel p1, p2=r2,r3 crel puó essere eq per equal, ne per non equal, lt per low than, ecc. Vengono confrontati i due valori contenuti in r2 e r3. Le istruzioni settano i due predicate registers p1 e p2 a seconda che la condizione sia vera o falsa. Se la condizione é vera p1 e posto a 1, se é falsa p1 posto a 0. P2 vienesettato sempre all’inverso di p1, se p1 vale 1 p2 vale 0 e viceversa. I due predicate registers possono poi essere messi nel codice precedendo le istruzioni. esempio: (p1) add loc7=loc7,loc5 (p2) add loc7=loc7,loc6 A seconda che la condizione sia vera o falsa loc7 sará sommato o loc5 o loc6. 2.5 Dep 1 dep r1=r22,r3,pos6,len4 dep. z r1=r2,pos6,len6 Deposit serve per prelevare da un registro un gruppo di bit che iniziano al bit 0, di lunghezza specificata da len4, per poi depositarli in un altro registro alla posizione indicata da pos4. puó essere utile in alcuni casi, l’unico problema che la lunghezza del gruppo di bit che vengono depositati al massimo espressa su 4 bit, ed il gruppo di bit quindi al massimo di 15 bit. Se si vuole fare deposit di un numero di bit superiore non c’é modo. La seconda versione dep.z azzera i bit del registro destinazione e ha valori di pos e len su 6 bit. 2.6 1 2 Extr extr r1=r3,pos6,len6 extr.u r1=r3,pos6,len6 2.7. LD 29 Extract, al contrario di dep, estrae un gruppo di byte da un registro e li mette in un altro registro a partire dal bit 0. Il gruppo di bit di lunghezza len6 e inizia in posizione indicata da pos6. Questa istruzione molto utile perché permette di selezionare dei bit da un registro, operazione molto comune negli algoritmi crittografici. L’unico punto a sfavore é che se ne puó eseguire solo una per colpo di clock. 2.7 Ld La load ha molte forme e molti modificatori; questi permettono di specificare quanti byte devono essere letti da memoria, se la cache deve considerare il valore caricato come di uso frequente oppure no, se la cache deve fare l’advance load della word successiva al valore caricato in vista di una load uleriore o no, e cosı́ via. I modificatori non verranno presentati qui. Per approfondimenti consultare [1]. 1 2 3 ldsz r1=[r3] ldsz r1=[r3],r2 ldsz r1=[r3],imm9 sz sta per size; puó valere 4, 8 o 16, indicati quanti nyte sono letti. In tutte e tre le forme l’indirizzo contenuto in r3. In 2 il valore di r3 aggiornato dopo la load, sommando r2. In 3 dopo la load a r3 viene sommato un immediato di 9 bit. Le ultime 2 forma sono di autoincremento. 2.8 1 2 3 4 5 6 Nop nop imm21 nop.i imm21 nop.b imm21 nop.m imm21 nop.f imm21 nop.x imm62 La nop é molto importante perché usata per riempire gli slot vuoti. Quando si hanno meno di 3 istruzioni per formare un bundle si possono usare delle nop per arrivare a tre istruzioni e usará e l’explicit bundling. Gli immediati sono ignorati dal processore, possono servire solamente come demarcazione in punti del programma. Esistono differenti tipi di nop; la prima solo una pseudo operazione e viene mappata su una delle successive. Ogni nop puó andare ad occupare un’unitá funzionale diversa, una unitá integer o di branch, oppure di memory, oppure di floating point o una x unitá . 2.9 Mix-Mux Per queste istruzioni si rimanda al mauale Intel [1]. 30 CAPITOLO 2. ISTRUZIONI PRINCIPALI 2.10 1 2 3 4 Shl Shr r1=r3,r2 shr.u r1=r3,r2 shr r1=r3,count6 shr.u r1=r3,count6 Lo shift Left semplicemente shifta a destra R3 di n posizioni pari al contenuto di R2 o dell’immediato count su 6 bit. 2.11 1 2 Shr Shl r1=r3,r2 shr r1=r3,count6 Lo shift Right semplicemente shifta a destra R3 di n posizioni pari al contenuto di R2 o dell’immediato count su 6 bit. 2.12 R2 Shrp R3 R1 Figura 2.1: Shrp 1 shrp r1=r2,r3,coun6 shif right pair é un’operazione molto utile: serve per eseguire i rotate fissi sia a destra che a sinistra. I due operandi r2 e r3 sono affiancati e considerati come un unico blocco di 128 bit; da questo blocco vengono selezionati 64 bit, a partire dalla posizione specificata da count6, e messi in un altro registro, come si puó vedere in figura 2.1. Per ottenere uno shift fisso, bisogna avere il valore da routare da 32 bit in r2. Si deve mettere lostesso valore in r3, ma nella parte alta del registro. Cosda avere, una volta eseguita la shrp, il valore da routare 2 volte affiancato. Poi in count6 si mette un valore pari a 32+il numero di bit di cui si vuole il rotate a sinistra oppure 32-il numero di bit di cui si vuole il rotate a destra. In r3 nei primi 32 bit si avrá il valore routato dei bit voluti. Unica accortezza é quella di azzerare i bit da 32 a 64 di r3, (quando necessario). 2.13. XOR 2.13 1 1 31 Xor xor r1=r2,r3 xor r1=imm8,r3 I due operandi sorgente sono r2 e r3 e quello destinazione r1, in caso di immediato la limitazione é che l’immediato deve essere su al massimo su 8 bit. 32 CAPITOLO 2. ISTRUZIONI PRINCIPALI Parte III Ottimizzazione degli Algortimi 33 2.14. INTRODUZIONE 2.14 35 Introduzione In questa parte verranno presentati gli algortimi ottimizzati. La parte degli algortmi é strutturata in modo da permettere una lettura piú semplice o una piú approfondita. Per ogni algoritmo vi é prima una parte che descrive in modo generale le caratteristiche dell’algortimo e le ottimizzazioni eseguite. Nella sezione successiva, chiamata ottimizzazione in dettaglio, vengono forniti maggiori dettagli, e vengono anche presentate e spiegate parti di codice assembler. La sezione Ottimizzazioni in Itanium riassume il lavoro svolto specificatamente per Itanium, traendo le conclusioni finali. Nel capitolo linee generali vengono introdotti dei metodi di ottimizzazione generali, validi per tutti gli algortimi e sono illustrate le situazioni che si incontrano spesso in tutti gli algoritmi. 36 Capitolo 3 Linee generali 3.1 Ottimizzazione generale del codice Le parti del codice che subiscono ritardi e su cui si puó operare un’ottimizzazione sono,in generale, le seguenti: • load-store • cicli tipo for o while • jump 3.1.1 Load-Store Il discorso sulle load vale per tutte le architetture su cui si usano le load, in generale nei processori RISC. Le load dalla memoria impiegano minimo 2 colpi di clock per essere eseguite; questo vuol dire che,dopo la load ,deve passare un colpo di clock prima di poter usare il valore caricato. Questo colpo di clock andrá sprecato. Il modo per risolvere il problema é anticipare il piú possibile le load, in modo tale da avere i valori caricati dalla memoria il prima possibile. Non tutte le load saranno anticipabili nel codice perché questo dipende da quando l’indirizzo a cui fa riferimento la load disponibile nel codice. Nel caso in cui l’istruzione di load e quella che ne usa il risultato siano vicine, il colpo di clock tra le due istruzioni deve essere riempito con istruzioni che non siano dipendenti dalla load, e far sı́ che possano essere eseguite prima del completamento della load. esempio: 1 xor R45=R45, R39 2 ld4 R40=[R40];; 3 xor R42=R40,R41 4 add R45=R45,R46 5 sub R47=R47,R50;; 6 xor R45=R45,R47;; Dopo la Load ci sará un colpo di clock in cui il processore non usato perché l’istruzione successiva di xor usa R40 che é il risultato della load. Si puó notare come le istruzioni 4 e 5 non dipendano dalla load. Il codice eseguito in 4 colpi di clock. Trasformando il codice cosı́ 37 38 CAPITOLO 3. LINEE GENERALI 1 xor R45=R45, R39 2 ld4 R40=[R40];; 3 add R45=R45,R46 4 sub R47=R47,R50;; 5 xor R42=R40,R41 6 xor R45=R45,R47;; Il buco dopo la load é stato riempito con le due istruzioni e il codice eseguito in 3 colpi di clock. Per le store il discorso é inverso; esse possono essere posticipate nel codice, ed eseguite quando si hanno degli slot liberi. 3.1.2 Cicli e loop unrolling Il classico ciclo del tipo: for i=1 to 10 do a[i]=a[i-1]+b c=c+f Quando questo ciclo compilato per ogni iterazione del ciclo verrá perso minimo un colpo di clock, perché al termine ci sará un’istruzione di compare che verifica se si é arrivati alla fine del ciclo e ci sará un salto al codice iniziale per una nuova iterazione. In realtá dipendentemente dall’architettura, si perderanno molti piú colpiú di clock tanto piú é lunga la pipeline. Essendo la pipeline Itanium molto lunga, si perderanno anche 10 colpi di clock per iterazione. La soluzione il loop unrolling, cioil codice viene srotolato, invece che usare il for si scrivono esplicitamente le varie iterazioni del ciclo, in modo da eliminare i branch al fondo di ogni iterazione. esempio: iterazione 1 a[1]=a[0]+b c=c+f iterazione 2 a[2]=a[1]+b c=c+f iterazione 3 a[3]=a[2]+b c=c+f iterazione 4 a[4]=a[3]+b c=c+f . . . . . . Il loop unrolling dá anche un’altra opportunitá perché ora che abbiamo le operazioni una dopo l’altra si puó sfruttare il parallelismo del codice operando uno scheduling sulle istruzioni, ovvero riordinando ed eseguendo quante piú operazioni possibili in parallelo. Ci sono due tipi di cicli: i cicli in cui le varie iterazioni sono dipendenti le una dalle precedenti, e quelle in cui sono invece indipendenti tra di loro. Il caso di prima quello in cui un’iterazione del ciclo dipende dalla precedente. piú le iterazioni sono indipendenti, piú si avrá parallelismo nel codice e piú si avrá beneficio dal loop unrolling e dall’ottimizzazione. 3.2. OTTIMIZZAZIONE DELL’ASSEMBLER 3.2 39 Ottimizzazione dell’assembler Quando si ha un codice assembler, quello che limita il parallelismo e la velocitá del codice sono : • dipendenze di dato • dipendenze di nome • dipendenze di controllo Le dipendenze di dato sono quelle in cui un’istruzione ha come operando un input un valore che l’output di una precedente operazione, quindi tra le due c’é una dipendenza di dato. esempio: XOR R40=R40,R42 ADD R41=R40,R46 Sono proprio queste dipendenze per cui non si puó fare nulla; esse sono indipendenti dall’architettura su cui viene compilato il codice e limitano il parallelismo del codice. Le dipendenze di nome sono quelle in cui due istruzioni usano lo stesso registro, ma non c’é dipendenza di dato tra le due. esempio: XOR R40=R41,R42 ADD R41=R39,R32 Queste dipendenze possono essere evitate nella scrittura del codice semplicemente cambiando il nome ai registri coinvolti. In particolare in Itanium, dato il gran numero di registri, non si verificano spesso dipendenze di questo tipo. Le dipendenze di controllo sono quelle relative ai branches. Un’istruzione dentro ad un if é dipendente dalla condizione dell’if e dal branch che eventualmente verreseguito. E’ utile,dove possibile, trasformare le dipendenze di controllo in dipendenze di dato. In Itanium ,per risolvere questo problema, esiste il meccanismo di Predication, che grazie ai predicati, come spiegato nell’apposita sezione, permette al processore di eseguire o meno delle istruzioni. 3.3 Fasi di Ottimizzazione degli Algoritmi Per Ottimizzare gli algoritmi di openssl sono state eseguite le seguenti fasi: 1. Stdio approfondito dell’algoritmo 2. scelta dell’implementazione 3. traduzione in assembler dello pseudocodice o codice C 4. debug 5. ottimizzazione e riordinamento del codice 6. debug 40 CAPITOLO 3. LINEE GENERALI Lo studio dell’algoritmo ha lo scopo di capire le caratteristiche principali dell’algoritmo e il suo funzionamento. La fase successiva prevede di scegliere tra le varie implementazioni dell’algoritmo quella che piú si adatta all’architettura Itanium, e se necessario apportare delle modifiche a queste implementazioni che sono scritte in pseudocodice o in C. Scelta l’implementazione, questa viene tradotta in assembler Itanium, dopo che si sono scelte le istruzioni da usare. In alcuni casi sono state eseguite prove con differenti versioni di codice assembler per vedere quale risultava il piú efficiente. Un esempio é la permutazione iniziale del DES, di cui sono state realizzate 2 versioni: una usando l’istruzione extr. u e l’altra usando solo shift+and. Attraverso una lunga fase di Debug, nella quale vengono testati i vari spezzoni di codice uno per uno per poi essere montati insieme, si arriva ad una versione funzionante del codice assembler. A questo punto il codice non ancora ottimizzato, anzi, puó addirittura risultare piú lento del codice in C. Nella fase di ottimizzazione vengono apportare le ultime modifiche al codice per renderlo piú veloce possibile e soprattutto vengono riordinate le istruzioni e racchiuse in budles. La fase di scrittura del codice e quella di ottimizzazione non sono del tutto separate, perché mentre si scrive il codice si cerca di scriverlo giá ottimizzato o comunque pronto per esserlo in un ulteriore fase. La fase di riordinamento del codice é essenziale e deve essere eseguita con cura, perché si rischia di perdere colpi di clock solo invertendo l’ordine di due istruzioni. 3.4 3.4.1 Operazioni presenti in tutti gli algoritmi Uso di Costanti Gli algoritmi usano spesso costanti da sommare o mettere in xor. Di solito queste sono costanti a 32 bits. Dato l’alto numero di registri disponibili, é conveniente caricarle all’inizio dell’algoritmo in un registro e mantenerle, in modo da non doverle piú ricaricare. Per questo si usa l’istruzione movl(move long). esempio: movl R50,0x0000000034b56a76 L’istruzione movl usa 2 slot all’interno del bundle; per questa ragione é meglio usarla in uno spazio del codice in cui c’é uno spazio libero di 2 slot. E’ facile che si generi uno split issue se non posizionata bene. Di solito si usa in un bundle con prima un’istruzione Ingeger o ALU. ES: { xor R43=r44,R45 movl R50,0x0000000034b56a76 } 3.4.2 Selezione di bits da registro Un’operazione molto comune consiste nel dover selezionare un gruppo di bits da un registro. Questa operazione é eseguita di solito con uno shift + un and. Co lo shift si porta il gruppo di bits da selezionare in basso nel registro, con l’and e una maschera opportuna si selezionano solo i bits che occorrono. Per esempio, per selezionare i bits dal 20 al 26 del registro R40. Le operazioni da compiere sono: 3.4. OPERAZIONI PRESENTI IN TUTTI GLI ALGORITMI 41 Shift right R40 di 20 posizioni and R40,0x3f Su Itanium, poiché i registri sono da 64 bit, per poter eseguire un and con una maschera piú lunga di 22 bit si deve caricare la maschera in un registro con una movl. Un altro modo per selezionare i bits dato dall’uso dell’istruzione extr. Questa é studiata apposta per selezionare un insieme di bits da un registro e posizionarli in un altro registro. Sembrerebbe che con una sola istruzione si possa eseguire il tutto. In realtá quando le selezioni di bits da registro sono molte (come accade negli algoritmi esaminati), l’uso di extr. u non é molto conveniente. La questione che extr.u é eseguibile solo dall’unitá funzionale I0, e quindi una sola extr.u puó essere eseguita per colpo di clock. Usando shift+and, gli shift possono essere eseguiti dalle unitá funzionali I0 e I1, quindi si possono eseguire 2 shift per colpo di clock. In ogni caso si eseguono al massimo 2 selezioni di bits da registro ogni 2 colpi di clock. In molti casi ho preferito usare gli shift perché sono piú facilmente riorganizzabili e pongono meno limitazioni, mentre gli extr.u sono molto limitativi in quanto possono essere eseguiti solo da una unitá funzionale. 3.4.3 Look up in tabella Per eseguire il look up in tabella c’é bisogno dell’indirizzo base di questultima; esso sará caricato in un registro, dopodiché é necessario un displecement che sará sommato alla base. Itanium supporta solo le load in cui l’indirizzo contenuto in un registro, quindi non si possono usare modi di indirizzamento complicati come base+displecement, ma i due valori vanno sommati preventivamente. Il valore base della tabella va dichiarato nella sezione data, per esempio se il nome della tabella é Te0: . data . global Te0# successivamente si carica l’indirizzo in un registro, si puó sommare il displacement e caricare il valore da memoria: movl R50=Te0# add 51=0xf, R50 ld4 R51=[R51] 3.4.4 Uso delle load E’ frequente dover caricare da memoria valori tutti contigui in memoria. E’ il caso delle chiavi dei round. Solitamente le chiavi dei round sono posizionate in uno struct e i loro valori sono contigui in memoria. Siccome Itanium dispone di load dalle varie dimensione, si possono usare load4 che carica 32 bits, load8 che carica 64 bits ma anche load16 che carica 128 bits dalla memoria. Poi, una volta caricati i valori, se erano necessarie unitá da 32 bits, basterá dividere i blocchi. In realtla regola é quella di usare una load sempre della dimensione minima necessará ia. Se si lavora su dati a 32 bit meglio usare load4 e non load8. Il problema é che finché si usano load4, l’accesso alla cache non avrá problemi. Quando si usano le load8, gli accessi a memoria devono essere allineati a 8 bytes. La cache supporta accessi non allineati in qualche caso, ma si rischia 42 CAPITOLO 3. LINEE GENERALI di incappare in una penalitá in termini di colpi di clock persi. Considerando anche il fatto che Itanium permette 2 load per colpo di clock, se si usano valori a 32 bit, non c’é ragione di usará e load8, a meno che non sia strettamente necessario e si sia assolutamente certi che i valori da caricare siano allineati. Un esempio concreto stato quello di rc5: lo struct che conteneva le chiavi del round strutturato in questo modo: typedef struct rc5_key_st { int rounds; RC5_32_INT data[2*(RC5_16_ROUNDS+1)]; } RC5_32_KEY; Come si puó vedere, nello struct prima delle chiavi contenute in data c’é un int di 32 4 bytes. La versione iniziale di RC5 usava le load8. Siccome le chiavi si trovano dopo un int, sono sfasate tutte di 4 bytes, usando le load8, ci saranno degli accessi non allineati in memoria. Il risultato é stato che il codice era piú lento di quello originale. Modificando il codice e usando le load4 il codice risultava piú veloce. In questo caso si era perso circa il 15-20 % delle prestazioni. E’ molto importante non mischiare load4 con load8. 3.4.5 Xor tra una serie di valori A volte nel codice sono presenti linee di questo genere: A=A^B^C^D^E^F^G Questo codice sará compilato in modo da eseguire uno xor alla volta. In realtá , siccome lo xor é un’operazione che gode di proprietá commutative e associative, il codice sopra puó essere eseguito in 3 colpi di clock invece che in 6. Se si ipotizza che i valori di A, B, C. . siano contenuti nei registri R40, R41, R42, R43, R44, R45, R46, Il codice puó essere riscritto come: 1 clock xor R40=R40,R41 xor R48=R42,R43 xor R49=R44,R45 2 clock xor R40=R40,R48 xor R49=R49,R46 3 clock xor R40=R40,R49 Dato il gran numero di registri disponibili in Itanium é opportuno usare piú registri e risparmiare colpi di clock. 3.4.6 Htonl Ntohl In tutti gli algoritmi c’é il problema di interpretare le word o come host order o come network order. Per trasformare una word da host order a network order e viceversa, i singoli byte devono essere scambiati di posto nella word in questo modo: 3.4. OPERAZIONI PRESENTI IN TUTTI GLI ALGORITMI 43 host order: B1 B2 B3 B4 network order: B4 B3 B2 B1 Per eseguire tale operazione normalmente si usano degli shift piú or. In Itanium ci sono delle istruzioni particolari come la mux. Queste istruzioni fanno esattamente questa operazione. Quindi, per convertire da host to network order e viceversa, l’ideale é usare l’istruzione mux1 3.4.7 R32=R33,@rev Uso dei bundles per le istrzioni assembler Si puó usare sia l’explicit Boundling che l’implicit Boundling. Con l’implicit Boundling il compilatore deciderá i boundling, i dual issue e gli split issue. Usando l’explicit Boundling, si ha il pieno controllo sul processore, si sa quale istruzione verrassegnata a quale unitá funzionale, si sa quando il processore caricherun nuovo bundles, e quali istruzioni verranno eseguite in un dato colpo di clock. Se si decide di usará e i Bundles, é preferibile racchiudere in Bundles TUTTE le istruzioni del codice, non soltanto quelle piú critiche. E’ sconsigliabile mischiare istruzioni racchiuse in Bundles con operazioni non racchiuse in Bundles. Questo perché il compilatore in alcuni casi inserisce uno split issue anche dove non serve, e tutto il lavoro di perfezionamento del codice é inutile se si perdono colpi di clock in questo modo. Inoltre, é anche difficile vedere dove il compilatore ha inserito lo split issue, perché bisogna operare con un debugger sul codice compilato e controllare linea per linea se il codice come ce lo aspettavamo. Per esempio se inserisco un codice di questo tipo: {. mii load4 R40,[R40] xor R41=R41,R42 shr R43=R43,2 } xor R44=R44, R48;; Tutte queste istruzioni possono essere eseguite nello stesso colpo di clock perché non hanno dipendenze fra di loro. Come si vede le prime tre sono racchiuse in un bundle e l’ultimo xor no. E’ il caso in cui si é mischiato un bundle con operazoni non in bundle. Siccome lo xor é un’operazione che puó essere eseguita sia dalle unitá Integer che dalle unitá di memoria, lo xor puó realmente essere eseguito nello stesso clock perché ci sono ancora unitá di memoria disponibile. sarebbe ragionevole aspettarsi che il compilatore compiú li il codice posizionando le istruzioni per essere eseguite in un solo colpo di clock. Invece il compilatore compila il codice per essere eseguito in due colpi di clock in questo modo: load4 R40,[R40] xor R41=R41,R42 shr R43=R43,2 ;; xor R44=R44,R48;; 44 CAPITOLO 3. LINEE GENERALI Questo perché interpreta lo xor come istruzione Integer, e poiché le due unitá integer erano giá occupate dalle precedenti istruzioni di xor e shr, inserisce uno stop implicito ;;. Per questo c’é uno split issue. Per essere sicuri di come il codice verreseguito quindi preferibile usará e solamente l’explicit Boundling. Il codice corretto il seguente: {. mii load4 R40,[R40] xor R41=R41,R42 shr R43=R43,2 } {. mmf xor R44=R44,R48 nop. m 0 nop. f 0 } uso di NOP I nop risultano molto utili per l’uso di explicit Boundling; perché ogni volta non ci sono tre istruzioni per formare un Boundle si useranno dei nop per riempire gli spazi vuoti. 3.5 Regole Generali per Itanium I punti di forza di Itanium che devono essere sfruttati per l’ottimizzazione sono: 1. Gran numero di unitá funzionali 2. Efficiente architettura per l’accesso in memoria 3. Gran numero di registri 4. Architettura totalmente a 64 bit 5. vasto set di itsruzioni Dato il gran numero di unitá funzionali e il gran numero di registri, si deve cercare di sfruttare tutto il parallelismo presente nel codice. Si deveno cercare di usare piú registri per poter compiere piú operazioni possibili in parallelo, fino ad arrivare ad eseguire 6 istruzioni per colpo di clock. L’architettura potente per l’accesso in memoria, dispone di ben 4 porte per l’accesso, 2 per le load e 2 per le store, quindi si possono eseguire 2 load e 2 store per colpo di clock. Si deve cercare, in un codice in cui sono presenti molte load, di usare tutte le porte disponibili. Grazie all’architettura a 64 bit, quelle operazioni che prima erano dispendiose ora risultano piú semplici. In realtá tutti gli algoritmi da me analizzati non fanno uso dellarchitettura a 64 bit. Il vasto set di istruzioni puó essere sfruttato, laddove conveniente, usando operazioni specifiche di Itanium, che migliorino le prestazioni. Capitolo 4 DES 4.1 Caratteristiche Principali Le caratteristicge principali del DES sono: • unitá fondamentale a 32 bit • permutazioni • espansioni • s-boxes • i round lavorano su 2 blocchi da 32 bit • poco parallelismo nel codice, molte dipendenze di dato L’algoritmo DES é un algoritmo che non presenta molto parallelismo nel codice, quindi non otterrá un gran aumento di prestazioni quando eseguito su Itanium, anche se ottimizzato. 4.1.1 Permutazioni Le permutazioni non sono operazioni semplici, e siccome i processori general purpose non le supportano, devono essere ottenute da una serie di altre istruzioni piú semplici come shif, and e xor. Questa serie di operazioni atte ad ottenere una permutazione non sono da eseguire in contemporanea, bensı́ una istruzione deve essere effettuata di seguito all’altra. Questo non permette di trarre alcun vantaggio dal gran numero di unitá funzionali di Itanium, perché una, o al massimo due istruzioni sará anno eseguite contemporaneamente, mentre Itanium puó effettuare anche 6 istruzioni simultaneamente. L’unica possibilitá di ottimizzazione sulle permutazioni é quella di ridurre il numero di operazioni da eseguire usando delle istruzioni ad-hoc presenti in Itanium. In conclusione, non possibile ottimizzare molto le permutazioni, al massimo si puó ottenere un miglioramento intorno al 10%. 45 46 4.1.2 CAPITOLO 4. DES 32 bit-64 bit L’algoritmo DES prende in input un blocco da 64 bit ; questo blocco viene diviso in due blocchi di bit ognuno da 32 bit. Su questi 2 blocchi da 32 bit l’algortimo esegue le sue operazioni. Di conseguenza, l’unitá fondamentale dell’algoritmo é un blocco da 32 bit. Anche se Itanium dispone di registri a 64 bit, questi non possono essere sfruttati in quanto l’agoritmo lavora internamente su blocchi a 32 bit. 4.1. CARATTERISTICHE PRINCIPALI 4.1.3 47 Table look-up Il modo piú veloce per eseguire l’oprazione di s-boxe é utilizzare un table lookup in tabella. Questa tabella non é nient’altro che una matrice di byte in cui si entra con 6 bit selezionati da un registro da 32 bit e questa restituisce il risultato dell’s-boxe. poiché per fare table look-up si deve eseguire un accesso in memoria, é possibile sfruttare la capacitá di Itanium che é in grado di compiere 2 load da memoria nello stesso colpo di clock. In questo modo la velocitá di esecuzione dell’algoritmo verrá migliorata. Input m1 m64 Initial Permutation IP L0 R0 K1 F L1 R1 K2 F Rounds 2−15 L15 R15 k16 F L16 R16 FP Inverse Permutation Output c1 c2.. c64 Figura 4.1: struttura dell’algoritmo DES 48 4.2 4.2.1 CAPITOLO 4. DES Ottimzzazione DES in Dettaglio Initial Permutation e Final Permutation 58 60 62 64 57 59 61 63 50 52 54 56 49 51 53 55 42 44 46 48 41 43 45 47 IP 34 36 38 40 33 35 37 39 26 28 30 32 25 27 29 31 18 20 22 24 17 19 21 23 10 12 14 16 9 11 13 15 2 4 6 8 1 3 5 7 Tabella 4.1: DES Initial Permutation 40 39 38 37 36 35 34 33 8 7 6 5 4 3 2 1 48 47 46 45 44 43 42 41 16 15 14 13 12 11 10 9 IP 56 55 54 53 52 51 50 49 24 23 22 21 20 19 18 17 64 63 62 61 60 59 58 57 32 31 30 29 28 27 26 25 Tabella 4.2: DES Final Permutation Essendo queste due operazioni eseguite solo all’inizio e alla fine, esse non sono cosı́ decisive per l’ottimizzazione, in quanto nell’economia dell’algoritmo la cosa piú importante é ottimizzare i 16 round. La permutazione iniziale a anche quella finale che sono una l’inverso dell’altra prendono in input 64 bit e li trasformano in output in 64 bit. Sembrerebbe necessario compiere operazioni sui singoli bit per ottenere le due permutazioni; in realtá possono essere scomposte in operazioni che agiscano su gruppi di bit. Benché la permutazione sia su 64 bit, non si riesce ad ottenere un gran vantaggio usando registri a 64 bit. Questo perché tale permutazione é un’operazione molto complessa. Personalmente non ho trovato un modo per suddividerla in una serie di operazioni semplici su 64 bit, al massimo si puó suddividere la permutazione con una serie di operazioni su 32 bit. L’IP(Initial Permutation) puó essere suddivisa in 5 passi piú semplici, come illustrato in figura 4.2. Su 32 bit queste 5 fasi erano ottenute usando uno stratagemma per scambiare i bit da tra un registro ed un altro, sfruttando il fatto che dati due registri R1 e R2: R1^R2^R1=R2 e R2^R1^R2=R1 Su Itanium la soluzione migliore é quella di usare maschere e shift. Il miglioramento di prestazioni é circa del 10%. 4.2. OTTIMZZAZIONE DES IN DETTAGLIO 49 Input Block m1 m2 m3 m4 ..... ..m63 m64 R0 L0 m1 m2.. m32 m33 m34 m64 FASE 1 R0 R0 R0 R0 R0 L0 FASE 2 L0 FASE 3 L0 FASE 4 FASE 5 Figura 4.2: 5 Fasi dell’Initial Permutation L0 L0 50 CAPITOLO 4. DES E 32 4 8 12 16 20 24 28 1 5 9 13 17 21 25 29 2 6 10 14 18 22 26 30 3 7 11 15 19 23 27 31 4 8 12 16 20 24 28 32 5 9 13 17 21 25 29 1 Tabella 4.3: DES Expansion E P 16 29 1 5 2 32 19 22 7 12 15 18 8 27 13 11 20 28 23 31 24 3 30 4 21 17 26 10 14 9 6 25 Tabella 4.4: DES permutation P 4.2.2 16 Rounds I 64 bit di output dell’Initial Permutation sono divisi in due blocchi da 32 bit R e L. R viene espanso secondo la tabella E da 32 a 48 bit per poter essere messo in xor con la chiave del round di 48 bit. Il risultato é diviso in 8 gruppi da 6 bit ciascuno, e viene eseguita l’operazione di s-boxe. Ogni gruppo viene mandato ad un s-boxe diverso: il primo gruppo all’S-boxe 1 il secondo all’S-boxe 2 e cosı́ via. I 6 bit b1, b2..b6 vengono usati per indicizzare la tabella S-BOX in questo modo: le row vengono ottenute cosı́ row=2*b1+b6, le column sono ottenute considerando b2b3b4b5 come la rappresentazioni di un numero c su 4 bit, cio c compreso tra 0 e 16. Ognuno degli 8 S-boxe dá in output 4 bit, in totale si ottengono 32 bit. Questi 32 bit vengono permutati secondo la tabella P. Per maggiori dettagli consultare [3]. Le ottimizzazioni sul round sono le seguenti: Si puó notare che nell’espansione E ogni bit compare al max due volte. Questo vuol dire che un singolo bit di R messo in xor al massimo con 2 diversi bit della chiave. Inoltre si puó notare che se in E si considerano le righe pari e poi le dipari separatamente come illustrato nella tabella. Sia nelle righe pari sia in quelle dispari i bit di R non si ripetono mai e sono disposti in maniera crescente. Dalla tabella 4.5 si puó capire come i valori delle righe pari e dispari siano semplicemente i valori di R shiftati di 1 a destra nelle pari, perché queste iniziano col bit 32, e shiftati di 4 a sinistra nelle righe dipari perché queste iniziano col quarto bit di R. Quindi, invece di mettere E(R) in xor con la chiave, si puó mettere 2 volte R in xor con la chiave. Anche la chiave deve essere disposta in modo giusto perché il risultato sia uguale a prima. Il risultato di questo xor sará 64 bit invece che 48 bit, ma di questi 64 ne verranno selezionati 48 al 4.3. RISULTATI 32 8 16 24 1 9 17 25 E pari 2 3 10 11 18 19 26 27 51 4 12 20 28 5 13 21 29 4 12 20 28 5 13 21 29 E dispari 6 7 8 14 15 16 22 23 24 30 31 32 9 17 25 1 Tabella 4.5: Righe pari e disperi di E momento di eseguire gli S-Boxes. In breve, prima avevo 48 bit e li raggruppavo in 6 bit dal bit 1 al bit 5, dal bit 6 al bit 11, dal bit 12 al bit 18, ecc; ora devo selezionarli da 64 bit ma le operazioni sono identiche, perché si tratta sempre di shift + and.Ho applicato l’espansione E invece che 16 volte sui round 1 volta sulla chiave. In questo modo si evitata l’espansione E nei round. Per maggiori chiarimenti consultare [4] Abbiamo visto come debbano essere selezionati 6 bit alla volta da un registro. Itanium offre un’operazione dedicata a questo scopo, l’extr.u(extract unsigned), che permette di estrarre un numero di bit arbitrario in una posizione arbitraria da un registro e metterli in un altro registro. Tutto questo evita un’operazione, infatti nel modo tradizionale avremmo dovuto usare uno shift + un and. L’unico inconveniente é dato dal fatto che Itanium puó eseguire solo un extr. u per colpo di clock, quindi, in definitiva, usare un metodo o l’altro non cambia di molto le cose. Usando Extr.u risparmio un colpo di clock, pereseguo un Extr.u per clock; usando shift + and ci metto 2 colpi di clock ma posso farne 2 in contemporanea. Un’altra ottimizzazione data dall eseguire l’operazione di S-boxes prima, seguita poi dall’espansione P sul risultato degli S-boxes. Queste 2 operazioni possono essere fuse in un unica azione. Quando si accede alla tabella di look up per gli S-boxes, in output invece che il risultato dell’S-boxe soltando, c’é il risultato dell’S-boxe gipermutato secondo P. 4.2.3 Ottimizzazione in Itanium In questa sezione si riassumono le ottimizzazioni peculiari apportate al DES su Itanium. Per quando riguarda L’IP e la FP sono state usate istruzioni assembler proprie di Itanium come l’istruzione mix.1 che hanno portato ad un miglioramento di circa il 10%. Per quanto riguarda i round l’uso di istruzioni assembler proprie di Itanium come la Extr.u non ha portato benefici. Nei 16 round si é cercato di sfruttare al massimo la capacitdi accedere a memoria di Itanium, cioé impaccando e sistemando le operazioni in modo da sfruttare al massimo le 2 load per colpo di clock disponibili. In questo modo ogni round é stato migliorato di circa il 10%. In conclusione, l’algoritmo per la sua struttura interna a 32 bit e per la presenza di poco parallelismo nel codice, non trae particolare beneficio dall’architettura Itanium. 4.3 Risultati Le prestazioni sono state migliorate di circa il 10%. Mentre il codice C compilato impiegava 9.19 secondi per eseguire 167772160 volte la funzione DES, il codice ottimizzato impiega 8.27 secondi. I risultati del test di velocitá openssl non vengono riportati perché sussiste ancora un problema nel linking tra codice C 52 CAPITOLO 4. DES e codice assembler, che porta il codice ottimizzato a non essere piú veloce del codice C, ma questo non dipende dal codice, ma dal compilatore. Capitolo 5 RC5-32/12/16 5.1 Caratteristiche Principali Le caratteristiche principali sono: • semplicitá • uso di rotate variabili • uso di add • poco parallelismo nel codice, molte dipendenze di dato L’algortimo RC5 é molto semplice, le uniche istruzioni usate sono add, xor e rotate variabili. Come si vedrá piú avanti le operazioni in un round sono una dipendente dalla precedente, il parallelismo é molto scarso. Inoltre in Itanium non c’é un’istruzione che corrisponda allo shift variabile. Non vi é un particolare vantaggio prestazionale su Itanium, anzi, c’é da porre la massima attenzione alle somme che sono da eseguire in modulo 232 su registri da 64 bit. 5.1.1 Rotate variabili In Itanium 2 non c’é il supporto per i rotate variabili. L’unico supporto é quello per i rotate fissi, con l’istruzione shrp, come spiegato nella sezione istruzioni. I rotate variabili devono essere ottenuti usando 2 shift, uno xor e un sxt o zxt(vedi istruzioni).Il risultato é che uno shift variabile eseguito in 4 colpi di clock. 5.1.2 Add modulo 232 Per eseguire le add modulo 232 con operandi su 32 bit, bisogna fare attenzione, perché essendo i registri a 64 bit, laddove su registri a 32 bit il risultato di una somma di due numeri positivi generava un overflow e dá ı́ come risultato un numero negativo, ora su 64 bit tale risultato non sará negativo benspositivo, poiché lo spazio é diventato modulo 264 . La soluzione é quella di usare le istruzioni sxt4 o zxt4 a seconda dei casi che, ripetitivamente, estendono il segno o mettono a zero i bit dal trentaduesimo in poi del registro. Di conseguenza, per ogni add esiste un’operazione supplementare da eseguire. 53 54 CAPITOLO 5. RC5-32/12/16 5.2 Ottimzzazione RC5-32/12/16 in Dettaglio A=A+S[0] B=B+S[1] for i=1 to r do A=((A^B)<<B)+S[2*i]; B=((B^A)<<A)+S[2*i+1]; << =rotate variabile A e B sono i due blocchi da 32 bit bit in cui diviso l’input block da 64 bit S il vettore con le chiavi per round Figura 5.1: RC5 pseudocodice dell’algoritmo 5.2.1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 RC5-32/12/16 single round xor loc3=loc3,loc2 and loc9=0x1f,loc2;; sub loc1=32,loc9 shl loc4=loc3,loc9;; shr loc10=loc3,loc1;; or loc8=loc4,loc10;; add loc8=loc8,loc14 and loc3=loc8,loc15;; ld4 loc14=[in1],4 xor loc2=loc2,loc3 and loc1=0x1f,loc3;; sub loc4=32,loc1 shl loc9=loc2,loc1;; shr loc10=loc2,loc4;; or loc11=loc9,loc10;; add loc11=loc11,loc13;; and loc2=loc11,loc15 ld4 loc13=[in1],4;; //A^B //seleziono i 5 low bit di B //sottraggo da 32 il valore 5 low bit di B //shift left per il rotate //shif right per il rotate //or dei due shift //somma (a^B)<<B + S[1] //azzero bit 32-64 di A //load di S per il prossimo round //B^A //seleziono 5 low bit di A //sottraggo da 32 i 5 low bit di A //shift left dper secondo rotate //shift roght per secondo rotate //or dei due shift //somma di (B^A)>>a + S[2] //azzero bit 32-64 di B //load di S per il prossimo round Figura 5.2: RC5 single round Le operazioni piú dispendiose nel round sono i rotate variabili. Eseguire A << B significa ruotare A di un numero di posizioni pari ai 5 bit meno significativi di B. Per fare questo devo quindi selezionare i 5 bit meno significativi di B mettendo B in and con la maschera 0x1f. Guardando il codice e lo pseudocodice, le righe 1-8 del codice assembler corrispondono alla prima riga dentro il for dello pseudocodice. Alla riga uno c’é l’operazione (AB ), le righe dalla 2 alla 6 sono il rotate (AB ) << B. La settima riga é la somma con S[2]. S[2] é il loc14 e il suo valore era giá stato caricato precedentemente. L’ottava riga é un 5.2. OTTIMZZAZIONE RC5-32/12/16 IN DETTAGLIO 55 and per selezionare solo i primi 32 bit di A, perché dopo la somma, come detto in precedenza, dato che i registri sono a 64 bit ci possono essere dei bit oltre il trentadiesimo che devono essere eliminati o si deve estendere il segno. Nella riga 9 e 19 si caricano i valori di S per il round successivo. Dalla riga 10 alla 18 vi é l’aggiornamento di B che corrisponde nello pseudocodice alla seconda riga del for. Come si puó notare le operazioni per colpo di clock sono al massimo due, quindi viene usata un terzo della capacitá di esecuzione di Itanium. L’ottimizzazione che é stata portata consistenell’eseguire nel round precedente le load di S di quello successivo, in modo da avere i valori di S giá pronti e non perdere ulteriori colpi di clock. Non é possibile anticipare altre operazioni del round successivo nel round precedente, perché le istruzioni successive sono dipendenti dalle precedenti. Questo dato anche dal fatto che RC5 in un round aggiorna sia A che B. 5.2.2 Ottimizzazioni in Itannium Data la semplicitá dell’algoritmo, non c’é rano particolari ottimizzazioni da eseguire nello pseudocodice. Le ottimizzazioni nello scrivere l’assembler sono state quelle di anticipare al round precedente le load di S per il round succesivo. Da notare che siccome S é un vettore di valori da 32 bit tutti contigui, invece che usará e le load4 che caricano 32 bit alla volta avrei potuto usare le load8 che caricano 64 bit alla volta e dimezzare le load. Questa operazione non é conveniente perché in questo modo non si risparmiamo colpi di clock ma si rischia solo di incorrere in un accesso alla cache disallineato che viene penalizzato in termini di colpi di clock. Vedi Linee guida. 56 5.3 CAPITOLO 5. RC5-32/12/16 Risultati rc5 20971520 times on 16 size blocks 5242880 times on 64 size blocks 1310720 times on 256 size blocks 327680 times on 1024 size blocks 40960 times on 8192 size blocks Time 14. 6s 13. 94s 13. 76s 13. 71s 13. 7s Tabella 5.1: rc5-openssl speed results type rc5 16 bytes 22982. 49k 64 bytes 24070. 61k 256 bytes 24385. 49k 1024 bytes 24474. 42k 8192 bytes 24492. 29k Tabella 5.2: rc5-openssl speed results rc5 20971520 times on 16 size blocks 5242880 times on 64 size blocks 1310720 times on 256 size blocks 327680 times on 1024 size blocks 40960 times on 8192 size blocks Time 11. 08s 10. 43s 10. 24s 10. 19s 10. 18s Tabella 5.3: rc5-optimized code speed results type rc5 16 bytes 44267. 06k 64 bytes 46995. 00k 256 bytes 47527. 52k 1024 bytes 47662. 55k Tabella 5.4: rc5-optimized code speed results 8192 bytes 47527. 52k Capitolo 6 SHA-1 6.1 Caratteristiche Principali Le caratteristiche principali dell’algortimo sono: • epsansione di 128 bit a 640 bit. • uso di rotate fissi • molto parallelismo nel codice • in ogni round vengono aggiornati solo 2 dei 5 registri usati • uso di molti registri per massime prestazioni L’algortimo SHA1 riceve in input un numero di bit arbitrario, li considera 128 bit alla volta e produce in output una stringa da 128 bit. I round sono 80, quindi l’obbiettivo principale ottimizzare al massimo la durata di un round, perché risparmiare anche un solo colpo di clock per round significa risparmiare 80 colpi di clock. Le operazioni dei round possono essere parzialmente sovrapposte, in modo da risparmiare ulteriori colpi di clock. I registri utilizzati sono molti, perché piú cresce il parallelismo del codice, piú si avrá necessitá di usare piú registri. Questo perché i vari round che sono eseguiti in parallelo devono usare ognuno un diverso set di registri. Altra particolaritá é che in ogni round vengono coinvolti 5 registri in input, ma solo 2 di essi vengono aggiornati, gli altri restano uguali per il round successivo. Questo fa sı́ che alcuni registro debba mantenere il proprio valore per 4 round, portando ad un aumento nel numero di registri usati. Inoltre Come si puó vedere dal codice presente il openssl dal file sha locl l’espansione delle word inizia con il round 16. Anche l’espansione eseguita in parallelo con il codice che elabora le word ha bisogno di altri registri per poter lavorare. Oltre alle operazioni semplici, quali xor and ecc., l’algortimo usa rotate fissi. Itanium ha il supporto per i rotate fissi con l’uso dell’istruzione shrpair. Ulteriori spiegazioni verranno fornite in seguito. 57 58 6.2 6.2.1 CAPITOLO 6. SHA-1 Descrizione dell’algoritmo e ottimizzazione Descrizione dei round Come si puó vedere dallo pseudocodice in figure 6.1 l’algoritmo é composto da 80 round. Ci sono 4 gruppi di round:dall’1 al 20, dal 21 al 40, dal 41 al 60, dal 61 all’80. Questi 4 gruppi si differenziano solamente per le costanti usate e per la funzione F, G, H che cambia in ogni gruppo. I round hanno tutti una struttura simile, prendono in input 5 valori e li elaborano. Guardando il codice in figura 6.2, in cui BODY 00 15 é una macro per un round definita sopra, si puó notare la struttura dell’algortimo. I valori in input al primo round sono A, B, C, D, E, T. I valori aggiornati in questo round, come si vede dal codice di BODY 00 15, sono quelli di T e di B, gli altri valori restano uguali. Al round successivo i valori vengono semplicemente passati in input a BODY 00 15 shiftati di una posizione a destra; il secondo round riceve in input T, A, B, C, D, E. Questa struttura si ripete per tutti i round. La struttura puó essere semplificata come in figura 6.3. Dai colori si puó notare come i valori iniziali di colore nero perdurino fino al round 4 perché E resta tale fino al quarto round. Al settimo round i valori in input saranno di nuovo come quelli del primo(anche se modificati nel corso dei round). Il blocco base composto da 6 round. Usando registri giusti nei rounnd giusti si puó usare un blocco base di 6 round. 6.2.2 Espansione delle word L’espansione delle word che nello pseudocodice in figura 6.1 era eseguito prima di tutti i round, viene eseguito solo quando necessario, per risparmiare tempo. L’espansione genera le word dalla 17 alla 79; poiché le word vengono usate in ordine una per round, la diciassettesima word non sará usata prima del diciassettesimo round, ed é per questo che le espansioni nel codice openssl iniziano al round 16. Per ogni espansione di una word vengono usati 6 colpi di clock, ma questi possono avvenire in contemporanea con l’algoritmo che elabora le word round per round. In realtá il codice openssl compilato perde comunque dei colpi di clock per eseguire le espansioni delle word. Quello che si é cercato di fare invece nel codice assembler é far iniziare le espansioni delle word prima, al secondo round, e tenere i valori del vettore X in 16 registri fin da subito, in modo da eseguire le espansioni perfettamente in parallelo all’altro codice usando le unitá funzionali libere. Questo stratagemma si puó vedere passando dalla versione finale non ottimizzata dell’algoritmo a quella ottimizzata, in cui le espansioni prima eseguite in blocchi a parte, vengono ora mischiate al codice dei round ed assorbite cosı́ dall’algortimo. In questo modo si sono risparmiati 2-3 colpi di clock per round. La difficoltá di inserire il codice delle espansioni in mezzo all’altro codice é data dalla lunghezza finale del file, 1800 righe circa. E’ necessario quindi distribuire il codice su 1800 righe prestando attenzione ai registri usati e cercando di non intralciare le istruzioni del codice dei round. 6.3. OTTIMIZZAZIONE SHA-1 NEL DETTAGLIO 6.3 6.3.1 59 Ottimizzazione SHA-1 nel dettaglio Singolo Round Per eseguire il rotate fisso di un regisrto si usa l’istruzione shrpr, come illustrato nella sezione istruzioni. Analizzando per esempio i round 1 e 2, si puó notare come nel primo l valore A é usato ruotato di 5, nel secondo A é ruotato di 30. Queste operazioni si ripetono in tutti i round. Per risparmiare colpi di clock, quando nel primo round si sono settati i due registri per l’istruzione shrpair, si ruota A di 5 e anche A di 30, ovvero si eseguono 2 shrpair su A. Il secondo risultato verrá usato successivamente. Dal momento che, come si puó vedere in figura 6.3 molti valori in input rimangono invariati in output al round, alcune delle operazioni del round successivo, possono essere eseguite in anticipo. Un esempio é dato dall rotate di prima. Altro esempio l’operazione nel secondo round E=ROTATE(T, 5)+f(A, B, C)+D+X2+Y1. La load di X2 puó essere fatta in anticipo, Y2 una costante, C e D non vengono modificato nel primo round. Con questi valori si puó eseguire anticipatamente giá nel primo round la somma X2+Y1+D, cosı́ , per aggiornare E, resterá da sommare nel secondo round il ROTATE(T, 5) e f(A, B, C). Si puó quindi raggiungere una parziale sovrapposizione tra un round e quello successivo. 6.3.2 Espansione delle word Le word vengono tenute nei registri R34-R50, escluso R36. Per i round 116 i registri R34-R50 contengo le word X[0]-x[15], per i round 16-32 le word contengono le word X[16]-X[31] e cosı́ via. L’espansione delle word la cui parte di codice si puó vedere in figura 6.4 puó venire mischiata, come giá spiegato in precedenza, al codice dei round. La parte difficile consiste nel far usare all’espansione delle word dei registi non usati in quel momento dal codice dei round. Il tutto complicato ulteriormente perché l’espansione della word 1 puó sovrapporsi con quella della word 2 o della word 3 e cosı́ via. In pratica, si inserisce il codice dell’espansione ovunque ci sia una unitá funzionale lasciata libera dal codice dei round. Nella versione finale del codice le istruzioni marcate con etichette del tipo ESP1 34 2 sono le istruzioni di espansione. E’ vero che Itanium ha 6 unitá funzionali, ma ha solo 2 unitá Integer, quelle usate per eseguire le istruzioni integer, quali sono gli shift a shrpair. Il limite maggiore quindi causato da queste due unitá funzionali, perché Itanium non puó eseguire piú di due shift per clock e non piú di un shrpair per clock. 6.3.3 Ottimizzazione in Itanium SHA-1 é un algoritmo con molto parallelismo all’interno, di conseguenza é stato possibile sfruttare tutte e 6 le unitá funzionali di Itanium. In questo modo i colpi di clock impiegati per l’espansione delle word sono quasi totalmente assorbiti all’interno del codice dei round, risparmiando il 20% dei colpi di clock. Un altro 10% é stato guadagnato sovrapponendo in parte il codice di un round e quello del round successivo. E’ stato possibile grazie al fatto che in un round si aggiornano solo 2 valori di quelli in input al round, cosche alcune operazioni del round successivo(quelle che operano sui valori non modificati dal precedente round) possono giá essere eseguite nel round precedente. Grazie a queste migliorie si 60 CAPITOLO 6. SHA-1 é ottimizzato il tempo di esecuzione di SHA-1 di circa 32% rispetto al codice openssl originale. 6.4 Risultati SHA1 128 10485760 times on 16 size blocks 2621440 times on 64 size blocks 655360 times on 256 size blocks 163840 times on 1024 size blocks 20480 times on 8192 size blocks Time 6. 04s 5. 96s 5. 96s 5. 94s 6. 00s Tabella 6.1: SHA-1:openssl speed results type SHA1 128 16 bytes 27776. 85k 64 bytes 28149. 69k 256 bytes 28149. 69k 1024 bytes 28244. 47k 8192 bytes 27962. 03k Tabella 6.2: SHA-1:openssl speed results SHA1 128 10485760 times on 16 size blocks 2621440 times on 64 size blocks 655360 times on 256 size blocks 163840 times on 1024 size blocks 20480 times on 8192 size blocks Time 4. 43s 4. 42s 4. 41s 4. 40s 4. 45s Tabella 6.3: SHA-1:optimized code speed results type SHA1 128 16 bytes 37871. 82k 64 bytes 37957. 50k 256 bytes 38043. 57k 1024 bytes 38130. 04k 8192 bytes 37701. 61k Tabella 6.4: SHA-1:optimized code speed results Nelle tabella sono riportati i risultati ottenuti compilando il codice di SHA-1 senza e con il codice assembler Itanium. LA funzione implementata in assembler Itanium SHA1 BLOCK HOST ORDER é stata migliorata del 26,6%. Il codice C impiegava 6.04 secondi per eseguire 10485760 volte la funzione, il codice assembler impiega 4.43 secondi. 6.4. RISULTATI 1-Definizione di costanti h1, h5=5 costanti iniziali y1, y4=4 costanti additive per round 2- Pad in input riceve una bitstring di lunghezza b>=0 si aggiunge un pad per arrivare ad un input composto da 16*m 32-bit words:x0, x1, x2. . . . x16m-1. 3-Inizializzione (H1, H2, H3, H4, H5) <- (h1, h2, h3, h4, h5) 4-Esapnsione: di definisce un vettore di 80 word X[80] si copiano le prime 16 word di input in x[0]-x[15] for j=16 to 79 do: Xj<-((Xj-3^Xj-8^Xj-14^Xj-16)<<-1) (dove <<-1 sta per shift left di uno( 5-Processing inizializziamo le variabili usate (A, B, C, D, E)<-(H1, H2, H3, H4, H5) t variabile temporanea le funzioni f, h, g sono: f(B, C, D) = ((C^D)&B) ^D h(B, C, D) = B^C^D g(B, C, D) = (B&C) | ((B|C)&D) (Round 1)for j=0 to 19 do t<-((A<<-5)+f(B, C, D)+E+Xj+y1) (A, B, C, D, E)<-(t, A, B<<-30, C, D) (Round 2)for j=20 to 39 do: t<-((a<<-5)+h(B, C, D)+E+Xj+y2) (A, B, C, D, E)<-(t, A, B<<-30, C, D) (Round 3)for j=40 to 59 do: t<-((a<<-5)+g(B, C, D)+E+Xj+y3) (A, B, C, D, E)<-(t, A, B<<-30, C, D) (Round 4)for j=60 to 79 t<-((A<<-5)+h(B, C, D)+E+Xj+y4) (A, B, C, D, E)<-(t, A, B<<-30, C, D) (H1, H2, H3, H4, H5)=<-(1+A, H2+B, H3+C, H4+D, H5+E) 6-Final H1, H2, H3, H4, H5 costituiscono il risultato finale Figura 6.1: Pseudocodice semplificato di SHA-1 61 62 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 CAPITOLO 6. SHA-1 #define BODY_00_15(i,a,b,c,d,e,t,xi) \ (t)=xi+(e)+K_00_19+ROTATE((a),5)+F_00_19((b),(c),(d)); \ (b)=ROTATE((b),30); for (;;) { BODY_00_15( 0,A,B,C,D,E,T,X( 0)); BODY_00_15( 1,T,A,B,C,D,E,X( 1)); BODY_00_15( 2,E,T,A,B,C,D,X( 2)); BODY_00_15( 3,D,E,T,A,B,C,X( 3)); BODY_00_15( 4,C,D,E,T,A,B,X( 4)); BODY_00_15( 5,B,C,D,E,T,A,X( 5)); BODY_00_15( 6,A,B,C,D,E,T,X( 6)); BODY_00_15( 7,T,A,B,C,D,E,X( 7)); BODY_00_15( 8,E,T,A,B,C,D,X( 8)); BODY_00_15( 9,D,E,T,A,B,C,X( 9)); BODY_00_15(10,C,D,E,T,A,B,X(10)); BODY_00_15(11,B,C,D,E,T,A,X(11)); BODY_00_15(12,A,B,C,D,E,T,X(12)); BODY_00_15(13,T,A,B,C,D,E,X(13)); BODY_00_15(14,E,T,A,B,C,D,X(14)); BODY_00_15(15,D,E,T,A,B,C,X(15)); Figura 6.2: stralcio del file primi 15 round 6.4. RISULTATI A 63 B C D E A B C D T A B C D=Rotate(E,5)+f(T,A,B)+C+X3+y1 E T B A D C ROUND 4 C=Rotate(D,5)+f(E,T,A)+B+X4+y1 E=Rotate(E,30) C ROUND 3 D T=Rotate(T,30) D ROUND 2 E E=Rotate(T,5)+f(A,B,C)+D+X2+y1 A=Rotate(A,30) E ROUND 1 T=Rotate(A,5)+f(B,C,D)+E+Xj+y1 B=Rotate(B,30) T T E T A B ROUND 5 B=Rotate(C,5)+f(D,E,T)+A+X5+y1 D=Rotate(D,30) B C D T E B ROUND 6 A=Rotate(B,5)+f(C,D,E)+T+X6+y1 B=Rotate(B,30) A A C D E T I cambiamenti di colori rappresentano i valori che vengono aggiornati ad ogni round Figura 6.3: Struttura dei primi 6 round di SHA-1 ROUND 7 64 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 CAPITOLO 6. SHA-1 //espansione //X[0] xor loc52=loc34,loc37 xor loc51=loc43,loc48;; xor loc34=loc51,loc52;; shl loc52=loc34,1 shr.u loc51=loc34,31;; or loc34=loc52,loc51;; sxt4 loc34=loc34 //X[1] xor loc52=loc36,loc38 xor loc51=loc44,loc49;; xor loc36=loc52,loc51;; shl loc52=loc36,1 shr.u loc51=loc36,31;; or loc36=loc52,loc51;; sxt4 loc36=loc36 Figura 6.4: SHA-1 espansione di 2 word in Assembler Itanium Capitolo 7 AES (Rijndael) 7.1 Caratteristiche Principali • ottimizzazione che riduce l’algoritmo a look up in tabella • unitá minima usata é il byte • molto parallelismo nel codice • uso di molte load L’algortimo AES non ha una struttura semplice come per esempio RC5, ci sono piú fasi e operazioni da svolgere sulle word in input. Tuttavia, grazie ad un’ottimizzazione giá apportata da Rijndael stesso, l’algortimo si riduce a look up in tabella e a shift + and per selezionare i bit per fare il look up. Le caratteristiche dell’algortimo cosı́ implementato sono perfette per Itanium. Come si puó vedere dal codice nel file AESround.s, s0, s1, s2, s3 sono i valori di input, e Te1, Te2, Te3 sono le tabelle su cui fare look up, in un round ci sono 16 load(i 16 accessi nelle tabelle), e le righe 2, 4, 6, 8 del primo round possono essere eseguite tutte in parallelo. 7.1.1 Scelta dell’implementazione Tra le implementazioni di AES ci sono 2 filoni distinti, uno che esegue tutte le operazioni di AES una per una e l’altro che invece usa i look up in tabella. Dal momento che Itanium puó eseguire fino a 6 istruzioni per colpo di clock, anche la prima soluzione avrebbe potuto andare bene, perché anche se il numero di istruzioni sarebbe piú alto rispetto alla soluzione con i look up in tabella, si potrebbero comunque eseguire 6 operazioni per clock. Usando invece i look up in tabella e le load, il numero di istruzioni é minore, ma ogni load impiega 2 colpi di clock per caricare il valore dalla memoria. Tuttavia, siccome Itanium puó eseguire 2 load per colpo di clock, cioé 2 look up in tabella per colpo di clock, é stata scelta l’implementazione con i table look up che risulta la piú veloce su Itanium. 65 66 CAPITOLO 7. AES (RIJNDAEL) State Cipher Key Add Round Key 1−Sub Bytes 2−Shift Rows 3−Mix Colums Round Key 0−9 Add Round Key 1−Sub Bytes 2−Shift Rows Round Key 10 Add Round Key Figura 7.1: AES-struttura dell’algortimo 7.2 Ottimizzazione AES nel dettaglio L’algortimo ha 4 fasi distinte per ogni round: 1. Bytes Sub 2. Shift Row 3. Mix Column 4. Add Round Key Sub Bytes In questa fase l’input di 128 bit viene considerato 1 byte alla volta. Ogni byte viene sostituito tramite look up in tabella S-BOX in figura 7.1. I primi 4 bit vengono usati come coordinate X, gli ultimi come coordinate Y. Per esempio il byte 0x13 verrá sostituito con 0x7d. In realtá queste trasformazioni sono operazioni sui campi di Galuoise, ma i dettagli matematici qui non sono importanti. Quello che conta é che l’unitá minima di bit usata in questa fase é 8 bit, quindi anche se l’algoritmo prende in ingresso 128 bit in realtá qui li considera byte per byte. 7.2. OTTIMIZZAZIONE AES NEL DETTAGLIO hex 0 1 2 3 4 5 6 7 8 9 a b c d e f x 0 63 ca b7 04 09 53 d0 51 cd 60 e0 e7 ba 70 e1 8c 1 7c 82 fd c7 83 d1 df a3 0c 81 32 c8 78 3e f8 a1 2 77 c9 93 23 2c 00 aa 40 13 4f 3a 37 25 b5 98 89 3 7b 7d 26 c3 1a ed fb 8f ec dc 0a 6d 2e 66 11 0d 4 f2 fa 36 18 1b 20 43 92 5f 22 49 8d 1c 48 69 bf 5 6b 59 3f 96 6e fc 4d 9d 97 2a 06 d5 a6 03 d9 e6 67 S-BOX Y 6 7 6f c5 47 f0 f7 cc 05 9a 5a a0 b1 5b 33 85 38 f5 44 17 90 88 24 5c 4e a9 b4 c6 f6 0e 8e 94 42 68 8 30 ad 34 07 52 6a 45 bc c4 46 c2 6c e8 61 9b 41 9 01 d4 a5 12 3b cb f9 b6 a7 ee d3 56 dd 35 1e 99 a 67 a2 e5 80 d6 be 02 da 7e b8 ac f4 74 57 87 2d b 2b af f1 e2 b3 39 7f 21 3d 14 62 ea 1f b9 e9 0f c f3 9c 71 eb 29 4a 50 10 64 de 91 65 4b 86 ce b0 Tabella 7.1: AES S-BOX Shift Rows Considerando l’output di 128 bit della prima fase diviso in 4 righe da 32 bit ciascuna. Le 4 righe vengono ruotate a destra di 0, 1, 2, 3 byte rispettivamente, come si vede in figura 7.2. L’unitá minima di bit usata qui é la riga cioé 32 bit. [ht] d4 27 11 ae e0 bf 98 f1 b8 b4 5d e5 1e 41 52 30 d4 bf 5d 30 e0 b4 52 ae b8 41 11 f1 1e 27 98 e5 Tabella 7.2: AES Shift Rows d4 02 03 01 01 04 bf 01 02 03 01 66 5d 01 01 02 03 81 30 03 01 01 02 e5 Figura 7.2: AES-Mix colums Mix Colums In questa fase il blocco di 128 bit viene considerato in 4 colonne di 32 bit ciascuna. Ogni colonna moltiplicata per una matrice, con una moltiplicazione d d7 a4 d8 27 e3 4c 3c ff 5d 5e 95 7a bd c1 55 54 e ab 72 31 b2 2f 58 9f f3 19 0b e4 ae 8b 1d 28 bb f 76 c0 15 75 84 cf a8 d2 73 db 79 08 8a 9e df 16 68 d4 d4 d4 d4 CAPITOLO 7. AES (RIJNDAEL) x x x x 02 01 01 03 + + + + bf bf bf bf x x x x 03 02 01 01 + + + + 5d 5d 5d 5d x x + x 01 03 02 01 + + + + 30 30 30 30 x x x x 01 01 03 02 = = = = 04 66 81 e5 Figura 7.3: AES-Operazioni di Mix Colums matriciale. Come si puó vedere dalla figura 7.2, i valori della matrice sono solo 3. Da questa moltiplicazione tra matrici, si puó notare come un singolo byte darun contributo a tutti i byte della colonna in output. Per esempio il byte d4 verrá moltiplicato per 02 nelle prima riga, per 01 nella seconda e nella terza riga e per 03 nella quarta riga. L’unitá minima usata qui é la colonna, cioé 32 bit. Add Round key In questa fase il blocco da 128 bit viene messo in xor con la chiave del round di 128 bit. Il codice di schedulazione delle chiave usa le stesse operazioni dell’algoritmo di Shift Rows e Sub-Bytes. La generazioni delle chiavi per i round non verrá analizzta, perché non é essenziale per l’ottimizzazione in quanto interviene una solo volta all’inizio dell’algoritmo. Last Round La differenza dell’ultimo round é che manca la fase di Mix xolumns. 7.2. OTTIMIZZAZIONE AES NEL DETTAGLIO 7.2.1 69 Ottimizzazione dell’algoritmo Come descritto sopra nella fase di Sub Bytes, ogni byte viene sostituito con il suo corrispondente tramite S-box. Nella seconda fase di shift Rows si cambiano semplicemente le posizioni all’interno delle righe dei singoli byte, ma non vi é nessuna sostituzione di valore dei bytes. Inoltre, un byte iniziale dopo le prime 2 trasformazioni contribuisce per un solo byte in output, non ci sono variazioni nel numero di byte. Quindi, le prime due fasi possono anche essere invertite, si puó eseguire prima la fase di Shift Rows e poi la fase di Sub Bytes. La terza trasformazione di MixColums é anch’essa fissa e indipendente dall’input. Guardando la figura 7.2, si osserva che la colonna in input di 4 byte viene moltiplicata in successione per ogni riga della matrice per generare una nuova colonna. Osservando la figure 7.3, considerandola per colonne, si vede che la prima colonna é il primo byte in input, (d4), la seconda la prima riga della matrice, la terza é il secondo byte in input, la quarta la seconda colonna della matrice e cosı́ via. Di conseguenza, il primo byte moltiplicato per la prima colonna della matrice, il secondo byte per la seconda colonna della matrice e cosvia. Ogni byte in input da un contributo ad ogni riga di figura 7.3, cioé ogni byte da un contributo per ognuno dei byte in uscita. Le operazioni di somma nella figura 7.3 sono in realtá operazioni di somma senza riporto quindi degli xor. poiché il primo byte in input della colonna in input subirá sempre le stesse operazioni di moltiplicazione, e lo stesso accade per il secondo e anche per gli altri due, si puó usare un look up in tabella che restituisca il risultato. Si usano 4 tabelle, una per ogni byte in input. In queste tabelle si entra in input con un byte e si ritorna con 32 bits di risultato. Questi 32 bits di risultato corrispondono in figura 7.3 alle colonne della figura. Ottenuti i 4 risultati dei 4 look up, questi verranno messi in xor tra di loro (perché le somme sono degli xor)e sará ottenuta la colonna di output. Ora sappiamo che la terza fase é ottenuta tramite table look-up. Considerando le prime tre fasi in cui avevamo invertito le prime due, si puó notare che un byte in input subirá nella prima fase un cambiamento di posizione, ma non verrá modificato nel suo valore. Nella seconda e terza fase subirá 2 trasformazioni di fila: una quella di Sub Bytes e, successivamente, il look up in tabella della terza fase. Per ottimizzare ulteriormente, invece che eseguire separatamente le 2 trasformazioni, queste si possono unificare in un unico look up in tabella. A questo punto le tabelle usate per il look up prenderanno il posto delle operazioni di S-Boxes e di Mix Colomns. Per ottenere la prima fase di Shift Rows si usano degli shift + and per selezionare i bytes. La qarta fase di Add Round Key é semplicemente uno xor con la chiave del round. Esaminando ora nuovamente il codice nel file AESround.s si possonono capire le singole operazioni. Le tabelle Te0, Te1, Te2, Te3 sono le tabella che fanno le operazioni di Sub-Bytes e di Mix-Colums, gli shift ¡¡ sono le operazioni di Shift Rows e gli and 0xff selezionano i vari bytes uno per uno. I risultati dei look up in tabella sono poi messi in xor tra di loro, gli xor rappresentano le somme della figura 7.3. I valori sono poi direttamente messi in xor con la chiave del round kr[y]. L’ultimo round usa una tabella diversa, la tabella Te4, perché non c’é la fase di Mix Columns. 70 7.3 CAPITOLO 7. AES (RIJNDAEL) Ottimizzazione In Itanium Data la forte presenza di look up in tabella e quindi di load, le potenzialitá di Itanium riguardante gli accessi in memoria é sfruttata appieno. C’é molto parallelismo nel codice, e questo permette di sfruttare tutte le unitá funzionali di Itanium e di conseguenza la possibilitdi eseguire 6 operazioni per colpo di clock. Il fatto che, pur ricevendo in input 128 bit, l’algoritmo li elabora byte per byte, porta a non avere vantaggi dall’architettura a 64 bit di Itanium, e a non poter sfruttare i registri a 64 bit. Per selezionare i singoli byte, operazione che nel codice C é eseguita con shift e and, si potrebbero usare le istruzioni di extr.u, ma poiché queste sono molto limitanti perché si puó eseguire un solo extr.u per colpo di clock si é preferito usare gli shifts. Gli S-Boxes e la fase di Mix Columns, usano come input per i table look-up un singolo byte, ottenendo in output 32 bit. Su Itanium si puó ipotizzare l’uso di tabelle che prendano in input 16 bytes e restituiscano in output 64 bit. A sfavore di questa possibile scelta ci sono vari fattori: uno di questi é che la tabella risultante sarebbe troppo grossa, dovrebbe essere di 216 word da 64 bits. L’accesso a questa tabella cosı́ grossa causerebbe problemi anche su un’architettura come Itanium dotata di una cache grande e di una singola linea di cache L1 lunga 32 bytes. Si correrebbe il rischio di ripetuti miss nella cache di primo livello, portando a un enorme deterioramento delle prestazioni del codice. Altro fattore negativo che si dovrebbero usare le load8 invece che load4, e queste potrebbero causare problemi se gli accessi in memoria non fossero allineati. Pertanto, la soluzione di utilizzare tabelle che usino in input 8 bit e ne restituiscano 32 in output sembra quella ottimale. La istruzioni sono state combinate in modo da sfruttare al massimo le 2 porte di load per l’accesso alla memoria da un lato, e dall’altro le 2 unitá funzionali Integer che eseguono le istruzioni di shift. I risultati ottenuti sono un miglioramento delle prestazioni attorno al 30%. 7.4. RISULTATI 7.4 71 Risultati AES-cbc 128 10485760 times on 16 size blocks 2621440 times on 64 size blocks 655360 times on 256 size blocks 163840 times on 1024 size blocks 20480 times on 8192 size blocks Time 5. 96s 5. 24s 5. 07s 5. 02s 5. 02s Tabella 7.3: AES-openssl speed results type aes-128 cbc 16 bytes 28149. 69k 64 bytes 32017. 59k 256 bytes 33091. 16k 1024 bytes 33420. 75k 8192 bytes 33420. 75k Tabella 7.4: AES-openssl speed results AES-cbc 128 10485760 times on 16 size blocks 2621440 times on 64 size blocks 655360 times on 256 size blocks 163840 times on 1024 size blocks 20480 times on 8192 size blocks Time 3. 79s 3. 57s 3. 53s 3. 52s 3. 53s Tabella 7.5: AES-optimized code speed results type aes-128 cbc 16 bytes 44267. 06k 64 bytes 46995. 00k 256 bytes 47527. 52k 1024 bytes 47662. 55k 8192 bytes 47527. 52k Tabella 7.6: AES-optimized code speed results Il codice ottimizzato impiega 3.79 secondi per compiere 10485760 volte la funzion AES encrypt, il codice C impiega 5.96 secondi. Il miglioramento é del 36%. In realtá usando la procedura AES encrypt all’interno di openssl, il miglioramanto é minore perché il codice assembler compilato viene poi inserito all’interno di altro codice C, quindi la parte di codice C rallenta le prestazioni. 72 CAPITOLO 7. AES (RIJNDAEL) Parte IV Appendix 73 Capitolo 8 Help Itanium/OpenVMS 8.1 Assembler Itanium Per quanto riguarda lo scrivere in assembler Itanium, ampia spiegazione é giá stata fornita prima. Qui verrá aggiunto un esempio di codice in assembler Itanium che potrá essere usato per iniziare. mask1=0x00000000ffffffff .data .text .global encrypt_proc .proc encrypt_proc encrypt_proc: alloc loc0=ar.pfs,3,26,0,0 movl loc15=mask1 add loc16=96, in1 add loc22=64, in1;; add in1=128, in1 br.ret.sptk.many b0 .endp encrypt_proc Si scrive un file tipo quello sopra in cui al posto di encrypt proc c’é il nome della procedura che si vuole implementare. Una volta scritto il file assembler Itanium con qualsiasi editor disponibile, puó essere salvato con qualsiasi estensioni, meglio .s cosı́ non ci si confonda. 8.2 Compilare il codice Il compilatore assembler disponibile é il compilatore dell’Intel IAS. Il compilatore gcc non era in grado di compilare il codice assembler. I comandi per compilare il codice sono semplicemente IAS nomefile. s -o libreria.o Il compilatore, se non ci sono errori, compiú la il file in un file output oggetto di nome libreria.o. Se in questo file c’erano riferimenti esterni a funzioni 75 76 CAPITOLO 8. HELP ITANIUM/OPENVMS o variabili esterne, queste non vengono considerate in suddetta fase perché non sono ancora risolte. 8.3 Linking A questo punto si deve eseguire il linking del file C, che richiama la funzione, e di questo file appena compilato. La soluzione é scrivere un file di header .h in cui c’é la firma della procedura sopra implementata e includerlo nel file C. Nel file .h troveremo una linea tipo la seguente: void encrypt proc(int a, int b); A questo punto si puó compiú lare il tutto e linkare con gcc. L’istruzione del tipo: gcc libreria.o main.c -o main.exe Da notare che é importante specificare -o main.exe perché gli eseguibili in openVMS hanno estensione exe. Se non si mette l’estensione si rischia poi di fare confusione sul file che si vorrá eseguire.(Anche perché openVMS conserva le versioni successive dei vari file salvati). Non resta che eseguire il file compilato e linkato. 8.4 Debugger Ora, per vedere come funziona il codice e come stato compilato useremo il debugger di openVMS GBD. Per usare il debugger é consigliabile aggiungere al comando gcc il flag -g che compila il file con la tabella dei nomi, in modo da poter poi settare dei break utilizzando direttamente i nomi delle funzioni. Il comando per poter debuggare il codice é RUN MAIN /DEBUG run é il comando che fa eseguire il codice, main il nome dell’eseguibile senza estensione, /debug indica che il codice va eseguito in modalitdebugger. Una volta eseguito il comando si entra nell’ambiente GBD. Si possono trovare online sul sito dell’HP degli ottimi manuali di GBD, oppure si puó usare l’aiuto del debugger semplicemente digitando HELP. I comandi principali sono: 1 2 3 4 5 SET BREAK NOME FUNZIONE -->setta un break point alla procedura di nome nome_funzione GO -->salta al prossimo break EXAMINE NOME\_FUNZIONE:NOME\_FUNZIONE+300 -->lista a video il codice compilato tra le linee agli indirizzi nome_funzione e nome\_funzione+300 STEP/INSTRUCTION 10 -->avanza di 10 istruzioni assembler STEP -->avanza di una riga C 8.4. DEBUGGER 6 EXAMINE/EXADECIMAL R43 7 EXAMINE/EXADECIMAL R32:R40 8 QUIT 77 -->visualizza il contenuto del registro r43 in esadecimale -->visualizza i contenuti dei registri da r32 a r4 in hex -->esce da GBD E’ importante mettere in maiuscolo i nomi delle funzioni nei comandi, perché questi vengono riconosciuti solo se scritti cosı́ . Il modoficatore /EXADECIMAL puó anche essere sostituito con /BIN ed altri. Per la scrittura di codice usando Explicit Bundle si consulti la sezione apposita. 78 CAPITOLO 8. HELP ITANIUM/OPENVMS Capitolo 9 Summary The power of Itanium consists of its capabilty in executing 6 istructions simultaneously;furthermore Itanium has an efficient architecture to access to cache and memory. Itanium also has a large number of registers and assembler istructions. The algorithms that can improve their performances in a significant way are those which have big parallelism in the code and those which use a lot of memory accesses. I tryed to optimized the following algorithms: • DES • RC5 • AES • SHA-1 9.1 DES Main features: • 32 bit block is the fundamental work unitá • permutations • expansions • s-boxes • little parallelism in the code, lots of data dependences DES has very little parallelism in the code, so DES’s performances aren’t improved very much on Itanium, because the latter doesn’t use its 6 execution units for DES contemporaneously, it only uses one or two execution units at the same time. As far as Expansion IP and FP are concerned it hadn’t been possible to optimize the code over 10 %. 9.1.1 DES Results The implemented procedure is DES encrypt. By executing this procedure 167772160 times, the optimzed code needs 8.27 seconds to be over, whereas C code ends in 9.19 seconds. 79 80 CAPITOLO 9. SUMMARY 9.2 RC5-32/12/16 Main features: • semplicity • variable rotate • use of add • little parallelism in the code, lots of data dependences As in the case of DES, RC5 has not a lot of parallelism in the code, so it can’t improve its performances significantly. Furthermore RC5 uses variable rotate and Itanium doesn’t implemnent this istruction. So RC5 improves its performances by 10% . rc5 20971520 times on 16 size blocks 5242880 times on 64 size blocks 1310720 times on 256 size blocks 327680 times on 1024 size blocks 40960 times on 8192 size blocks Time 14. 6s 13. 94s 13. 76s 13. 71s 13. 7s Tabella 9.1: RC5-openssl speed results type rc5 16 bytes 22982. 49k 64 bytes 24070. 61k 256 bytes 24385. 49k 1024 bytes 24474. 42k 8192 bytes 24492. 29k Tabella 9.2: RC5-openssl speed results rc5 20971520 times on 16 size blocks 5242880 times on 64 size blocks 1310720 times on 256 size blocks 327680 times on 1024 size blocks 40960 times on 8192 size blocks Time 11. 08s 10. 43s 10. 24s 10. 19s 10. 18s Tabella 9.3: RC5-optimized code speed results type rc5 9.3 16 bytes 44267. 06k AES Main features: 64 bytes 46995. 00k 256 bytes 47527. 52k 1024 bytes 47662. 55k 8192 bytes 47527. 52k 9.3. AES 81 • expansion from 128 bits to 640 bit. • fixed rotate • lots of parallelism in the code • only 2 of the 5 registers used are refreshed in each round • use of many registers for optimized performances AES is an algorithm that strongly improves its performances on Itanium, because AES uses all the 6 execution units . AES also uses fixed rotate istruction, and Itanium has the support for that by using the istruction shrp. Because only 2 of the 5 registers used in one round are refreshed in each round, the istructions of one round and the istructions of the next round can be partially overlapped. AES-cbc 128 10485760 times on 16 size blocks 2621440 times on 64 size blocks 655360 times on 256 size blocks 163840 times on 1024 size blocks 20480 times on 8192 size blocks Time 5. 96s 5. 24s 5. 07s 5. 02s 5. 02s Tabella 9.4: AES-openssl speed results type aes-128 cbc 16 bytes 28149. 69k 64 bytes 32017. 59k 256 bytes 33091. 16k 1024 bytes 33420. 75k 8192 bytes 33420. 75k Tabella 9.5: AES-openssl speed results AES-cbc 128 10485760 times on 16 size blocks 2621440 times on 64 size blocks 655360 times on 256 size blocks 163840 times on 1024 size blocks 20480 times on 8192 size blocks Time 3. 79s 3. 57s 3. 53s 3. 52s 3. 53s Tabella 9.6: AES-optimized code speed results type aes-128 cbc 16 bytes 44267. 06k 64 bytes 46995. 00k 256 bytes 47527. 52k 1024 bytes 47662. 55k Tabella 9.7: AES-optimized code speed results 8192 bytes 47527. 52k 82 CAPITOLO 9. SUMMARY 9.4 SHA-1 Main features: • optimez C code reduces the algorithm to a lots of look up in table • works on single byte • lots of parallelism in the code • use of many loads from memory(table looks up) SHA-1, in its optimized form, is reduced to many look up in table. SHA-1 improves its performances on Itanium because it has many parallelisms in the code and it uses many loads from memory. Because Itanium can execute 2 loads at the same time, the code is executed fast. [] SHA1 128 10485760 times on 16 size blocks 2621440 times on 64 size blocks 655360 times on 256 size blocks 163840 times on 1024 size blocks 20480 times on 8192 size blocks Time 6. 04s 5. 96s 5. 96s 5. 94s 6. 00s Tabella 9.8: SHA-1:openssl speed results type sha1 128 16 bytes 27776. 85k 64 bytes 28149. 69k 256 bytes 28149. 69k 1024 bytes 28244. 47k 8192 bytes 27962. 03k Tabella 9.9: SHA-1:openssl speed results SHA1 128 10485760 times on 16 size blocks 2621440 times on 64 size blocks 655360 times on 256 size blocks 163840 times on 1024 size blocks 20480 times on 8192 size blocks Time 4. 43s 4. 42s 4. 41s 4. 40s 4. 45s Tabella 9.10: SHA-1:optimized code speed results type sha1 128 16 bytes 37871. 82k 64 bytes 37957. 50k 256 bytes 38043. 57k 1024 bytes 38130. 04k Tabella 9.11: SHA-1:optimized code speed results 8192 bytes 37701. 61k Bibliografia [1] Intel. 245319-Istruction Set Reference. Intel, 2002. [2] Intel. 245318-System Archutecture Guide. Intel, 2002. [3] Handbook of Applied Cryptography. CRC Press Inc, 1997. [4] David C. Feldmeier. A high-speed software des implementation. Technical report, Computer Communication Research Group Bellcore,Morriston,NJ, 1989. [5] Intel. 245317-Application Architecture Guide. Intel, 2002. [6] Intel. 24536301-IA-64 Assembly Language Reference Guide. Intel, 2002. [7] Intel. 24547403-Intel Itanium Processor Reference Manual fo rSoftware Optimization. Intel, 2002. [8] Intel. 2511103-Intel Itanium 2 Processor Reference Manual. Intel, 2002. [9] Intel. Intel Itanium Architecture Assembly Language Reference Guide. Intel, 2002. [10] Intel. asmusrgd- IA-64 Assmbler. Intel, 2002. [11] Itanium Software Optimization Strategies. [12] IA-64 Architecture. [13] Paul Berreto Vincent Rijmen, Antoon Bossolaers. rijndael-alg-fst.c -fast code for rijndael cipher. Technical report, 2000. [14] Ronald L. Rivest. The rc5 encryption algorithm. Technical report, MIT laboratory. 83