# Processore vettoriale

Un processore vettoriale (o array processor) è una CPU progettata per svolgere operazioni matematiche su più dati elementari contemporaneamente.

## Caratteristiche

Questo in contrasto con l'architettura classica di un processore scalare che prevede l'elaborazione di un singolo dato per volta. La maggior parte dei processori sono scalari (o esternamente lo sembrano). I processori vettoriali sono comuni nelle applicazioni scientifiche e sono spesso alla base dei supercomputer fin dagli anni ottanta. Con la fine degli anni novanta i microprocessori sono cresciuti di prestazioni e molti processori per applicazioni generiche si sono dotati di unità vettoriali o sono diventati vettoriali al loro interno. Nel 2000 IBM, Toshiba e Sony hanno iniziato lo sviluppo del processore Cell, un microprocessore ad elevate prestazioni dotato di svariate unità vettoriali e rivolto ad applicazioni che spaziano dalle console al supercalcolo.
Attualmente praticamente ogni CPU moderna include istruzioni vettoriali tipicamente conosciute come istruzioni SIMD. Le console per i videogiochi e le schede grafiche fanno un ampio uso di processori vettoriali dato che l'elaborazione di flussi audio e video in tempo reale è un campo che ben si presta all'elaborazione vettoriale.

## Storia

I primi sviluppi di processori vettoriali si ebbero alla fine degli anni 60 presso la Westinghouse durante il progetto Solomon. Il progetto Solomon mirava ad incrementare drasticamente le prestazioni matematiche utilizzando un elevato numero di semplici coprocessori matematici (o ALU) sotto il controllo di una singola CPU. La CPU avrebbe dovuto inviare l'operazione matematica comune a tutte le ALU, le ALU avrebbero prelevato i dati e dopo averli elaborati li avrebbero salvati. Questo avrebbe consentito lo svolgimento di un singolo algoritmo su molti dati contemporaneamente. Nel 1962 la Westinghouse decise di cancellare il progetto ma alcuni ricercatori fuoriusciti dalla società convinsero l'Università dell'Illinois (Urbana-Champaign) a riavviare le ricerche e a sviluppare l'ILLIAC IV. Inizialmente la macchina doveva essere dotata di 256 processori elementari e doveva sviluppare 1 GigaFLOPS ma nel 1972 quando venne presentata la macchina questa era dotata di 64 processori e sviluppava solo 100 MegaFLOPS (150 MegaFLOPS di picco). Nonostante l'ILLIAC IV sia considerato un insuccesso nelle applicazioni che trattavano elevate quantità di dati come la fluidodinamica computazionale la macchina era il più veloce supercomputer del pianeta.
La prima implementazione vincente dei processori vettoriali si ebbe con la CDC STAR-100 e con il Texas Instruments Advanced Scientific Computer. L'ALU base dell'ASC utilizzava un'architettura a pipeline per eseguire le operazioni scalari e vettoriali ed era in grado di sviluppare 20 MegaFLOPS di picco durante l'esecuzioni delle operazioni vettoriali. ALU estese gestivano due o quattro pipeline e quindi le prestazioni si raddoppiavano o si quadruplicavano. La banda fornita dal sottosistema di memoria era in grado di soddisfare le ALU estese. Invece la STAR era più lenta delle altre macchine della CDC come il CDC 7600 dato che nel caso dell'esecuzione di istruzioni scalari la sua decodifica era lenta. Nel caso dell'esecuzione delle istruzioni vettoriali invece la macchina era veloce nella decodifica e quindi questo compensava la lentezza delle operazioni scalari.
La tecnica vettoriale venne sfruttata in pieno dal famoso Cray-1. Questo computer a differenza dello STAR o dell'ASC non manteneva i dati esclusivamente in memoria ma aveva anche 8 registri vettoriali a 64 bit nella CPU. Le istruzioni vettoriali erano eseguite sui dati memorizzati nei registri e quindi erano molto più veloci di quelle svolte accedendo alla memoria principale. In aggiunta il progetto prevedeva diverse pipeline per le diverse operazioni e quindi la pipeline di somma/sottrazione era diversa dalla pipeline delle moltiplicazioni. Quindi più istruzioni vettoriali potevano essere concatenate ed eseguite simultaneamente. Il Cray-1 era in grado di eseguire 80 MIPS ma concatenando fino a tre serie di istruzioni si poteva arrivare a 240 MIPS un numero di operazioni rispettabile anche al giorno d'oggi.
Altri esempi seguirono. La CDC seguì nuovamente la strada del calcolo vettoriale con l'ETA-10, una macchina che non vendette bene e convinse la società ad uscire dal settore del supercalcolo. Alcune compagnie giapponesi (Fujitsu, Hitachi e NEC) presentarono delle macchine basate su registri vettoriali più veloci delle macchine statunitensi e più piccole. Le Floating Point System della Oregon furono utilizzati per realizzare array di processori per minicomputer prima di sviluppare i suoi minisupercomputer. Tuttavia Cray continuava a dominare la scena del supercalcolo con le sue macchine Cray-2, Cray X-MP e Cray Y-MP. In seguito il mercato si concentrò sui sistemi a parallelismo massivo piuttosto che sui sistemi vettoriali.
Oggi i moderni computer sono in grado di decodificare un flusso video che un supercomputer degli anni 70 avrebbe decodificato con difficoltà. Ciò e possibile sia per le elevate frequenze di funzionamento sia per l'aggiunta di unità vettoriali nei microprocessori che accelerano le operazioni multimediali. Usualmente queste unità vengono indicate come unità SIMD. In queste implementazioni le unità vettoriali sono incluse nelle CPU scalari e vengono utilizzate solamente dai programmi che le richiedono esplicitamente.

## Funzionamento

In genere le CPU sono capaci di manipolare uno o due frazioni di dati alla volta. Per esempio la maggior parte delle CPU hanno un'istruzione che essenzialmente dice “somma A a B e memorizza il risultato in C” mentre altre come il MOS 6502 necessitano di due o tre istruzioni per eseguire questo genere di operazioni. I dati per A, B e C possono teoricamente essere contenuti nell'istruzione stessa; tuttavia le cose sono raramente così semplici. In genere il dato non viene inviato in forma grezza, ma è invece “puntato” passando un indirizzo alla locazione di memoria che contiene il dato. Decodificare questo indirizzo e tirare fuori il dato dalla memoria, occupa del tempo. Con l'incremento della velocità delle CPU, questa latenza di memoria è diventata un impedimento sempre più grande per le prestazioni finali.
Per ottimizzare i tempi, le più moderne CPU utilizzano una tecnica conosciuta come pipeline di istruzioni, nella quale l'istruzione passa attraverso differenti sotto-unità a turno. La prima sotto-unità legge l'indirizzo e lo decodifica, la successiva reperisce i valori situati a quell'indirizzo e la successiva ancora elabora l'effettivo calcolo matematico. Il “trucco” della pipeline consiste nel cominciare la decodifica della successiva istruzione persino prima che la precedente sia stata effettivamente elaborata dalla CPU, nello stile di una catena di montaggio, cosicché la decodifica degli indirizzi sia costante. Ogni particolare istruzione necessita la stessa quantità di tempo per essere completata, ma la CPU può processare un'intera serie di operazioni molto più velocemente rispetto a quanto farebbe calcolandone una alla volta.
I processori vettoriali portano questo concetto un passo più avanti. Anziché utilizzare la tecnica di pipeline solo con le istruzioni, la utilizzano anche sui dati stessi; sono quindi capaci di applicare una medesima istruzione ad un grosso lotto di dati in parallelo, senza decodificare ogni singola istruzione dalla memoria. Questo spesso, come ad esempio nelle applicazioni multimediali, consente di risparmiare grosse quantità di tempo.
Un processore vettoriale contiene al suo interno una memoria condivisa, varie unità di calcolo, e un'unità di controllo che gestisce il parallelismo tra le unità di calcolo. Il parallelismo è gestito a livello hardware e non è visibile a livello di programmazione.
Per illustrare che differenza comporta l'elaborazione vettoriale, consideriamo il semplice compito di sommare due gruppi di dieci numeri. In un normale linguaggio di programmazione si scriverebbe un “ciclo” che prende per ogni coppia il primo numero e lo somma al secondo.
Ecco un esempio in pseudocodice:
Ma per un processore vettoriale, questo compito è differente:
Diversi sono i motivi inerenti a questo approccio che portano risparmio di tempo. Innanzi tutto, solo due traduzioni di indirizzi sono necessarie; a seconda dell'architettura, questo può rappresentare un risparmio significativo di per sé. Un altro risparmio è dato dal recupero e decodifica dell'istruzione stessa, che deve essere effettuato solo una volta anziché dieci. Il codice stesso è inoltre più compatto, il che può portare ad un più efficiente utilizzo della memoria.
Ma oltre a questo, il processore vettoriale ha tipicamente una forma di implementazione superscalare, il che significa che il compito viene svolto da più ALU anziché una sola. Dato che l'output di un comando vettoriale non dipende dall'input di qualsiasi altro, i “blocchi” di dati possono essere quindi elaborati separatamente, moltiplicando la quantità di tempo risparmiata. Come è già stato detto precedentemente, l'implementazione del Cray porta questa tecnica ad un raffinamento maggiore, permettendo a differenti tipi di operazioni di essere “trasportati” al tempo stesso. Consideriamo il codice che somma due numeri e li moltiplica ad un terzo; nel Cray questi venivano recuperati tutti in una volta, e sia sommati che moltiplicati in una singola operazione.
Nello stesso pseudocodice di sopra, essenzialmente è così:
Le operazioni matematiche vengono così completate molto più velocemente, l'unico fattore limitante è dato dal tempo richiesto per recuperare i dati dalla memoria.

### Memoria

Esistono due modi in cui un processore vettoriale accede ai suoi operandi, a seconda della sua progettazione interna. Nell'organizzazione da memoria a memoria (memory to memory), gli operandi vengono recuperati dalla memoria, elaborati, e salvati direttamente nella memoria stessa. Nell'organizzazione da registro a registro (register to register) gli operandi vengono invece, prima segmentati, e poi caricati in registri vettore; durante l'elaborazione i risultati vengono poi salvati in un terzo registro vettore. Solo in seguito vengono poi immagazzinati nella memoria.
Il vantaggio della gestione da memoria a memoria è dato dal fatto che permette al processore di calcolare vettori molto lunghi senza mai fermarsi per “spezzarli” in sotto-vettori, ma i tempi di latenza della memoria sono maggiori rispetto a quelli dei registri; questo porta ad un rallentamento nei tempi di recupero dei dati dalla memoria.
Esempi di questa architettura sono il già citato Texas Instruments Inc. Advanced Scientific Computer e qualche altra macchina realizzata negli anni settanta. In seguito questa tecnica fu abbandonata a causa della latenza di memoria troppo elevata, che rendeva questi processori troppo lenti in operazioni su vettori piccoli, anche di 100 elementi. I processori vettoriali da registro a registro sono invece decisamente più efficienti nelle operazioni su vettori relativamente piccoli, e questa è la strategia maggiormente adottata fino al giorno d'oggi.

## Utilizzo

Non tutti i problemi possono essere ottimizzati con questo tipo di soluzione. Innanzi tutto aggiungere questo genere di istruzioni rende più complessa l'architettura della CPU, la quale complessità potrebbe rendere altre istruzioni più lente. Le istruzioni più complesse aggiungono inoltre ulteriore complessità ai decodificatori, che potrebbero essere quindi più lenti nella decodifica delle istruzioni più comuni come una semplice somma. Inoltre, i processori vettoriali lavorano meglio solo quando ci sono grosse quantità di dati su cui lavorare. Per queste ragioni, questo tipo di CPU viene principalmente utilizzato nei supercomputer, e non nelle applicazioni di utilizzo comune, dato che i supercomputer stessi sono generalmente situati in laboratori meteorologici o laboratori fisici, dove si lavora spesso con grossi quantitativi di dati. Infine la realizzazione di questi processori è costosa; essendo utilizzati solo per scopi ben specifici, i costi di progettazione sono una parte consistente del costo totale, e questo tipo di architettura necessita di grossi quantitativi di memoria cache ad alta velocità, anch'essa costosa.