# Performance dei calcolatori

Negli ultimi anni i miglioramenti nella tecnologia dei semiconduttori (dimensione e velocità di clock) e i miglioramenti nell'architettura dei computer (compilatori HLL, UNIX, architetture RISC) hanno permesso lo sviluppo di computer lightweight e linguaggi di programmazione interpretati.
In questa lezione vedremo quali sono le metriche e le tecniche comunemente usate per valutare le prestazioni di una macchina, concentrandosi in particolare sui tempi di calcolo.

## Valutazione delle performance

Per definire "quale computer è più veloce dell'altro" dobbiamo decidere alcune metriche. Solitamente sono due le metriche considerate, che dipendono dai soggetti coinvolti del sistema:
* Computer system user: obiettivo minimizzare il tempo di risposta $exec_{time}=time_{end}-time_{start}$
* Computer center manager: obiettivo massimizzare il rateo di completamento (throughput) $n.jobs/sec$
Si ricorda che ${\text{throughput}}={\frac {1}{\text{average response time}}}$ solo se non ci sono sovrapposizioni tra i task (parallelismo), altrimenti risulta maggiore.
Esempio:
    Un buffet con 5 stazioni, ogni persona impegna ogni stazione per 2 minuti. Ogni persona impiega 10 minuti a riempire il vassoio. Se consideriamo che al buffet ci accede una persona alla volta il throughput è 1/10, altrimenti è ½ perché 5 persone occupano contemporaneamente le 5 stazioni.

### Funzione di speedup

Diciamo che "X è n% più veloce di Y" se:
    ${\frac {exec_{time}(x)}{exec_{time}(y)}}=1+{\frac {n}{100}}$
inoltre definiamo:
    ${\text{performance}}(a)={\frac {1}{exec_{time}(a)}}$
da cui:
    ${\frac {{\text{performance}}(y)}{{\text{performance}}(x)}}=1+{\frac {n}{100}}$
Infine possiamo definire la funzione di speedup come:
    $speedup(x,y)={\frac {{\text{performance}}(x)}{{\text{performance}}(y)}}$
Se dobbiamo decidere cosa ottimizzare nel sistema come facciamo? Usiamo il buon senso: spesso ci si trova a dover valutare un trade-off, dobbiamo favorire il caso frequente rispetto a quello infrequente. Ad esempio: in una CPU le operazioni di fetch e decode sono più utilizzate della moltiplicazione, conviene quindi ottimizzare le prime.
Per selezionare il caso frequente e valutare l'impatto si usa la Legge di Amdahl, spiegata nella prossima sezione.

### Amdahl's Law

Sia E un miglioramento del sistema, allora possiamo scrivere:
    $speedup(E)={\frac {\text{performance con W}}{\text{performance senza E}}}={\frac {\text{exectime senza E}}{\text{exectime con E}}}$
Solitamente il miglioramento non coinvolge tutto il sistema, allora si ricorre alla formula:
    ${\text{exectime}}_{new}={\text{exectime}}_{old}\times \left[(1-Fraction_{E})+{\frac {Fraction_{E}}{speedup(E)}}\right]$
da cui:
    $speedup_{\text{complessivo}}={\frac {{\text{exectime}}_{old}}{{\text{exectime}}_{new}}}={\frac {1}{(1-Fraction_{E})+{\frac {Fraction_{E}}{speedup(E)}}}}$
Il miglioramento massimo ottenibile (situazione ideale) è quindi:
    $speedup_{max}={\frac {1}{1-Fraction_{E}}}$
Esempio: Si vuole valutare l'impatto di una nuova CPU 10x più veloce, su un server che rimane bloccato per il 60% del tempo in operazioni di I/O. Abbiamo allora:
    $speedup_{\text{complessivo}}={\frac {1}{(1-Fraction_{E})+{\frac {Fraction_{E}}{speedup(E)}}}}={\frac {1}{(1-0.4)+{\frac {0.4}{10}}}}=1.56$

#### Corollario

Se un miglioramento è usabile solo per una frazione di un task, non possiamo velocizzare il task più del reciproco di 1 meno la frazione.

## Computer performance

Concentriamoci ora sulle performance di un processore. Possiamo identificare due parametriche o metriche applicabili:
* Tempo di risposta: è il tempo di latenza per completare un task, inclusi accessi a disco, altri I/O, tempi del sistema operativo, ecc. Dipende quindi da molti fattori e spesso è difficilmente calcolabile in maniera precisa.
* Tempo di CPU: non include i tempi di I/O e corrisponde al tempo per il quale la CPU lavora effettivamente a eseguire un determinato programma. Dato un programma P, si calcola come:
       $CPU_{time}(P)={\frac {\text{cicli di clock necessari ha eseguire P}}{\text{frequenza di clock}}}$
Analizziamo ora in dettaglio come calcolare il tempo di CPU, scomponendolo nelle sue componenti:
    $CPU_{time}(P)={\frac {\#{\text{Instruzioni}}}{IC}}\times {\frac {\#{\text{Cicli}}}{CPI}}\times {\frac {Secondi}{T_{clock}}}$
Dove:
* IC (Instruction Count): numero di istruzioni nel programma P. Dipende dall'algoritmo, dal compilatore utilizzato e dall'ISA del processore.
* CPI (Clock per Instruction): numero medio di clock necessari per eseguire un'istruzione. È determinato dall'ISA e dall'organizzazione della CPU (il programmatore non può farci niente). Per ridurre questo valore si utilizza la pipelining.
* $T_{clock}$: è il tempo che intercorre tra un fronte del clock e l'altro. L'inverso determina la frequenza del processore. Dipende dalla tecnologia hardware usata, dall'organizzazione e dal design del circuito.
Riassumiamo i parametri e da cosa sono influenzati:
| | Algoritmo | Compilatore | ISA | Organizzazione HW | Tecnologia HW |
| IC | SI | SI | SI | NO | NO |
| CPI | NO | (parzialmente) | SI | SI | NO |
| $T_{clock}$ | NO | NO | NO | SI | SI |
NOTA BENE: Per minimizzare il tempo di CPU (quindi migliorare le performance) non è importante minimizzare il singolo termine, ma tutto il prodotto. Inoltre i tre termini sono spesso correlati tra di loro (es. se modifico l'ISA per diminuire IC, devo prevedere un'organizzazione del circuito più complessa e quindi aumentare il tempo di clock).

### Altre metriche

Sono spesso utilizzate altre metriche, tra cui il throughput che utilizza diverse unità di misura, ad esempio:
* MIPS = Millions of Instructions Per Second = IC/(execution time * 10^6) = clock frequency / (CPI * 10^6)
** Execution time = Instruction count / (MIPS* 10^6)
* MFLOPS = Milions of Floating point Operation Per Second = Floating Point operations in program / (CPU time * 10^6)
** Più "sicuro" per determinare la potenza di calcolo perché le operazioni in virgola mobile (FP) sono solitamente indipendenti dal compilatore e dall'ISA.
** Casi in cui non sono comunque indipendenti: forti ottimizzazioni del compilatore, confronto tra architetture in cui mancano delle istruzioni (es. sin, cos, radice quadrata, ecc.)

## Benchmarks

Per valutare nella realtà la potenza di calcolo di un computer si fa riferimento ad appositi software, costruiti per testare le prestazioni secondo determinate metriche.
I benchmarks si dividono principalmente in quattro tipi:
* Real programs: programmi reali che rappresentano un workload reale. Esempi: un compilatore, un editor di testo, ecc.
* Kernels or microbenchmarks: sono un frammento di un programma, utili per concentrarsi su particolari caratteristiche (del processore, del disco, ecc.)
* Synthetic benchmarks: stessa filosofia dei precedenti, ma costruiti appositamente, di solito partendo da basi statistiche (frequenza delle operazioni e operandi presi da un largo insieme di programmi)
* Instruction mixes: benchmark particolari per valutare CPI.
Tuttavia i benchmark potrebbero non essere rappresentativi (ad esempio un benchmark per la CPU che conteggia anche le operazioni di I/O) e invecchiano presto: le aziende di compilatori, di hardware e di software ottimizzano i loro software per far raggiungere un valore alto a uno specifico benchmark, senza che questo coincida con un vero miglioramento delle performance. Per questo i benchmark devono essere periodicamente rivisti.
Quando si deve valutare, come fanno i benchmark, su più campioni le prestazioni, punto cruciale è capire che tipologie di metrica utilizzare:
* media aritmetica: è valida solamente se i programmi sono eseguiti con la stessa frequenza;
* media armonica: si usa quando velocizzare i benchmark lenti dà più vantaggi rispetto a velocizzare dello stesso modo i benchmark veloci. (Esempio: se in auto percorro 10 km a 30 km/h e 10 km a 70 km/h, la media non è 50 km/h!);
* media geometrica: si usa quando sono dati i ratei e non i tempi.

## Potenza ed Energia

La metrica Thermal Design Power (TDP) rappresenta un'indicazione del calore (energia) dissipato da un processore. Caratterizza sostanzialmente la potenza consumata e si utilizza per calcolare l'alimentatore e il sistema di raffreddamento necessari. Oggigiorno rappresenta un indicatore fondamentale per valutare la qualità di un sistema, soprattutto nei grandi centri di calcolo e supercomputer. Bisogna tenere sempre presente che però non rappresenta la potenza di picco reale che in certe occasioni si può ottenere. Per misurare il TDP si ricorre solitamente a energy-per-task, ovvero l'energia consumata per eseguire uno specifico task.
La velocità di clock influisce notevolmente sul TDP (quindi sui consumi e sulle temperature) ed è il principale motivo del perché la frequenza di clock ha smesso di crescere.

### Potenza dinamica e statica

Definiamo energia dinamica (dynamic energy) l'energia necessaria per portare un transistor dallo stato 1 allo stato 0 o viceversa. È calcolabile dalla formula:
    $E_{d}={\frac {1}{2}}\times {\text{Capacity Load}}\times {\text{Voltage}}^{2}$
a partire da questa definiamo anche la potenza dinamica:
    $P_{d}={\frac {1}{2}}\times {\text{Capacity Load}}\times {\text{Voltage}}^{2}\times Frequency$
Definiamo poi il consumo di potenza statico (static power consumption), e indica la corrente consumata naturalmente dalle imperfezioni dei transistor, anche quando non avviene alcuna transizione (legge di ohm):
    $P_{s}={\text{Current}}\times {\text{Voltage}}$
Le principali tecniche per ridurre la potenza sono:
* Dynamic Voltage-Frequency Scaling: si abbassa e si alza la tensione di alimentazione o la frequenza del clock a seconda di una politica.
* Low power state: alcuni dispositivi (es. DRAM, disks) si pongono in stand-by quando non usati
* Power-gating: si spengono selettivamente alcuni cores o parti del circuito se non usati