Blowfish mit CUDA Dominik Oepen
Inhalt ● Blowfish Grundlagen ● Implementierungsdetails ● Performance ● Fazit
Blowfish Basics
Was ist eine Blockchiffre ● Eingabestrom wird in gleich große Blöcke zerlegt ● Blöcke werden einzeln ver-/entschlüsselt
Funktionsweise Blowfish ● S-Boxen: 4 Arrays mit jeweils Bit integer Werten ● P-Array: 18 konstante 32 Bit Werte ● P und S Boxen werden mit verwendetem Schlüssel verknüpft ● Permutationen, etc. in Abhängigkeit von den Boxen
Modi für Blockchiffren ● ECB ● CBC ● OFB ●... ● Key diversity: Schlüssel wird für jeden Block mit Blockindex per XOR verknüpft => anderer Schlüssel für jeden Block, weiterhin parallelisierbar
Implementierte Modi ● ECB ● Key diversyity ● Key diversity hat extremen Speicherbedarf: 1024 mal so viel Speicher für S-Boxen wie für Input => geringer Grad an Multiprogramming => CUDA Implementierung bringt kaum Vorteile
Implementationsdetails
Programmablauf ● Lade Daten in GPU Speicher ● Initialisiere S-Boxen und P-Array im globalen Grafikkartenspeicher ● Jeder Thread verschlüsselt einen Block (64 Bit) ● Mehrere Durchläufe des Kernels mit fester Anzahl an Threads ● Kopiere verschlüsselte Daten zum Host
Speicheraufteilung - ECB ● S-Boxen verknüpft mit Schlüssel im globalen Speicher der Grafikkarte ● __device__ > Blowfish_Init ● Nicht parallelisiert (Race conditions möglich), aber nur geringer Aufwand ● Jeder Thread verschlüsselt einen Block (2 uint32_t Werte => 64 Bit)
Speicheraufteilung – ECB optimiert ● Eine Kopie der S-Boxen in shared Memory pro Block ● CUDA Block Größe: 256 Threads/Block => jeder Thread kopiert einen Wert der S-Boxen ● Zu verschlüsselnde Daten werden in den lokalen Speicher der Threads kopiert. ● Nach Verschlüsselung kopieren der Ergebnisse in globalen Grafikkartenspeicher, von dort zum Host
Quellcode ● Blowfish-cuda.cu enthält Host Programm mit kernel Aufrufen ● blowfish-kernel.cu enthält Kernel und device Funktionen
Performance Messungen
Verwendete Plattform ● Intel Core 2 Duo 8400 (3 GHz, 4 MB Cache) ● 4 GByte DDR2-800 RAM ● Ubuntu 8.04 (64 Bit), Kernel ● CUDA 2.0 Beta ● NVIDIA Treiber
Methode ● Gemessen wurde die Verschlüsselung von einer 50 einer 100, einer 200 und einer 300 MByte großen Datei ● Es wird nur die Rechenzeit (ohne I/O) gemessen, mittels gettimeofday => mikrosekunden Auflösung ● Erwartung: CUDA Implementierung lohnt sich erst bei großen Dateien
Vergleich der Varianten
● CUDA ohne shared memory => Speedup von ungefähr 2 gegenüber Pthreads ● CUDA mit shared memory => Speedup von ungefähr 3 gegenüber Pthreads ● Shared memory => Speedup von ca. 0,5
ECB mit shared memory
Vergleich mit OpenSSL ● CUDA nur minimal schneller als OpenSSL ● CUDA 4096 Threads vs. OpenSSL 0.9.8g: 2,9s vs 3,2s Gemessen mit time, inklusive I/O, stark schwankende Ergebnisse
Analyse mit CUDA Visual Profiler
● Shared memory ist static ● Großteil der Speicherzugriffe ist “uncoalesced” => Serialisierung der Threads => großes Optimierungspotential ● GPU ist komplett ausgelastet (ohne shared memory nur Auslastung von 0,33) ● Siehe Analyse.csv
Verschmolzene Speicherzugriffe ● The simultaneous global memory accesses by each thread of a half- warp (16 threads on G80) during the execution of a single read or write instruction will be coalesced into a single access if: – The size of the memory element accessed by each thread is either 4, 8, or 16 bytes – The elements form a contiguous block of memory – The Nth element is accessed by the Nth thread in the half-warp – The address of the first element is aligned to 16 times the element’s size
Globale Speicherzugriffe __shared__ uint32_t shared_S[4][256]; shared_S[0][threadIndex] = ctx_S[0][threadIndex]; shared_S[1][threadIndex] = ctx_S[1][threadIndex]; shared_S[2][threadIndex] = ctx_S[2][threadIndex]; shared_S[3][threadIndex] = ctx_S[3][threadIndex]; In __global__ void Blowfish_Encrypt256: In __device__ void do_decryption: Xl = data[index]; Xr = data[index + 1];... data[index] = Xl; data[index + 1] = Xr;
Optimierungsmöglichkeiten ● Daten in Texturspeicher laden ● Daten für jeden Block in shared Memory laden ● Vor dem Zurückschreiben der Daten __synchthreads() für verschmolzenen Speicherzugriff
Fazit ● Grundsätzlich ist es recht einfach ein CUDA Programm zu schreiben ● Debugging ist schwierig (keine Host Funktionen, Emulationsmodus liefert teilweise andere Ergebnisse als reale Ausführung) ● Optimierung ist schwierig, CudaVisualProfiler ist ein gutes Werkzeug um Bottlenecks zu finden ● Hauptschwierigkeit: Organisation des Speichers (in meinem Fall)
Verwendete Quellen ● CUDA Programming Guide: CUDA_Programming_Guide_1.1.pdf CUDA_Programming_Guide_1.1.pdf ● General Purpose Computation on the GPU: ● CUDA Tutorial: A_Tutorial_No_NDA_Apr08.pdf A_Tutorial_No_NDA_Apr08.pdf ● GPGPU Course: