Prestazioni
Abbiamo comunque deciso di misurare il tempo necessario per il processo per vedere se esiste un vantaggio nell'usare CUDA anche con un'implementazione cruda, o se è necessaria molta pratica per trarre veramente i benefici dall'uso della GPU. Abbiamo usato un notebook dotato di processore Core 2 Duo T5450 e una GeForce 8600M GT, con Windows Vista. Questo sistema è ben lungi dall'essere un supercomputer, ma i risultati ottenuti sono stati interessanti, anche perché il nostro test non è molto favorevole per la GPU. Nvidia ha certamente tutto l'interesse a mostrare un'enorme accelerazione usando un sistema equipaggiato con una GPU potentissima e un bandwidth mastodontico, ma la realtà dei sistemi in circolazione è ben diversa, come sappiamo.
Questi sono i risultati ottenuti processando un'immagine da 2048x2048:
- CPU 1 thread: 1419 ms
- CPU 2 threads: 749 ms
- CPU 4 threads: 593 ms
- GPU (8600M GT) blocks of 256 pixels: 109 ms
- GPU (8600M GT) blocks of 128 pixels: 94 ms
- GPU (8800 GTX) blocks of 128 pixels / 256 pixels: 31 ms
Abbiamo modificato l'implementazione CPU in maniera tale da renderla thread-ottimizzata. Come abbiamo detto, il codice è ideale per questa situazione: tutto quello che dovrete fare è dividere l'immagine iniziale in tante zone, come se fossero dei thread. Da notare che avremo un'accelerazione lineare passando da uno a due thread sulla nostra CPU dual-core, che mostra la natura altamente parallela del nostro programma di test. Abbastanza inaspettatamente, la versione four-thread si è dimostrata più veloce, dove noi non ci aspettavamo differenze, o meglio, ci aspettavamo una perdita di efficienza a causa del lavoro aggiuntivo necessario per creare i thread addizionali.
Cosa spiegano questi risultati? Difficile da dire, ma pensiamo che il thread scheduler di Windows abbia qualche responsabilità. In ogni caso i risultati rimangono riproducibili. Con una texture di dimensioni inferiori (512x512), il guadagno offerto dalla divisione dei thread è meno marcato (circa il 35%, anziché il 100%), e il comportamento con la versione four-threaded è più logico, poiché non mostra un guadagno prestazionale rispetto alla versione two-thread. La GPU è sempre più veloce, ma in maniera meno marcata.
La seconda osservazione è che anche una GPU "lenta" è circa sei volte più veloce di una CPU di buon livello. Considerando che si tratta di un primo programma, il risultato è molto incoraggiante. I risultati, inoltre, possono ancora migliorare, ricorrendo a blocchi più piccoli. La spiegazione è semplice - il nostro programma usa 14 registri per thread, con blocchi da 256 thread, questo significa 3584 registri per blocco; nel nostro caso abbiamo tre blocchi o 10572 registri. Ma un multiprocessore ha solo 8192 registri, quindi può tenere solo due blocchi attivi. Diversamente, con blocchi da 128 pixel, abbiamo bisogno di 1792 registri per blocco; 8192 diviso 1792, arrotondando all'intero più vicino, si ottengono quattro blocchi. In pratica, il numero di thread è lo stesso (512 per multiprocessore, dove teoricamente il massimo è 768), ma avendo più blocchi, la GPU può essere più flessibile con gli accessi in memoria, e, quando si esegue un'operazione a lunga latenza, può lanciare le istruzioni su un altro blocco, mentre aspetta che arrivi il risultato dell'istruzione precedente. Quattro blocchi riescono a mascherare la latenza, specialmente se il programma effettua diversi accessi alla memoria.