GRAFIˇCKA KARTICA KAO MASIVNO

ˇ
SVEUCILIŠTE
U ZAGREBU
FAKULTET ORGANIZACIJE I INFORMATIKE
VARAŽDIN
Krunoslav Ðuras
ˇ
KARTICA KAO
GRAFICKA
ˇ
MASIVNO-PARALELNO RACUNALO
DIPLOMSKI RAD
Varaždin, 2013.
ˇ
SVEUCILIŠTE
U ZAGREBU
FAKULTET ORGANIZACIJE I INFORMATIKE
VARAŽDIN
Krunoslav Ðuras
Redoviti student
Broj indeksa: 40525/11-R
Smjer: Baze podataka i baze znanja
Diplomski studij
ˇ
GRAFICKA
KARTICA KAO
ˇ
MASIVNO-PARALELNO RACUNALO
DIPLOMSKI RAD
Mentor:
Doc. dr. sc. Ivan Hip
Varaždin, kolovoz 2013.
Sadržaj
1.
Uvod . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2.
GPGPU . . . . . . . . . . . . . . . .
2.1. Nvidia CUDA . . . . . . . . . . .
2.2. OpenACC . . . . . . . . . . . . .
2.3. OpenCL . . . . . . . . . . . . . .
2.4. OpenCL vs CUDA . . . . . . . .
2.4.1. Postoje´ce implementacije .
2.4.2. Portabilnost . . . . . . . .
2.4.3. Brzina . . . . . . . . . . .
2.4.4. Mogu´cnosti . . . . . . . .
2.4.5. Dostupne biblioteke . . .
2.5. CPU vs. GPU processing . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2
3
3
5
6
7
8
8
8
8
9
3.
OpenCL . . . . . . . . . . . . . . . . . . . . . . . . . .
3.1. Arhitektura OpenCL-a . . . . . . . . . . . . . . . . .
3.1.1. Model platforme . . . . . . . . . . . . . . . .
3.1.2. Model izvršavanja . . . . . . . . . . . . . . .
3.1.2.1. Kontekst . . . . . . . . . . . . . . .
3.1.2.2. Redovi naredbi . . . . . . . . . . . .
3.1.2.3. Kategorije kernela . . . . . . . . . .
3.1.3. Model memorije . . . . . . . . . . . . . . . .
3.1.4. Model programiranja . . . . . . . . . . . . . .
3.1.4.1. Podatkovni paralelizam . . . . . . .
3.1.4.2. Zada´cni paralelizam . . . . . . . . .
3.1.4.3. Sinkronizacija . . . . . . . . . . . .
3.2. OpenCL Framework . . . . . . . . . . . . . . . . . .
3.2.1. OpenCL Platform Layer . . . . . . . . . . . .
3.2.1.1. Dohva´canje informacija o platformi .
3.2.1.2. Dohva´canje informacija o uredajima
¯
3.2.1.3. Konteksti . . . . . . . . . . . . . . .
3.2.2. OpenCL Runtime . . . . . . . . . . . . . . . .
3.2.2.1. Redovi naredbi . . . . . . . . . . . .
3.2.2.2. Kerneli . . . . . . . . . . . . . . . .
3.2.2.3. Izvršavanje kernela . . . . . . . . .
3.3. C programski jezik OpenCL-a . . . . . . . . . . . . .
3.3.1. Ugradeni
¯ tipovi podataka . . . . . . . . . . . .
3.3.1.1. Skalarni tipovi podataka . . . . . . .
3.3.1.2. Vektorski tipovi podataka . . . . . .
3.3.1.3. Objekti . . . . . . . . . . . . . . . .
3.3.2. Ugradene
funkcije . . . . . . . . . . . . . . .
¯
3.3.2.1. Funkcije za rad s radnim stavkama .
3.3.2.2. Matematiˇcke funkcije . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
12
12
12
13
13
13
13
14
17
17
17
17
18
19
19
20
20
21
21
21
22
23
24
24
24
25
25
25
26
4.
Instalacija potrebnih programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.1. Arch linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.2. Ubuntu 13.04 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
I
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
4.3. Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.4. clinfo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.5. SWIG . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
5.
Primjeri programa koji koriste OpenCL . . . . . . . . . . . . . .
5.1. Hashkill . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.2. oclHashcat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.3. Usporedba brzine izvodenja
na centralnom i grafiˇckom procesoru .
¯
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
37
37
37
38
6.
Implementacije algoritama u OpenCL-u . . . . . . . . . . . . . . . . . . . .
6.1. Metaprogramiranje za grafiˇcke procesore . . . . . . . . . . . . . . . . . . .
6.2. PyOpenCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.2.1. Izgled jednostavnog programa u PyOpenCL-u . . . . . . . . . . . . .
6.3. Množenje matrica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.3.1. Programski kod . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.3.2. Objašnjenje programskog koda . . . . . . . . . . . . . . . . . . . . .
6.3.3. Rezultati testiranja . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.4. Množenje matrica uz korištenje lokalne memorije OpenCL uredaja
. . . . . .
¯
6.4.1. Programski kod . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.4.2. Objašnjenje programskog koda . . . . . . . . . . . . . . . . . . . . .
6.4.3. Rezultati testiranja . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.5. Mandelbrot fraktal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.5.1. Programski kod . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.5.2. Objašnjenje programskog koda . . . . . . . . . . . . . . . . . . . . .
6.5.3. Rezultati testiranja . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.6. Sortiranje . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.6.1. Selection sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.6.2. Selection sort — poboljšanje korištenjem lokalne memorije . . . . .
6.6.3. Selection sort — poboljšanje smanjenjem ukupnog broja radnih stavki
6.6.4. Selection sort — kombinacija 1. i 2. poboljšanja . . . . . . . . . . .
6.6.5. Usporedba implementiranih verzija selection sort algoritma . . . . .
6.6.6. Bitonic sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
41
41
42
43
46
47
50
51
54
54
55
56
58
58
61
63
64
65
69
71
73
75
76
7.
Zakljuˇcak . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
8.
Literatura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
II
1. Uvod
Moderne grafiˇcke kartice zapravo su masivno-paralelna raˇcunala na kojima je uz pomo´c odgovaraju´cih programskih suˇcelja kao što su CUDA ili OpenCL mogu´ce implementirati sve vrste
proraˇcuna (GPGPU - General-Purpose computing on Graphics Processing Units). Cilj ovog diplomskog rada je da se korištenjem programskog suˇcelja OpenCL implementira paralelno rješavanje nekoliko standardnih problema na GPU te ispita koliko je doista mogu´ce dobiti na brzini u
usporedbi s implementacijom na CPU.
Nakon ovog uvoda, u 2. poglavlju je dan op´ceniti opis i definicija GPGPU-a. Osim toga u 2.
poglavlju je opisana Nvidia CUDA tehnologija, OpenACC i OpenCL standardi te je prikazana
usporedba OpenCL-a i Nvidia CUDA-e prema raznim kriterijima. Nakon toga dana je usporedba
izmedu
¯ naˇcina na koje se provode izraˇcuni na grafiˇckom i centralnom procesoru te za koje vrste
problema i algoritama su prikladniji grafiˇcki procesori, a za koje vrste problema i algoritama su
prikladniji centralni procesori raˇcunala.
Tre´ce poglavlje daje opis arhitekture OpenCL-a, OpenCL Frameworka te varijante C programskog
jezika koji se koristi za pisanje OpenCL kernela.
U poglavlju 4 opisan je postupak instalacije drivera za grafiˇcku karticu, programa potrebnih za
korištenje OpenCL-a te PyOpenCL modula za Python. Opis je dan za Windows operacijski sustav
te za Ubuntu i Arch Linux distribucije linuxa. U tom poglavlju takoder
¯ je opisana naredba clinfo i
prikazani su podaci o hardveru korištenom kod testiranja algoritama implementiranih u OpenCL-u.
U poglavlju 5 opisana su dva programa koja se cˇ esto koriste u praksi u podruˇcju kriptografije, a
imaju podršku za OpenCL i Nvidia CUDA tehnologije. Osim toga u tom poglavlju dan je prikaz
usporedbe brzine izvršavanja tih aplikacija korištenjem centralnog procesora raˇcunala naspram
korištenja grafiˇckog procesora.
U poglavlju 6 opisan je PyOpenCL modul za Python te je prikazan primjer jednostavnog programa
u OpenCL-u. U tom poglavlju takoder
¯ su prikazane implementacije nekoliko razliˇcitih algoritama
u OpenCL-u te je prikazano koliko se dobiva na performansama korištenjem OpenCL-a naspram
korištenja standardnih algoritama koji se izvršavaju na CPU raˇcunala. Množenje matrica je prvi
algoritam koji je implementiran u OpenCL-u i testiran. Jednostavna implementacija množenja
matrica u OpenCL-u je poboljšana korištenjem lokalne memorije OpenCL uredaja
te su na taj
¯
naˇcin pove´cane njezine performanse. Implementirano je i raˇcunanje Mandelbrotovog fraktala uz
pomo´c OpenCL-a. Nakon toga prikazana je jednostavna implementacija selection sort algoritma u
OpenCL-u te mogu´ci naˇcini optimiranja. Konaˇcno, u OpenCL-u je implementiran i testiran bitonic
sort algoritam.
Na kraju se nalaze zakljuˇcak i popis korištene literature.
1
2. GPGPU
GPGPU (engl. General-purpose computing on graphics processing units) predstavlja primjenu
grafiˇckih procesora (koji se cˇ esto koriste samo u raˇcunalnoj grafici) za provodenje
izraˇcuna u apli¯
kacijama koje najˇceš´ce koriste samo centralni procesor raˇcunala. Grafiˇcki procesori se mogu koristiti za takve izraˇcune jer podržavaju dovoljan broj instrukcija i mogu´cnosti, pa se mogu koristiti
za sve vrste izraˇcuna. Osim toga, korištenje ve´ceg broja grafiˇckih kartica na jednom raˇcunalu ili
velikog broja grafiˇckih kartica raspodijeljenih na više raˇcunala omogu´cuje daljnju paralelizaciju
ve´c paralelnog naˇcina provodenja
izraˇcuna na grafiˇckim procesorima.
¯
Trenutno najˇceš´ce korišteni otvoreni jezik za GPGPU je OpenCL. Kod jezika koji nisu otvorene
prirode najˇceš´ce se koristi Nvidia CUDA.
GPGPU i ostale sliˇcne tehnologije koje se koriste radi postizanja boljih performansi kod izracˇ una poˇcele su se koristiti zbog pove´cane potrebe za performansama u znanosti i inženjerstvu. Te
tehnologije se najˇceš´ce koriste za paralelno provodenje
razliˇcitih izraˇcuna nad velikim skupovima
¯
podataka. Potreba za tim tehnologijama stvorila je jaku potrebu za arhitekturama za razvoj softvera
koje omogu´cuju razvoj softvera koji je sposoban iskoristiti grafiˇcke procesore i sliˇcne tehnologije
za masivno paralelno izvodenje
razliˇcitih algoritama. Obrada op´ce namjene na grafiˇckim proces¯
nim jedinicama (GPGPU) je priliˇcno novi trend u istraživanjima raˇcunalnih inženjera. Grafiˇcki
procesori su visoko optimizirani koprocesori za obradu raˇcunalne grafike. Obrada raˇcunalne grafike je podruˇcje u kojem dominiraju paralelne podatkovne operacije – toˇcnije, matriˇcne operacije
linearne algebre. (Kirk & mei W. Hwu, 2010)
Grafiˇcki procesor je specijalizirana vrsta procesora koja zadovoljava potrebe u zahtjevnim zadacima prikaza 3D grafike visoke rezolucije. (Kirk & mei W. Hwu, 2010) Osim toga grafiˇcki procesori su se razvili tako da omogu´cuju obradu velikih koliˇcina podataka. Ovaj dizajn je puno efikasniji nego dizajni klasiˇcnih CPU-ova kod zadataka paralelne obrade velikih koliˇcina podataka, kao
što su npr.
– DFT (engl. Discrete Fourier transform),
– obrada slike (engl. image processing),
– procesiranje audio signala,
– fizikalne simulacije,
– sortiranje velikih listi i
– statistiˇcki izraˇcuni.
2
2.1.
Nvidia CUDA
CUDA (engl. Compute Unified Device Architecture) je platforma za paralelno provodenje
izra¯
cˇ una koju je kreirala Nvidia i koju mogu koristiti grafiˇcke kartice koje proizvodi Nvidia. CUDA
ukljuˇcuje programski jezik temeljen na programskom jeziku C.
Za razliku od OpenCL-a, CUDA je vlasniˇcka (engl. proprietary) arhitektura i programski jezik koji
podržava samo hardver proizvodaˇ
¯ ca Nvidia. To je jedan od glavnih nedostataka ove tehnologije,
pogotovo u odnosu na otvorene standarde kao što je OpenCL.
CUDA je prvi puta uvedena 2006. godine, a od tada je korištena u mnogobrojnim aplikacijama i
cˇ esto je spominjana i obradivana
u znanstvenim cˇ lancima i knjigama.
¯
Postoji više naˇcina na koji programeri mogu iskoristiti mogu´cnosti koje pruža CUDA. Programeri
mogu
– iskoristiti ve´c postoje´cu biblioteku koja zamjenjuje ili nadograduje
biblioteke kao što su
¯
MKL BLAS, IPP, FFTW ili neku drugu od cˇ esto korištenih biblioteka,
– automatski paralelizirati petlje u C ili Fortran kodu koriste´ci OpenACC direktive ili
– razviti vlastite paralelne algoritme i biblioteke koriste´ci neki poznati programski jezik, npr.
C, C++, C#, Fortran, Java, Python, itd.
CUDA u odnosu na sliˇcne tehnologije ima raznih prednosti i nedostataka. Glavne prednosti i
nedostaci CUDA tehnologije u usporedbi s OpenCL-om po raznim kriterijima opisane su u šestom
poglavlju ovog diplomskog rada.
Zanimljivo je da tvrtka Nvidia koja je razvila CUDA tehnologiju dio svojih resursa ulaže u razvoj
otvorenog standarda OpenCL, iako je OpenCL jedan od glavnih konkurenata CUDA-i.
2.2.
OpenACC
OpenACC je standard koji se koristi kod paralelnog programiranje i koji je razvijen kako bi programerima koji programiraju u C, C++ ili Fortran programskom jeziku omogu´cio jednostavno
korištenje mogu´cnosti paralelizacije koje pružaju grafiˇcki procesori i sliˇcan hardver. Standard su
razvili Cray, CAPS, Nvidia i PGI.
OpenACC omogu´cuje programerima da s jednostavnim direktivama u kodu oznaˇce dijelove koda
koje c´ e program prevodilac (engl. compiler) automatski paralelizirati bez da programer mora uvoditi daljnje promjene u kodu. Program prevodilac provodi sve potrebne promjene tokom prevode¯
nja (engl. compiling) tako da dobivena izvršna verzija programa iskorištava dostupne mogu´cnosti
paralelizacije. Direktive koje se mogu koristiti omogu´cuju programerima da oznaˇce dijelove koda
3
koji c´ e se paralelizirati za izvodenje
na grafiˇckom procesoru ili sliˇcnom uredaju.
Osim toga di¯
¯
rektive se mogu koristiti za upravljanje prijenosom podataka izmedu
¯ hosta i akceleratora (npr.
grafiˇckog procesora) te omogu´cuju pokretanje i zaustavljanje akceleratora.
Neke od podržanih direktiva su
– #pragma acc parallel
– #pragma acc kernels
– #pragma acc data
– #pragma acc loop
– #pragma acc cache
– #pragma acc update
– #pragma acc declare
– #pragma acc wait
U nastavku je prikazan program za izraˇcunavanje broja π koriste´ci OpenACC.
#include <stdio.h>
#define N 1000000
int main(void) {
double pi = 0.0f; long i;
#pragma acc parallel loop reduction(+:pi)
for (i=0; i<N; i++) {
double t= (double)((i+0.5)/N);
pi +=4.0/(1.0+t*t);
}
printf("pi=%16.15f\n",pi/N);
return 0;
}
Isti program u Pythonu koriste´ci PyOpenCL modul za Python prikazan je u nastavku.
import pyopencl as cl
import numpy
platforms = cl.get_platforms()
4
device = platforms[0].get_devices(cl.device_type.GPU)[0]
ctx = cl.Context([device])
queue = cl.CommandQueue(ctx)
N = 10000000
mf = cl.mem_flags
b_dev = cl.Buffer(ctx, mf.WRITE_ONLY, size = N * 4)
prg = cl.Program(ctx, """
__kernel void Pi(__global float * b)
{
int i = get_global_id(0);
float x = 4.0 / (1 + ((i + 0.5) / %d) * ((i + 0.5) / %d));
b[i] = x;
}
""" % (N, N)).build()
prg.Pi(queue, (N,), b_dev)
b = numpy.empty((N,1)).astype(numpy.float32)
cl.enqueue_copy(queue, b, b_dev)
print numpy.sum(b) / N
Isti program u C-u implementiran za OpenCL imao bi puno više koda, pa se odmah vidi prednost korištenja Python programskog jezika i PyOpenCL modula za Python kod razvoja OpenCL
aplikacija. Ova i ostale prednosti PyOpenCL-a c´ e biti detaljnije objašnjene kasnije u ovom radu.
2.3.
OpenCL
OpenCL je otvoreni standard kojeg održava neprofitni konzorcij Khronos Group, a predstavlja novi
industrijski standard za podatkovno i zada´cno paralelno provodenje
izraˇcuna na mnogim razliˇcitim
¯
vrstama procesora (CPU, GPU, DSP, FPGA i sl.).
OpenCL definira skup osnovnih funkcionalnosti koje podržavaju svi uredaji.
Osim toga, OpenCL
¯
definira i neke dodatne funkcionalnosti koje se mogu koristiti samo na odredenim
uredajima.
Iako
¯
¯
OpenCL garantira portabilnost koda i mogu´cnost izvršavanja na razliˇcitim uredajima
(CPU, GPU i
¯
sl.), ta portabilnost ima svojih granica, a ona ovisi o hardverskoj arhitekturi uredaja.
Ipak, progra¯
mer ima mogu´cnost pisati program za jednu odredenu
arhitekturu, a da taj program može ispravno
¯
5
raditi i na ve´cini drugih arhitektura.
OpenCL je otvoreni standard kojeg može koristiti hardver velikog broja proizvodaˇ
¯ ca, ukljuˇcuju´ci
grafiˇcke katrice stolnih i prijenosnih raˇcunala od proizvodaˇ
¯ ca ATI i Nvidia. Ako na hostu nije
dostupan podržani grafiˇcki procesor, tada se izvršavanje može obaviti na centralnom procesoru.
OpenCL ukljuˇcuje jezik (temeljen na C99 jeziku) za pisanje kernela (funkcija koje se izvode na
OpenCL uredajima)
i API-je koji se koriste za kontrolu izvršavanja. OpenCL se može koristiti kako
¯
bi aplikacijama omogu´cio pristup grafiˇckom procesoru radi izvršavanja razliˇcitih vrsta izraˇcuna
koji ne moraju nužno imati veze s raˇcunalnom grafikom.
Prvotno je razvijen od Apple-a, ali ga sada održava konzorcij Khronos Group koji održava i neke
druge standarde vezane uz audio i video (npr. OpenGL standard). OpenCL je razvijen s namjerom
da olakša programerima posao kod pisanja aplikacija za heterogene sustave te osim toga rješava
problem sve ve´ce potrebe za platformama za programiranje aplikacija koje se izvršavaju na velikom broju procesorskih jezgri odjednom. (B. Gaster, Howes, Kaeli, Mistry, & Schaa, 2011)
2.4.
OpenCL vs CUDA
CUDA i OpenCL su frameworkovi za programiranje koji omogu´cuju korištenje grafiˇckih procesora za paralelnu obradu podataka i low-level pristup hardveru, ali samo OpenCL pruža otvoreni
framework. Zbog toga je OpenCL dobio podršku od gotovo svih proizvodaˇ
¯ ca centralnih i grafiˇckih
procesora ukljuˇcuju´ci AMD, Intel i Nvidia kao i proizvodaˇ
¯ ca procesora za mobilne platforme i
servere. Kao rezultat toga aplikacije pisane u OpenCL-u su prenosive na velik broj razliˇcitih vrsta
GPU-ova i CPU-ova.
Trenutno za CUDA-u postoji više biblioteka i high-level API-a, ali ve´c postoje biblioteke za
OpenCL i neke se još razvijaju tako da ovaj nedostatak OpenCL-a u budu´cnosti vjerojatno ne´ce
biti od tolike važnosti. Za oˇcekivati je da c´ e se s vremenom razviti i ve´ci broj high-level API-a za
OpenCL nego što ih ima za CUDA-u budu´ci da je OpenCL otvoreni standard i sve više se koristi.
Jedna važna prednost cˇ injenice da je OpenCL otvoreni standard leži u mogu´cnosti da bilo koji
proizvodaˇ
¯ c može implementirati podršku OpenCL-a za svoje proizvode. CUDA i OpenCL su
podržani na razliˇcitim operacijskim sustavima (Windows, Linux, MacOS).
Veliki broj programera najprije razvija kod na svojem osobnom raˇcunalu prije nego ga prebacuju na
skupinu raˇcunala (engl. cluster) ili radne stanice (engl. workstation), pa je mogu´cnost korištenja
hardvera proizvodaˇ
¯ ca razliˇcitih od Nvidia-e vrlo korisna. To je jedna od prednosti OpenCL-a u
odnosu na CUDA-u. Osim toga puno ljudi se ne želi odluˇciti za platformu koja je usko vezana uz
odredenog
proizvodaˇ
¯
¯ ca i njihove proizvode, pa takvi programeri izbjegavaju CUDA-u.
Medu
¯ neke od manje bitnih razlika izmedu
¯ OpenCL-a i CUDA-e spada cˇ injenica da za CUDA-
6
CUDA term
OpenCL term
host CPU
host
streaming multiprocessor (SM)
compute unit (CU)
scalar core
processing element (PE)
host thread
host program
thread
work-item
thread block
work-group
grid
NDRange
shared memory
local memory
constant memory space
constant memory
texture memory space
constant memory
Tablica 2.1: Usporedba oznaka u OpenCL-u i CUDA-i
u postoji jedan programski paket koji ukljuˇcuje sve potrebne alate za razvoj programa koriste´ci
CUDA tehnologiju. S druge strane, za razvoj programa koriste´ci OpenCL postoje razliˇciti SDKovi, integrirana razvojna okruženja, debuggeri i sl., a svaki od njih može biti od razliˇcitog proizvodaˇ
¯ ca. To je tako jer je OpenCL otvoreni standard koji ima specifikacije koje može zadovoljiti
bilo koji proizvodaˇ
¯ c ako to želi, te može izdati vlastitu implementaciju OpenCL-a, vlastite alate za
rad s OpenCL-om itd. Možemo zakljuˇciti da postoji sva programska podrška potrebna za razvoj
OpenCL programa (iako je softver možda potrebno nabaviti od više razliˇcitih proizvodaˇ
¯ ca), što
znaˇci da tvrdnje da ne postoji dovoljno alata za razvoj u OpenCL-u nisu istinite.
Prije poˇcetka programiranja za GPGPU potrebno je donijeti odluku da li koristiti OpenCL ili
CUDA-u. Kod donošenja te odluke u obzir se mogu uzeti
– postoje´ce implementacije,
– portabilnost,
– dostupne mogu´cnosti,
– brzina i
– dostupne biblioteke (engl. library).
2.4.1.
´ implementacije
Postojece
Trenutno postoji samo jedna implementacija CUDA-e, a to je implementacija od strane Nvidia-e.
Kod OpenCL-a postoji izbor izmedu
¯ više implementacija. Trenutno su to sljede´ce implementacije
– AMD,
– Nvidia,
7
– Apple (samo za MacOS X),
– Intel,
– IBM (za PowerPC) i
– Portable OpenCL - implementacija OpenCL-a otvorenog koda.
2.4.2.
Portabilnost
Iako je OpenCL otvoreni standard koji podržava velik broj razliˇcitih uredaja
¯ na kojima se mogu
izvršavati programi pisani u OpenCL-u, to ne znaˇci da c´ e OpenCL kod raditi optimalno na svim
ˇ niti
uredajima
bez nekih izmjena u kodu potrebnih za optimiziranje na odredenom
uredaju.
Cak
¯
¯
¯
ne postoji garancija da c´ e kod uop´ce raditi na nekom drugom uredaju
¯ ako se radi o kompleksnijem
kodu koji je pisan tako da koristi neka posebna svojstva odredenog
uredaja.
Bez obzira na to, ako
¯
¯
se programer drži OpenCL specifikacija i izbjegava dodatke vezane uz odredenog
proizvodaˇ
¯
¯ ca,
kod bi se morao mo´ci bez problema izvršiti na razliˇcitim uredajima,
iako možda performanse ne
¯
budu optimalne. Tu se odmah vidi prednost OpenCL-a, jer ne samo da se kod može izvršavati
na uredajima
razliˇcitih proizvodaˇ
¯
¯ ca, nego se kod može izvršavati i na razliˇcitim vrstama uredaja
¯
(GPU, CPU, DSP i sl.). S druge strane, CUDA GPGPU aplikacije se mogu izvršavati samo na
grafiˇckim procesorima proizvodaˇ
¯ ca Nvidia.
2.4.3.
Brzina
Ako se koriste na istom hardveru, CUDA i OpenCL bi trebali dati iste performanse. To varira
ovisno o driverima, ali na dulji rok performanse CUDA-e i OpenCL-a bi se trebale uravnotežiti.
2.4.4.
´
Mogucnosti
OpenCL ima ve´c ugradenu
potporu za generiranje koda za vrijeme izvodenja
(engl. run-time code
¯
¯
generation), dok za CUDA-u postoje posebni alati koji to omogu´cavaju.
2.4.5.
Dostupne biblioteke
Trenutno postoji više biblioteka (engl. library) za CUDA-u nego za OpenCL, pa je to trenutno
jedna od prednosti CUDA-e. Biblioteke za OpenCL se razvijaju i vjerojatno c´ e ubrzo postojati dovoljan broj takvih biblioteka pa stoga ova prednost CUDA-e ne´ce biti toliko znaˇcajna. Na primjer,
za OpenCL ve´c postoji besplatna biblioteka za matematiku poznata pod nazivom ViennaCL koja
sadrži razliˇcite funkcije za linearnu algebru te algoritme za iterativno rješavanje odredenih
vrsta
¯
problema.
8
2.5.
CPU vs. GPU processing
Pove´canje performansi izvodenja
algoritama na grafiˇckom procesoru u usporedbi s izvodenjem
¯
¯
na centralnom procesoru raˇcunala izmedu
¯ ostalog ovisi i o prikladnosti korištenja centralnog /
grafiˇckog procesora za rješavanje odredene
vrste problema (Kirk & mei W. Hwu, 2010). Centralni
¯
procesor raˇcunala može dati jako dobre performanse kod rješavanja odredenih
vrsta problema, tako
¯
da je teško dobiti znatno pove´canje performansi korištenjem grafiˇckog procesora za rješavanje tog
problema. Ve´cina aplikacija ima dijelova koji se mogu puno brže izvršiti na centralnom procesoru
raˇcunala. Zbog toga je kod izrade aplikacija bitno koristiti centralni procesor za izvršavanje takvih
dijelova programa, a grafiˇcki procesor je potrebno koristiti za rješavanje problema za cˇ ije rješavanje
je GPU pogodniji tako da se GPU i CPU medusobno
nadopunjuju i u globalu program ima najbolje
¯
mogu´ce performanse.
Razlog zbog kojeg postoji velika razlika u performansama centralnog i grafiˇckog procesora je cˇ injenica da su dizajnirani tako da zadovoljavaju odredenu
namjenu. Centralni procesor je dizajniran
¯
tako da daje najbolje performanse kod izvršavanja sekvencijalnog koda. Osim toga centralnim procesorima stoji na raspolaganju velika koliˇcina priˇcuvne (engl. cache) memorije cˇ ime se smanjuje
vrijeme potrebno za dohva´canje instrukcija velikih i kompleksnih aplikacija. (Kirk & mei W. Hwu,
2010).
Razvoj grafiˇckih procesora jako je podložan zahtjevima industrije igara (engl. gaming industry),
koja zahtijeva da grafiˇcki procesori budu dizajnirani tako da mogu izvršavati jako veliki broj izracˇ una nad podacima s pomiˇcnim zarezom kod iscrtavanja slike (engl. frame) u grafiˇcki naprednim
video igrama. Zbog toga proizvodaˇ
¯ ci grafiˇckih kartica dizajniraju grafiˇcke procesore tako da je optimizirana mogu´cnost izvodenja
jako velikog broja dretvi, a što rezultira cˇ injenicom da se grafiˇcki
¯
procesori osim za primjenu u raˇcunalnoj grafici mogu koristiti i za rješavanje ostalih problema cˇ ije
rješavanje se može paralelizirati.
Osim razlike u samoj namjeni i naˇcinu dizajna grafiˇckih i centralnih procesora, na razlike u performansama izmedu
¯ njih utjeˇce i njihova memorijska propusnost. Grafiˇcki procesori imaju znatno
ve´cu memorijsku propusnost. Tako su grafiˇcki procesori ve´c 2006. godine imali oko 10 puta ve´cu
propusnost od ve´cine centralnih procesora raspoloživih u to vrijeme. Razlog za takve razlike u
propusnostima su zahtjevi i memorijski modeli koje moraju podržavati grafiˇcki i centralni procesori. Centralni procesori radi kompatibilnosti s hardverom i programskom podrškom imaju jaˇce
ograniˇcene mogu´cnosti, pa je zbog toga i njihova memorijska propusnost puno manja od memorijske propusnosti grafiˇckih procesora koji ne moraju zadovoljiti toliko puno razliˇcitih zahtjeva za
kompatibilnost. (Kirk & mei W. Hwu, 2010)
Arhitekture centralnih procesora i grafiˇckih procesora su dizajnirane s razliˇcitim namjenama (Kirk
& mei W. Hwu, 2010):
9
– Centralni procesori su dizajnirani tako da su maksimizirane performanse izvodenja
pojedinih
¯
dretvi
– Grafiˇcki procesori su dizajnirani tako da je maksimizirana mogu´cnost izvršavanja jako velikog broja dretvi istovremeno po cijenu nižih performansi izvodenja
pojedine dretve.
¯
Centralni procesori zahtijevaju (B. R. Gaster & Howes, 2013):
– veliku koliˇcinu priˇcuvne memorije za optimiziranje dohva´canja instrukcija za izvodenje
i
¯
– kompleksnu logiku sklopova za podržavanje kompleksnih programa.
Grafiˇcki procesori zahtijevaju (B. R. Gaster & Howes, 2013):
– podršku vektora velikih veliˇcina, što je problem jer nije lako vektorizirati sav kod, i
– veliku koliˇcinu memorijskog prostora za pohranu stanja kako bi se podržale aktivne dretve
(engl. active threads).
Prema svemu opisanom vidljivo je da grafiˇcki i centralni procesori imaju razliˇcite hardverske implementacije te je potrebno algoritme dizajnirati tako da oni budu prikladni za izvodenje
na cen¯
tralnom, odnosno grafiˇckom procesoru.
Slika 2.1: Pove´canje performansi CPU-ova i GPU-ova tokom godina
Izvor: (Kirk & mei W. Hwu, 2010)
Na slici 2.1 prikazan je porast performansi u GFLOPS-ima (milijardama operacija na podacima
s pomiˇcnim zarezom u sekundi - engl. Giga FLoating-point Operations Per Second) CPU-ova i
10
GPU-ova od 2001. do 2009. godine. Vidljiv je trend brzog pove´canja performansi grafiˇckih procesora u posljednjih nekoliko godina i nešto sporijeg pove´canja performansi centralnih procesora
raˇcunala. Trenutno se performanse grafiˇckih procesora i dalje brzo pove´cavaju.
Trenutno najbrža lako dobavljiva grafiˇcka kartica od AMD-a je Radeon HD 7990 koji ima procesorsku snagu od 8.2 TFLOPs-a za aritmetiku brojeva s pomiˇcnim zarezom jednostruke preciznosti
i 2.04 TFLOPs-a za aritmetiku brojeva s pomiˇcnim zarezom dvostruke preciznosti. To je nekoliko
puta ve´ca brzina od brzine koje su pružali grafiˇcki procesori prije nekoliko godina.
Trenutno jedna od najbržih grafiˇckih kartica od proizvodaˇ
¯ ca Nvidia je GeForce GTX 780. Ova
kartica ima 2304 CUDA jezgre, 3GB video memorije i 7.1 milijardu tranzistora.
Grafiˇcka kartica autora ovog rada na kojoj su testirani algoritmi koje je autor implementirao u
OpenCL-u je Radeon HD 6850. Grafiˇcki procesor ove grafiˇcke kartice ima procesorsku snagu od
1.5 TFLOPs-a.
U sljede´coj tablici prikazana je usporedba osobina trenutno najbržih GPU-ova proizvodaˇ
¯ ca ATI i
Nvidia. Podaci su dobiveni sa službenih web stranica proizvodaˇ
¯ ca ATI i Nvidia.
GTX 780
GTX Titan
GTX 680
AMD Radeon 7970
Broj jezgri
2304
2688
1536
2048
Texture Units
192
240
128
128
ROPs
48
48
32
32
Broj tranzistora
7.1 milijarda 7.1 milijarda 3.5 milijarde
4.3 milijarde
Koliˇcina video memorije
3GB
6GB
2GB
3GB
Thermal Design Power (TDP)
250W
250W
195W
250W
Tablica 2.2: Usporedba osobina trenutno najbržih GPU-ova
ROPs (engl. Render Output Unit(s) ili Raster Operations Pipeline) služe za provodenje
izraˇcuna
¯
pozicija piksela koji se prikazuju na ekranu, kao i ostalih izraˇcuna vezanih uz prikaz slike (antialiasing, blending, Z-buffering i sl.). Korištenjem anti-aliasinga i sliˇcnih algoritama rezultiraju´ca
slika izgleda puno bolje.
TDP (engl. thermal design power) se odnosi na teoretski maksimalnu energiju koju sustav za
hladenje
odredenog
uredaja
¯
¯
¯ mora raspršiti.
11
3. OpenCL
3.1.
Arhitektura OpenCL-a
Da bi se opisala arhitektura OpenCL-a potrebno je opisati sljede´ce dijelove arhitekture OpenCL-a
(Munshi, 2012):
– Model platforme (engl. Platform Model)
– Model memorije (engl. Memory Model)
– Model izvršavanja (engl. Execution Model)
– Model programiranja (engl. Programming Model)
3.1.1.
Model platforme
Model platforme OpenCL-a je prikazan na slici 3.1. Model se sastoji od glavnog raˇcunala (engl.
host) koji je povezan s jednim ili više OpenCL uredaja
¯ (engl. compute device). OpenCL uredaj
¯
je podijeljen na jedan ili više raˇcunskih jedinica (engl. compute unit) koji se dalje dijele na jedan
ili više procesnih elemenata (engl. processing element). Izraˇcuni na uredaju
se izvršavaju na
¯
procesnim elementima.
Slika 3.1: OpenCL model platforme
Izvor: (Munshi, 2012)
12
3.1.2.
Model izvršavanja
Izvršavanje programa u OpenCL-u je u dva zasebna dijela:
– Kerneli - izvršavaju se na jednom ili više OpenCL uredaja
¯
– Program koji se izvršava na glavnom raˇcunalu. Program na glavnom raˇcunalu služi za odabir OpenCL uredaja
koji c´ e se koristiti, kreiranje konteksta, prijenos podataka s glavnog
¯
raˇcunala u memoriju OpenCL uredaja
¯ i obrnuto te dodavanje komandi u red komandi.
3.1.2.1.
Kontekst
Prvi korak kod inicijalizacije i korištenja OpenCL-a je kreiranje konteksta (engl. context). Kod
kreiranja konteksta potrebno je naznaˇciti za koji OpenCL uredaj,
¯ odnosno za koje OpenCL uredaje
¯
se kreira taj kontekst. Sve ostalo vezanu uz rad s OpenCL-om (prevodenje
kernela, rad s memori¯
jom, dodavanje naredbi u red komandi) se obavlja unutar tog konteksta. Jedan kontekst može biti
asociran s ve´cim brojem uredaja,
a unutar konteksta OpenCL garantira konzistentnost memorije
¯
izmedu
¯ razliˇcitih uredaja.
¯
3.1.2.2.
Redovi naredbi
Redovi naredbi (engl. command-queue) upravljaju redosljedom izvršavanja naredbi na OpenCL
uredaju.
Te komande se izvršavaju asinkrono na glavnom raˇcunalu i na OpenCL uredaju.
Komande
¯
¯
se izvršavaju u odnosu na druge komande na jedan od dva naˇcina:
– In-order Execution - naredbe se izvršavaju onim redosljedom u kojem se pojavljuju u redu
naredbi i završavaju po redu. To znaˇci da se naredba koja slijedi neku naredbu u redu komandi izvršava tek nakon što je završilo izvršavanje svih prethodnih naredbi.
– Out-of-order Execution - naredbe se pokre´cu onim redom kojim su zadane u redu komandi,
ali naredba koja slijedi neku naredbu u redu komandi ne cˇ eka da završi prethodna naredba.
3.1.2.3.
Kategorije kernela
Postoje dvije kategorije kernela
– OpenCL kerneli (engl. OpenCL kernels) i
– nativni kerneli (engl. native kernels).
13
OpenCL kerneli (jezgre) pisani su u OpenCL C programskom jeziku i prevode se pomo´cu programa prevodilaca za OpenCL. Sve implementacije OpenCL-a podržavaju OpenCL kernele. Razliˇcite implementacije OpenCL-a mogu pružati i neke druge naˇcine za kreiranje OpenCL kernela.
Nativnim kernelima se pristupa preko pokazivaˇca funkcije koja pokazuje na funkciju na glavnom
raˇcunalu. Nativni kerneli se na isti naˇcin stavljaju u red naredbi za izvršavanje na uredaju
kao i
¯
OpenCL kerneli te s njima dijele memorijske objekte. Mogu´cnost izvršavanja nativnih kernela je
dodatna mogu´cnost OpenCL-a te njihova semantika ovisi o pojedinoj implementaciji OpenCL-a.
OpenCL API pruža funkcije s kojima se može provjeriti koje mogu´cnosti pruža odredeni
uredaj
¯
¯
te se na taj naˇcin može provjeriti da li uredaj
¯ podržava izvršavanje nativnih kernela. U nativnim
kernelima mogu, na primjer, biti definirane funkcije iz neke biblioteke.
3.1.3.
Model memorije
Radne stavke (engl. work-item) koje izvršavaju kernel imaju pristup sljede´cim dijelovima memorije:
– Globalna memorija (engl. global memory) - Ovaj dio memorije dozvoljava pisanje i cˇ itanje
svim radnim stavkama iz svih radnih grupa (engl. work-group). Radne stavke mogu pisati
i cˇ itati bilo koji element globalne memorije. Pisanje i cˇ itanje u globalnu memoriju može
koristiti priruˇcnu memoriju (engl. cache) ovisno o mogu´cnostima uredaja
¯ na kojem se izvodi
OpenCL kernel.
– Memorija za konstante (engl. constant memory) - Ovaj dio globalne memorije ostaje nepromijenjem tokom cijelog vremena izvršavanja kernela. Glavno raˇcunalo alocira i inicijalizira
memorijske objekte pohranjene u memoriju za konstante.
– Lokalna memorija (engl. local memory) - Dio memorije koji je dostupan radnim stavkama
iz odredene
radne grupe. Svaka radna grupa ima alocirana svoj dio lokalne memorije i ima
¯
pristup samo svojoj lokalnoj memoriji.
– Privatna memorija (engl. private memory) - Dio memorije koji je zaseban za svaku radnu
stavku. Svaka radna stavka ima svoju privatnu memoriju i može cˇ itati i pisati samo u svoju
privatnu memoriju.
Kod optimizacije koda za OpenCL bitno je koristiti lokalnu memoriju umjesto globalne kada je to
mogu´ce. Bolje je koristiti lokalnu memoriju jer ona ima puno ve´cu propusnost od globalne memorije, pa u nekim sluˇcajevima njeno (ne)korištenje može jako utjecati na performanse programa u
OpenCL-u. Propusnost lokalne memorije je viša od 2 TB/s, a to je nekoliko puta ve´ca propusnost
od propusnosti globalne memorije.
14
OpenCL Memory Types CUDA Equivalent
global memory
global memory
constant memory
constant memory
local memory
shared memory
private memory
local memory
Tablica 3.1: Tipovi memorije u OpenCL-u i CUDA-i
Izvor: (Kirk & mei W. Hwu, 2010)
Slika 3.2: Konceptualni prikaz arhitekture OpenCL-a
Izvor: (Munshi, 2012)
OpenCL kerneli se izvršavaju istovremeno nad virtualnom mrežom koja je definirana kodom na
glavnom raˇcunalu. Ta mreža može imati više dimenzija, na sljede´coj slici prikazana je dvodimenzionalna mreža, ali mreže nad kojima se izvršavaju kerneli u OpenCL-u mogu biti jednodimenzionalne, dvodimenzionalne ili trodimenzionalne. Na primjer, jednodimenzionalna mreža može biti
polje brojeva koje treba sortirati, a dvodimenzionalna mreža može biti matrica.
15
Slika 3.3: Radne stavke i radne grupe
Izvor: (Munshi, 2012)
Radna stavka je element prethodno opisane virtualne mreže nad kojim se izvršava OpenCL kernel.
Za svaku radnu stavku pokre´ce se zasebna dretva koja izvršava OpenCL kernel. Radna stavka se
izvršava kao jedan od elemenata radne grupe, a one se razlikuje od drugih radnih stavki prema
svojem globalnom i lokalnom ID-u.
Radna stavka se može identificirati prema svojem jedinstvenom lokalnom ID-u ili prema svojem
globalnom ID-u i rednom broju radne grupe. Globalni i lokalni ID su uredene
n-torke, gdje je n
¯
broj dimenzija koje se koriste, a može biti jednak 1, 2 ili 3. Radne stavke u radnoj grupi dijele
lokalnu memoriju i barijere radne grupe. Kod izraˇcuna Mandelbrotovog fraktala implementiranog
i objašnjenog kasnije u ovom diplomskom radu jedna radna stavka predstavlja jedan piksel Mandelbrotovog fraktala koji raˇcuna implementirani OpenCL kernel, a kod primjera sortiranja jedna
radna stavka predstavlja jedan od N brojeva koje treba sortirati.
Postoje odredena
ograniˇcenja za veliˇcine radnih grupa. Preporuˇca se da broj radnih stavki radne
¯
grupe bude višekratnik broja 8, a najˇceš´ce se koriste veliˇcine izmedu
¯ 64 do 128 radnih stavki
u radnoj grupi. OpenCL uredaji
¯ imaju ograniˇcenje veliˇcine radne grupe (najˇceš´ce 256 radnih
stavki). Op´cenito, veliˇcina radne grupe može imati velik utjecaj na performanse, ali optimalna
veliˇcina radne grupe ovisi o samom problemu koji kernel rješava te o kodu kernela, a nešto manje
o arhitekturi GPU-a.
16
3.1.4.
Model programiranja
OpenCL-ov model izvršavanja podržava podatkovni paralelizam (engl. data parallelism) i zada´cni
paralelizam (engl. task parallelism) kao modele programiranja. Osim toga podržava i hibridne modele koji su kombinacija podatkovnog i zada´cnog paralelizma. Primarni model koji je namjenjen
izvodenju
u OpenCL-u je model podatkovnog paralelizma.
¯
3.1.4.1.
Podatkovni paralelizam
Podatkovni paralelizam je svojstven programskim petljama. Paraleliziranje petlji cˇ esto vodi do
sliˇcnih (ne nužno identiˇcnih) operacijskih sekvenci ili funkcija izvodenih
na elementima velikih
¯
ˇ
podatkovnih struktura. Cesto
je javlja u znanstvenim i inženjerskim problemima.
3.1.4.2.
´ paralelizam
Zadacni
Zada´cni paralelizam je svojstvo paralelnog programa koji potpuno razliˇcite izraˇcune može izvesti
na istom ili razliˇcitom skupu podataka. Ovo predstavlja suprotnost s podatkovnim paralelizmom,
gdje se isti izraˇcun provodi na istim ili razliˇcitim skupovima podataka. Zada´cni paralelizam uobicˇ ajeno ne raste razmjerno s veliˇcinom problema.
3.1.4.3.
Sinkronizacija
U OpenCL-u postoje dvije domene sinkronizacije
– sinkronizacija izmedu
¯ radnih stavki u jednoj radnoj grupi i
– sinkronizacija komandi u redu komandi koje se nalaze u jednom kontekstu.
Sinkronizacija izmedu
¯ radnih stavki u radnoj grupi se obavlja pomo´cu barijere radne grupe (engl.
work-group barrier). Sve radne stavke unutar radne grupe moraju pro´ci barijeru prije nego bilo
koja radna stavka može nastaviti izvršavanje koda koji se nalazi na poziciji nakon barijere. Potrebno je napomenuti da kroz barijeru moraju pro´ci sve radne stavke unutar radne grupe ili niti
jedna radna stavka unutar radne grupe, pa je potrebno paziti na tu cˇ injenicu ako se koristi if-thenelse. Ne postoji mehanizam za sinkronizaciju rada razliˇcitih radnih grupa.
Postoje dva naˇcina sinkronizacije komandi u redovima naredbi, a to su
– Barijera reda komandi (engl. command-queue barrier) - Ova barijera osigurava da su izvršene sve komande u redu komandi koje se nalaze prije barijere, te da su izvršene sve
promjene u memoriji koje su nastale kao razultat prethodnih komandi u redu, tako da se
17
komande koje se izvršavaju nakon barijere mogu pravilno izvršiti. Može se koristiti samo za
sinkroniziranje komandi u jednom redu komandi.
ˇ
– Cekanje
na odredeni
dogadaj
¯
¯ - sve OpenCL API funkcije koje u red komandi stavljaju naredbu vra´caju dogadaj
¯ koji identificira komandu i memorijske objekte koje komanda ažurira.
Komanda koja cˇ eka na taj dogadaj
¯ c´ e se tek izvršiti kada se ažuriraju svi memorijski objekti
i te izvršene promjene nad memorijskim objektima budu vidljive.
Svaki zasebni red komandi se može izvršavati in-order ili out-of-order. Za in-order redove komandi naredbe se izvršavaju po onom redu u kojem su zadane. Potrebno je eksplicitno sinkronizirati rad više redova komandi zato jer
– Ako postoji više uredaja
¯ na kojima se izvršava neki kod, svaki od tih uredaja
¯ ima zasebni
red komandi
– Mogu´ce je da postoji više redova komandi na jednom uredaju
¯
– Za sinkronizaciju više redova komandi potrebno je koristiti dogadaje
¯ (engl. events).
3.2.
OpenCL Framework
Framework OpenCL-a omogu´cuje aplikacijama da koriste glavno raˇcunalo (engl. host) i jedan
ili više OpenCL uredaja
¯ kao jedan jedinstveni paralelni raˇcunalni sustav. OpenCL Framework se
sastoji od sljede´cih komponenti
– OpenCL Platform layer - Omogu´cava glavnom raˇcunalu da otkrije dostupne OpenCL uredaje,
mogu´cnosti koje ti uredaji
¯
¯ podržavaju te osim toga omogu´cava programu koji se odvija
na glavnom raˇcunalu da kreira OpenCL kontekste.
– OpenCL Runtime - Omogu´cava programu koji se izvršava na glavnom raˇcunalu da radi promjene na OpenCL kontekstima nakon što su oni jednom kreirani.
– OpenCL Compiler - Kreira izvršne datoteke koje sadrže OpenCL kernel. C programski jezik
kojeg podržava program prevodilac podržava podskup mogu´cnosti ISO C99 programskog
jezika uz dodatke za paralelizam. Taj programski jezik je detaljnije opisan u sljede´cem
podpoglavlju.
18
Slika 3.4: Naˇcin rada OpenCL programa
Izvor: (Klckner, 2009)
3.2.1.
OpenCL Platform Layer
3.2.1.1.
´
Dohvacanje
informacija o platformi
Za dohva´canje informacija o platformi može se koristiti sljede´ca funkcija
cl_int
clGetPlatformInfo(cl_platform_info param_name,
size_t param_value_size,
void *param_value,
size_t *param_value_size_ret)
Na temelju rezultata ove funkcije možemo izmedu
¯ ostalog dobiti informacije o verziji OpenCL-a
koja je podržana korištenom implementacijom OpenCL-a ,a što je važna informacija jer novije
verzije OpenCL-a imaju više podržanih mogu´cnosti pa treba o tome voditi brigu kod pisanja aplikacija za OpenCL. Detaljnije informacije o podacima koje vra´ca ova funkcija mogu se prona´ci u
specifikacijama OpenCL-a.
19
3.2.1.2.
´
Dohvacanje
informacija o uredajima
¯
Informacije o dostupnim uredajima
se mogu dobiti pomo´cu sljede´ce funkcije
¯
cl_int
clGetDeviceIDs(cl_device_type device_type,
cl_uint num_entries,
cl_device_id *devices,
cl_uint *num_devices)
Sa argumentom device_type može se odabrati tip uredaja
¯ o kojima se žele dohvatiti podaci. Mogu´ci
tipovi uredaja
¯ su (Munshi, 2012):
– CL_DEVICE_TYPE_CPU
– CL_DEVICE_TYPE_GPU
– CL_DEVICE_TYPE_ACCELERATOR
– CL_DEVICE_TYPE_DEFAULT
– CL_DEVICE_TYPE_ALL
Sa sljede´com funkcijom mogu se dobiti detaljne informacije o odredenom
uredaju
¯
¯
cl_int
clGetDeviceInfo(cl_device_id device,
cl_device_info param_name,
size_t param_value_size,
void *param_value,
size_t *param_value_size_ret)
Na temelju rezultata ove funkcije možemo dobiti informacije o verziji drivera, podržanim mogu´cnostima, podržanoj verziji OpenCL-a, veliˇcini lokalne memorije, nazivu i proizvodaˇ
¯ cu uredaja,
¯
tipu uredaja
itd. Detaljnije informacije o podacima koje vra´ca ova funkcija mogu se prona´ci u
¯
specifikacijama OpenCL-a.
3.2.1.3.
Konteksti
Pomo´cu sljede´ce funkcije može se kreirati kontekst koji se dalje koristi za sve vezano uz rad s
OpenCL-om (prevodenje
kernela, odabir uredaja,
rad s memorijom, kreiranje reda naredbi itd.):
¯
¯
cl_context
clCreateContext(cl_context_properties properties,
cl_uint num_devices,
20
const cl_device_id *devices,
void (*pfn_notify)(const char *errinfo,
const void *private_info, size_t cb,
void *user_data),
void *user_data,
cl_int *errcode_ret)
3.2.2.
OpenCL Runtime
OpenCL Runtime omogu´cava da se s razliˇcitim pozivima funkcija upravlja OpenCL objektima kao
što su redovi naredbi, kerneli, memorijski objekti itd. Njime je omogu´ceno dodavanje komandi u
redove naredbi, kreiranje memorijskih objekata, rad s memorijskim objektima, dohva´canje memorijskih objekata, mapiranje memorijskih objekata i sl.
Jednostavno reˇceno OpenCL Runtime omogu´cava programu koji se izvršava na glavnom raˇcunalu da radi promjene na OpenCL kontekstima nakon što su oni jednom kreirani (sve operacije
u OpenCL-u osim dohva´canja popisa OpenCL uredaja
i sl. se moraju obaviti unutar OpenCL
¯
konteksta).
3.2.2.1.
Redovi naredbi
Objekti u OpenCL-u kao što su memorijski objekti i kerneli se kreiraju unutar odredenog
konteksta.
¯
Sve operacije koje se izvode nad tim objektima se pokre´cu korištenjem redova naredbi. Pomo´cu
redova naredbi može se pokrenuti niz operacija nad objektima koje c´ e se izvoditi po zadanom
redu. Korištenje više redova naredbi omogu´cava izvršavanje nezavisnih naredbi bez potrebe za
sinkronizacijom. To je mogu´ce ako objekti koji se mijenjaju izvršavanjem naredbi iz razliˇcitih
redova naredbi nisu zajedniˇcki za više redova naredbi. Ako se neki objekti dijele u više redova
naredbi potrebno je korištenje prikladnih metoda za sinkronizaciju.
3.2.2.2.
Kerneli
OpenCL kerneli (jezgre) pisani su u OpenCL C programskom jeziku i prevode se pomo´cu programa prevodilaca za OpenCL. Sve implementacije OpenCL-a podržavaju OpenCL kernele. Kerneli se kreiraju sa sljede´com funkcijom
cl_kernel
clCreateKernel(cl_program program,
const char *kernel_name,
cl_int *errcode_ret)
21
Naredba clCreateKernel vra´ca ispravan kernel objekt i varijablu errcode_retis postavljenu na vrijednost konstante CL_SUCCESS ako je kernel ispravno kreiran. U suprotnom vra´ca NULL vrijednost za kernel te varijablu errcode_retis postavi na vrijednost konstante koja predstavlja grešku
do koje je došlo kod kreiranja kernela. Razliˇcite implementacije OpenCL-a mogu pružati i neke
druge naˇcine za kreiranje OpenCL kernela.
3.2.2.3.
Izvršavanje kernela
Kerneli se mogu izvršiti sa sljede´com funkcijom
cl_int
clEnqueueNDRangeKernel(cl_command_queue command_queue,
cl_kernel kernel,
cl_uint work_dim,
const size_t *global_work_offset,
const size_t *global_work_size,
const size_t *local_work_size,
cl_uint num_events_in_wait_list,
const cl_event *event_wait_list,
cl_event *event)
Ova funkcija dodaje izvršavanje kernela u zadani red naredbi. Bitni argumenti ove funkcije su
argument kernel koji predstavlja ispravan kernel objekt, veliˇcina radne grupe, broj dimenzija, ukupan globalni broj radnih stavki i sl. Detalji o ovoj funkciji se mogu prona´ci u specifikacijama
OpenCL-a.
Bitno je za napomenuti da je autor za programiranje u OpenCL-u u svrhu izrade ovog diplomskog
rada koristio Python programski jezik te PyOpenCL modul za Python koji znatno olakšava programiranje u OpenCL-u jer je relativno jednostavan za korištenje i kod njegovog korištenja nije
potrebno detaljno poznavanje svih funkcija OpenCL-a. Dodatni razlozi za korištenje PyOpenCLa (i sliˇcnih modula za Python i druge programske jezike) opisani su u dijelu rada koji se bavi
metaprogramiranjem za grafiˇcke procesore.
22
3.3.
C programski jezik OpenCL-a
Programski jezik koji se koristi za pisanje OpenCL kernela je temeljen na ISO C99 programskom
jeziku. Varijanta tog programskog jezika koja se koristi ima neke dodatne funkcionalnosti, a neke
od funkcionalnosti su izbaˇcene. Od najvažnijih funkcionalnosti koje su izbaˇcene potrebno je nabrojiti:
– standardna C99 zaglavlja,
– pokazivaˇci na funkcije,
– rekurzije,
– polja varijabilnih duljina i
– bitovna polja.
Najvažnije funkcionalnosti koje su dodane su:
– radne grupe i radne stavke,
– vektorski tipovi podataka,
– sinkronizacija i
– oznake za tipove adresnog prostora (__global, __local, __private, __constant).
Osim navedenih dodatnih funkcionalnosti dostupan je i velik broj ugradenih
funkcija:
¯
– funkcije za rad s radnim stavkama,
– funkcije za rad s radnim grupama i
– matematiˇcke funkcije, itd.
Najvažnije i najˇceš´ce korištene funkcije te tipovi podataka koje podržava programski jezik za pisanje OpenCL kernela bit c´ e opisani u nastavku ovog podpoglavlja.
23
3.3.1.
Ugradeni
tipovi podataka
¯
3.3.1.1.
Skalarni tipovi podataka
U sljede´coj tablici prikazani su ugradeni
¯ skalarni tipovi podataka
Tip podataka
bool
char
uchar
short
ushort
int
uint
long
ulong
float
half
size_t
ptrdiff_t
intptr_t
uintptr_t
void
Opis
Tip podataka koji može imati vrijednost true ili false
8-bitni tip podataka s predznakom
8-bitni tip podataka bez predznaka
16-bitni cjelobrojni tip podataka s predznakom
16-bitni cjelobrojni tip podataka bez predznaka
32-bitni cjelobrojni tip podataka s predznakom
32-bitni cjelobrojni tip podataka bez predznaka
64-bitni cjelobrojni tip podataka s predznakom
64-bitni cjelobrojni tip podataka bez predznaka
32-bitni tip podataka s pomiˇcnim zarezom
16-bitni tip podataka s pomiˇcnim zarezom
Cjelobrojni tip podataka bez predznaka koji je vra´cen kao rezultat sizeof operatora
Cjelobrojni tip podataka koji je rezultat oduzimanja vrijednosti dvaju pokazivaˇca
Pokazivaˇc kojeg predstavlja cjelobrojni tip podataka s predznakom
Pokazivaˇc kojeg predstavlja cjelobrojni tip podataka bez predznaka
Standardni void tip podatka
Tablica 3.2: Podržani skalarni tipovi podataka
Postoji i podrška za 64-bitne podatke s pomiˇcnim zarezom, ali njega ne podržavaju svi grafiˇcki
procesori, pa zbog toga ti tipovi podataka nisu spomenuti u prethodnoj tablici.
3.3.1.2.
Vektorski tipovi podataka
Vektorski tip podataka definiran je svojim imenom (npr. char, uchar, short, itd.) i brojem koji
oznaˇcava veliˇcinu vektora. Podržane vrijednosti za veliˇcinu vektora su 2, 4, 8 i 16. Tako je, na
primjer, vektor int4 vektorski tip podataka koji se sastoji od 4 elemenata tipa int te u memoriji
zauzima 16 bajtova (4 * 32 bitova). Podržani tipovi vektora su sljede´ci
– charn
– ucharn
– shortn
– ushortn
– intn
24
– uintn
– longn
– ulongn
– floatn
pri cˇ emu zadnji znak n predstavlja broj koji može poprimiti vrijednost 2, 4, 8 ili 16.
3.3.1.3.
Objekti
U sljede´coj tablici prikazani su podržani tipovi objekata (Munshi, 2012):
Tip podataka
cl_platform_id
cl_device_id
cl_context
cl_command_queue
cl_mem
cl_program
cl_kernel
Opis
identifikator odredene
OpenCL platforme
¯
identifikator odredenog
OpenCL uredaja
¯
¯
handle za odredeni
¯ OpenCL kontekst
handle za red naredbi odredenog
OpenCL uredaja
¯
¯
handle za odredeni
¯ memorijski resurs
handle za odredeni
¯ programski resurs
handle za prevedeni kernel
Tablica 3.3: Podržani tipovi objekata u OpenCL-u
3.3.2.
Ugradene
funkcije
¯
3.3.2.1.
Funkcije za rad s radnim stavkama
Ovo su vrlo bitne funkcije te je njihovo poznavanje vrlo važno kako bi se uop´ce moglo programirati
kernele za OpenCL.
U OpenCL-u su definirane sljede´ce funkcije za rad s radnim stavkama (Munshi, Gaster, Mattson,
Fung, & Ginsburg, 2011):
– get_work_dim() - Vra´ca broj dimenzija koje se koriste. Ta vrijednost može biti 1, 2 ili 3.
– get_global_size(uint dimindx) - Vra´ca ukupan broj radnih stavki za dimenziju dimindx. Ova
vrijednost se najˇceš´ce koristi u petljama i predstavlja ukupan broj stavki koje treba obraditi.
– size_t get_global_id(uint dimindx) - Vra´ca jedinstveni globalni ID radne stavke za dimenziju
dimindx.
– size_t get_local_size(uint dimindx) - Vra´ca broj radnih stavki u radnoj grupi za dimenziju
dimindx.
25
– size_t get_local_id(uint dimindx) - Vra´ca lokalni ID radne stavke koji je jedinstven u radnoj
grupi za dimenziju dimindx.
– size_t get_num_groups(uint dimindx) - Vra´ca broj radnih grupa koje c´ e se izvršiti u kernelu
za dimenziju dimindx.
– size_t get_group_id(uint dimindx) - Vra´ca redni broj radne grupe koji može imati vrijednost
od 0 do get_num_groups(dimindx) – 1.
3.3.2.2.
ˇ
Matematicke
funkcije
Podržane matematiˇcke funkcije možemo podijeliti u dvije kategorije
– funkcije koje kao argumente primaju skalarne tipove podataka
– funkcije koje kao argumente primaju vektorske tipove podataka
Implementiran je veliki broj funkcija iz podruˇcja trigonometrije. Osim toga implementiran je veliki broj funkcija za rad s podacima s pomiˇcnim zarezom, funkcija za rad s cjelobrojnim podacima,
funkcija za rad s vektorima, funkcija za rad s potencijama, logaritmima itd. Detaljni popis postoje´cih funkcija može se prona´ci u specifikacijama OpenCL-a.
26
4. Instalacija potrebnih programa
U nastavku je prikazan postupak instalacije drivera i ostalih programa potrebnih za pokretanje
implementacija algoritama u OpenCL-u prikazanih u 6. poglavlju. Prikazani su koraci instalacije
na razliˇcitim operacijskim sustavima za ATI grafiˇcke kartice.
4.1.
Arch linux
Kako bi se mogli instalirati driveri za grafiˇcku karticu potrebno je u datoteku /etc/pacman.conf
dodati sljede´ci repozitorij prije svih ostalih repozitorija:
[catalyst]
Server = http://catalyst.wirephire.com/repo/catalyst/$arch
Mogu´ce je da repozitorij bude dostupan na nekoj drugoj adresi izlaskom novijih verzija Arch Linux
operacijskog sustava. Toˇcna adresa i ostale upute vezane uz instalaciju drivera za grafiˇcku karticu
mogu se prona´ci na Arch Wiki web stranicama.
Nakon toga potrebno je izvršiti sljede´ce naredbe kako bi se instalirali driveri
pacman -Syy
pacman -S catalyst catalyst-utils catalyst-dkms opencl-catalyst
PyOpenCL modul za Python se može instalirati pomo´cu sljede´ce naredbe
sudo pacman -S python2-pyopencl
ImagingTk modul za Python (koji se koristi kod prikaza Mandelbrotovog fraktala) može se instalirati pomo´cu sljede´ce naredbe
sudo pacman -S python2-imaging
Za instalaciju SWIG-a potrebno je pokrenuti sljede´cu naredbu
sudo pacman -S swig
4.2.
Ubuntu 13.04
Najprije je potrebno instalirati drivere za grafiˇcku karticu. Oni se mogu preuzeti s mrežnih stranica
AMD-a. Pri tome je potrebno obratiti pozornost na verziju Ubuntu operacijskog sustava, te da li
je instalirana 32-bitna ili 64-bitna verzija. Nakon preuzimanja drivera, oni se mogu instalirati
pokretanjem sljede´cih naredbi (naredbe se mogu malo razlikovati ovisno o verziji drivera):
27
unzip amd-driver-installer-catalyst-13-4-linux-x86.x86_64.zip
./amd-driver-installer-catalyst-13-4-x86.x86_64.run
Ako se radi nadogradnja drivera, potrebno je najprije deinstalirati stariju verziju drivera, te nakon
toga pokrenuti gore navedenu komandu.
Nakon instalacije drivera potrebno je instalirati AMD APP SDK kojeg treba preuzeti s mrežnih
stranica AMD-a preuzeti AMD APP SDK te pokrenuti sljede´ce naredbe
tar xzf AMD-APP-SDK-v2.8-lnx64.tgz
sudo ./Install-AMD-APP.sh
Nakon toga se može instalirati PyOpenCL modul za Python korištenjem sljede´ce naredbe
sudo apt-get install python-pyopencl
ImagingTk modul za Python (koji se koristi kod prikaza Mandelbrotovog fraktala) može se instalirati pomo´cu sljede´ce naredbe
sudo apt-get install python-imaging-tk
Za instalaciju SWIG-a potrebno je pokrenuti sljede´cu naredbu
sudo apt-get install swig python-dev
4.3.
Windows
Najprije je potrebno preuzeti drivere za grafiˇcku karticu s mrežnih stranica AMD-a te ih instalirati
i nakon toga na isti naˇcin potrebno je instalirati AMD APP SDK.
PyOpenCL modul za Python je najlakše instalirati na Windows operacijskom sustavu pokretanjem instalacije ve´c prethodno prevedene verzije PyOpenCL-a (engl. windows binary version),
koja se može prona´ci na raznim web stranicama na internetu. Kod instalacije je potrebno obratiti
pozornost na verziju Pythona koja se koristi i da li se radi o 32-bitnom ili 64-bitnom operacijskom sustavu. Jedna od mrežnih stranica gdje se može prona´ci velik broj windows binary datoteka za razne Python module dostupna je na sljede´coj adresi: http://www.lfd.uci.edu/
~gohlke/pythonlibs/.
28
4.4.
clinfo
Kako bi se provjerilo da li su uspješno instalirani driveri i ostali programi te kako bi se ujedno
dobio uvid u resurse koji nam stoje na raspolaganju može se pokrenuti naredba clinfo. Opis ove
naredbe i rezultati njenog pokretanja prikazani su u nastavku.
Pomo´cu naredbe clinfo mogu se dobiti razni podaci vezani uz OpenCL uredaje,
instaliranu verziju
¯
OpenCL-a, mogu´cnosti koje podržavaju dostupni OpenCL uredaji
¯ i sl. Rezultat pokretanja naredbe
clinfo za Phenom X4 955 BE 3.2 GHz i Radeon HD 6850 na kojima su testirane sve implementacije
algoritama u OpenCL-u koje su prikazane u ovom diplomskom radu prikazan je u nastavku.
Number o f p l a t f o r m s :
1
Platform Profile :
FULL_PROFILE
Platform Version :
OpenCL 1 . 2 AMD−APP ( 1 1 1 3 . 2 )
P l a t f o r m Name :
AMD A c c e l e r a t e d P a r a l l e l
Processing
P l a t f o r m Vendor :
Advanced Micro D e v i c e s , I n c .
Platform Extensions :
cl_khr_icd
cl_amd_event_callback cl_amd_offline_devices
P l a t f o r m Name :
AMD A c c e l e r a t e d P a r a l l e l
Processing
Number o f d e v i c e s :
2
D e v i c e Type :
CL_DEVICE_TYPE_GPU
D e v i c e ID :
4098
Board name :
AMD Radeon HD 6800 S e r i e s
Device Topology :
PCI [ B# 1 , D# 0 , F#0 ]
Max compute u n i t s :
12
Max work i t e m s d i m e n s i o n s :
3
Max work i t e m s [ 0 ] :
256
Max work i t e m s [ 1 ] :
256
Max work i t e m s [ 2 ] :
256
Max work g r o u p s i z e :
256
P r e f e r r e d v e c t o r width char :
16
P r e f e r r e d v e c t o r width s h o r t :
8
P r e f e r r e d v e c t o r width i n t :
4
P r e f e r r e d v e c t o r width long :
2
P r e f e r r e d v e c t o r width f l o a t :
4
P r e f e r r e d v e c t o r width double :
0
Native v e c t o r width char :
16
Native v e c t o r width s h o r t :
8
Native v e c t o r width i n t :
4
Native v e c t o r width long :
2
Native v e c t o r width f l o a t :
4
29
Native v e c t o r width double :
0
Max c l o c k f r e q u e n c y :
775Mhz
Address b i t s :
32
Max memory a l l o c a t i o n :
134217728
Image s u p p o r t :
Yes
Max number o f i m a g e s r e a d a r g u m e n t s :
128
Max number o f i m a g e s w r i t e a r g u m e n t s :
8
Max image 2D w i d t h :
16384
Max image 2D h e i g h t :
16384
Max image 3D w i d t h :
2048
Max image 3D h e i g h t :
2048
Max image 3D d e p t h :
2048
Max s a m p l e r s w i t h i n k e r n e l :
16
Max s i z e o f k e r n e l a r g u m e n t :
1024
Alignment ( b i t s ) of base a d d r e s s :
2048
Minimum a l i g n m e n t ( b y t e s ) f o r any d a t a t y p e :
128
Single precision floating point capability
Denorms :
No
Q u i e t NaNs :
Yes
Round t o n e a r e s t e v e n :
Yes
Round t o z e r o :
Yes
Round t o + ve and i n f i n i t y :
Yes
IEEE754 −2008 f u s e d m u l t i p l y −add :
Yes
Cache t y p e :
None
Cache l i n e s i z e :
0
Cache s i z e :
0
G l o b a l memory s i z e :
536870912
Constant buffer size :
65536
Max number o f c o n s t a n t a r g s :
8
L o c a l memory t y p e :
Scratchpad
L o c a l memory s i z e :
32768
K e r n e l P r e f e r r e d work g r o u p s i z e m u l t i p l e :
64
Error correction support :
0
U n i f i e d memory f o r H o s t and D e v i c e :
0
Profiling timer resolution :
1
Device e n d i a n e s s :
Little
Available :
Yes
Compiler a v a i l a b l e :
Yes
Execution c a p a b i l i t i e s :
E x e c u t e OpenCL k e r n e l s :
Yes
Execute n a t i v e f u n c t i o n :
No
Queue p r o p e r t i e s :
Out−of−O r d e r :
No
Profiling :
Yes
P l a t f o r m ID :
0 x00007f543cc534e0
Name :
Barts
30
Vendor :
Advanced Micro D e v i c e s , I n c .
D e v i c e OpenCL C v e r s i o n :
OpenCL C 1 . 2
Driver version :
1113.2
Profile :
FULL_PROFILE
Version :
OpenCL 1 . 2 AMD−APP ( 1 1 1 3 . 2 )
Extensions :
cl_khr_global_int32_base_atomics
cl_khr_global_int32_extended_atomics cl_khr_local_int32_base_atomics
cl_khr_local_int32_extended_atomics cl_khr_3d_image_writes
cl_khr_byte_addressable_store cl_khr_gl_sharing
c l _ e x t _ a t o m i c _ c o u n t e r s _ 3 2 c l _ a m d _ d e v i c e _ a t t r i b u t e _ q u e r y cl_amd_vec3
c l _ a m d _ p r i n t f cl_amd_media_ops cl_amd_popcnt
D e v i c e Type :
CL_DEVICE_TYPE_CPU
D e v i c e ID :
4098
Board name :
Max compute u n i t s :
4
Max work i t e m s d i m e n s i o n s :
3
Max work i t e m s [ 0 ] :
1024
Max work i t e m s [ 1 ] :
1024
Max work i t e m s [ 2 ] :
1024
Max work g r o u p s i z e :
1024
P r e f e r r e d v e c t o r width char :
16
P r e f e r r e d v e c t o r width s h o r t :
8
P r e f e r r e d v e c t o r width i n t :
4
P r e f e r r e d v e c t o r width long :
2
P r e f e r r e d v e c t o r width f l o a t :
4
P r e f e r r e d v e c t o r width double :
2
Native v e c t o r width char :
16
Native v e c t o r width s h o r t :
8
Native v e c t o r width i n t :
4
Native v e c t o r width long :
2
Native v e c t o r width f l o a t :
4
Native v e c t o r width double :
2
Max c l o c k f r e q u e n c y :
3200Mhz
Address b i t s :
64
Max memory a l l o c a t i o n :
2147483648
Image s u p p o r t :
Yes
Max number o f i m a g e s r e a d a r g u m e n t s :
128
Max number o f i m a g e s w r i t e a r g u m e n t s :
8
Max image 2D w i d t h :
8192
Max image 2D h e i g h t :
8192
Max image 3D w i d t h :
2048
Max image 3D h e i g h t :
2048
Max image 3D d e p t h :
2048
31
Max s a m p l e r s w i t h i n k e r n e l :
16
Max s i z e o f k e r n e l a r g u m e n t :
4096
Alignment ( b i t s ) of base a d d r e s s :
1024
Minimum a l i g n m e n t ( b y t e s ) f o r any d a t a t y p e :
128
Single precision floating point capability
Denorms :
Yes
Q u i e t NaNs :
Yes
Round t o n e a r e s t e v e n :
Yes
Round t o z e r o :
Yes
Round t o + ve and i n f i n i t y :
Yes
IEEE754 −2008 f u s e d m u l t i p l y −add :
Yes
Cache t y p e :
Read / W r i t e
Cache l i n e s i z e :
64
Cache s i z e :
65536
G l o b a l memory s i z e :
4149723136
Constant buffer size :
65536
Max number o f c o n s t a n t a r g s :
8
L o c a l memory t y p e :
Global
L o c a l memory s i z e :
32768
K e r n e l P r e f e r r e d work g r o u p s i z e m u l t i p l e :
1
Error correction support :
0
U n i f i e d memory f o r H o s t and D e v i c e :
1
Profiling timer resolution :
1
Device e n d i a n e s s :
Little
Available :
Yes
Compiler a v a i l a b l e :
Yes
Execution c a p a b i l i t i e s :
E x e c u t e OpenCL k e r n e l s :
Yes
Execute n a t i v e f u n c t i o n :
Yes
Queue p r o p e r t i e s :
Out−of−O r d e r :
No
Profiling :
Yes
P l a t f o r m ID :
0 x00007f543cc534e0
Name :
AMD Phenom ( tm ) I I X4 955
Processor
Vendor :
AuthenticAMD
D e v i c e OpenCL C v e r s i o n :
OpenCL C 1 . 2
Driver version :
1113.2 ( sse2 )
Profile :
FULL_PROFILE
Version :
OpenCL 1 . 2 AMD−APP ( 1 1 1 3 . 2 )
Extensions :
cl_khr_fp64 cl_amd_fp64
cl_khr_global_int32_base_atomics cl_khr_global_int32_extended_atomics
cl_khr_local_int32_base_atomics cl_khr_local_int32_extended_atomics
cl_khr_int64_base_atomics cl_khr_int64_extended_atomics
cl_khr_3d_image_writes cl_khr_byte_addressable_store cl_khr_gl_sharing
c l _ e x t _ d e v i c e _ f i s s i o n c l _ a m d _ d e v i c e _ a t t r i b u t e _ q u e r y cl_amd_vec3
32
c l _ a m d _ p r i n t f cl_amd_media_ops cl_amd_popcnt
Naredba clinfo vra´ca relevantne podatke za sve dostupne OpenCL uredaje.
Najˇceš´ce su to grafiˇcki
¯
i centralni procesor raˇcunala. Najvažniji podaci koje naredba vra´ca su:
– popis OpenCL platformi - Njih može biti više, ovisno o tome koje implementacije OpenCL-a
su instalirane (Intel, AMD, Nvidia, Apple i sl.).
– broj OpenCL uredaja
¯ - Najˇceš´ce se radi o dva OpenCL uredaja
¯ — centralni i grafiˇcki procesor raˇcunala, ali OpenCL podržava i druge vrste uredaja.
¯
– proizvodaˇ
¯ c OpenCL platforme (AMD, Intel, Nvidia, Apple i sl.), odnosno implementacije
OpenCL-a. Svaka implementacija OpenCL-a omogu´cava korištenje OpenCL-a na odrede¯
nim uredajima
(npr. AMD APP SDK omogu´cava korištenje OpenCL-a na centralnim i gra¯
fiˇckim procesorima tvrtke AMD).
– verzija OpenCL platforme - U vrijeme pisanja ovog diplomskog rada to je najˇceš´ce bila
verzija OpenCL 1.2, a za prethodno prikazani primjer to je konkretno verzija OpenCL 1.2
AMD APP (1113.2). Trenutno se razvijaju specifikacije za OpenCL 2.0.
– dostupni OpenCL uredaji
¯ - To su najˇceš´ce grafiˇcki i centralni procesor raˇcunala, ali ovisno o
implementaciji OpenCL-a mogu biti podržane i druge vrste uredaja.
¯
– proizvodaˇ
¯ c i naziv svakog OpenCL uredaja
¯
– veliˇcina lokalne memorije OpenCL uredaja
- Veliˇcina lokalne memorije bitna je kod pi¯
sanja OpenCL kernela jer treba obratiti pozornost na ograniˇcenu veliˇcinu lokalne memorije
OpenCL uredaja,
a lokalna memorija bi se trebala koristiti ukoliko je to mogu´ce jer je znatno
¯
brža od globalne memorije, pa se korištenjem lokalne memorije mogu pove´cati performanse
OpenCL kernela.
– veliˇcina globalne memorije OpenCL uredaja
¯ - Veliˇcina globalne memorije je bitna jer kod
pokretanja OpenCL kernela u obzir treba uzeti ograniˇcenu globalnu memoriju. Kod obrade
podataka cˇ ija veliˇcina prelazi veliˇcinu globalne memorije OpenCL uredaja
¯ problem treba na
neki naˇcin podijeliti na više manjih problema te rješenja tih manjih problema treba spojiti
u konaˇcno rješenje za dani problem. Na primjer, ako se želi sortirati polje veliˇcine 64M
koje se sastoji od podataka s pomiˇcnim zarezom jednostruke preciznosti, tada je potrebno da
globalna memorija OpenCL uredaja
¯ ima dostupno barem 64M ∗ 4B = 256MB slobodnog
prostora.
– broj adresnih bitova te maksimalna veliˇcina memorije koja se može alocirati
33
– maksimalna veliˇcina radne skupine - Ova vrijednost je najˇceš´ce jednaka 256 za GPU, a 1024
za CPU, ali mogu´ce su i druge vrijednosti. Maksimalna veliˇcina radne skupine bitna je kod
pisanja kernela te kod provodenja
razliˇcitih optimizacija na kernelu.
¯
– tip i veliˇcina priruˇcne memorije (ukoliko ona postoji) - Za CPU se ispisuju podaci o L1
priruˇcnoj memoriji.
– maksimalni radni takt procesora - Kod centralnih procesora ovaj takt je znatno ve´ci, dok je
kod grafiˇckih procesora nešto niži.
– preferirane duljine vektorskih tipova podataka - Arhitektura GPU-ova je takva da je najisplativije dohva´cati po 128 bitova podataka iz memorije odjednom, pa se prema tome ravnaju
i preferirane duljine vektorskih tipova podataka. Ako je za neki tip podataka preferirana duljina vektora na nekom uredaju
¯ jednaka 0, tada taj tip podataka nije podržan na tom OpenCL
uredaju.
To je sluˇcaj kod Radeon HD 6850 grafiˇcke kartice koja je korištena kod testiranja
¯
implementacija prikazanih u 6. poglavlju te ne podržava podatke s pomiˇcnim zarezom dvostruke preciznosti, što se vidi i prema prethodno prikazanom rezultatu pokretanja naredbe
clinfo. Velik broj grafiˇckih procesora ne podržava podatke s pomiˇcnim zarezom dvostruke
preciznosti, a podršku imaju ve´cinom samo high-end grafiˇcki procesori.
Opisani podaci mogu se dobiti i korištenjem PyOpenCL modula za Python pomo´cu sljede´ce Python skripte.
1
# -*- coding: utf-8 -*-
2
3
import pyopencl as cl
4
import numpy
5
6
platforms = cl.get_platforms()
7
devices = platforms[0].get_devices(cl.device_type.ALL)
8
9
for d in devices:
10
print "Verzija drivera:", d.driver_version
11
if d.type == cl.device_type.CPU == d.type:
12
13
14
print "Tip uredaja: CPU "
elif d.type == cl.device_type.GPU == d.type:
print "Tip uredaja: GPU "
15
print "Velicina globalne memorije:", d.global_mem_size
16
print "Velicina lokalne memorije:", d.local_mem_size
17
print "Broj racunskih jedinica:", d.max_compute_units
34
18
print "Maksimalna velicina radne grupe:", d.max_work_group_size
19
print "Maksimalna velicina memorij ekoja se moze alocirati:", \
d.max_mem_alloc_size
20
print "Maksimalni broj dimenzija radnih stavki:", \
21
d.max_work_item_dimensions
22
23
print "Verzija OpenCL-a:", d.opencl_c_version
24
print "Naziv OpenCL uredaja:", d.name
25
print "Proizvodac OpenCL uredaja:", d.vendor
26
print "Broj adresnih bitova OpenCL uredaja:", d.address_bits
27
print
Grafiˇcki procesori korišteni za testiranje algoritama prikazanih u 6. poglavlju razlikuju se po brzini
koju mogu posti´ci kod provodenja
izraˇcuna. Brzine prvenstveno ovise o broju stream procesora
¯
s kojima raspolažu grafiˇcki procesori, a grafiˇcki procesori korišteni za testiranje implementiranih
algoritma raspolažu sa sljede´cim brojem stream procesora:
– Radeon HD 6850 - 960 stream procesora,
– Mobility Radeon HD 5850 - 800 stream procesora,
– HD 6310 - 80 stream procesora.
Podaci o broju stream procesora preuzeti su sa službenih mrežnih stranica tvrtke AMD.
Raˇcunalo na kojem su testirani programi opisani u 5. poglavlju te implementacije algoritama u
6. poglavlju ima AMD Phenom X4 955 BE procesor takta 3.2GHz s 4 jezgre i Radeon HD 6850
grafiˇcku karticu.
Algoritmi opisani u 6. poglavlju takoder
¯ su testirani na dva prijenosna raˇcunala. Radi se o jednom
Acer prijenosnom raˇcunalu s i7 720QM centralnim procesorom takta 1.6 GHz s 4 jezgre i jednom
IBM Thinkpad prijenosnom raˇcunalu s E-350 APU centralnim procesorom takta 1.6 GHz s 2
jezgre.
4.5.
SWIG
SWIG (engl. Simplified Wrapper and Interface Generator) je korišten kako bi se u Python programskom jeziku mogli koristiti selection sort algoritam i množenje matrica implementirano u
programskom jeziku C. SWIG se osim u Pythonu može koristiti i u velikom broju drugih programskih jezika za pokretanje programa pisanih u C ili C++ programskom jeziku. SWIG se koristi
za parsiranje interface datoteka te se na taj naˇcin generira programski kod koji povezuje neki od
podržanih programskih jezika s programima pisanim u C ili C++ programskom jeziku.
35
Interface datoteka osim popisa funkcija takoder
¯ sadrži i funkcije koje pretvaraju strukture jednog
od podržanih programskih jezika u strukture programskog jezika C i obrnuto. Primjer jedne takve
funkcije prikazan je u nastavku, a prikazana funkcija služi za pretvaranje Python liste u C polje
koje sadrži podatke s pomiˇcnim zarezom.
1
2
%typemap(in) float * {
if (PyList_Check($input)) {
int size = PyList_Size($input);
3
5
$1 = (float *) malloc(4 * size);
int i = 0;
6
float pom;
7
for (i = 0; i < size; i++) {
4
PyObject *o = PyList_GetItem($input,i);
pom = (float) PyFloat_AsDouble(o);
8
9
$1[i] = pom;
10
}
11
}
12
13
}
36
5. Primjeri programa koji koriste OpenCL
5.1.
Hashkill
Hashkill je alat otvorenog koda za otkrivanje lozinki. Jedna od važnih karakteristika alata je da
kod izraˇcuna koristi grafiˇcki procesor (ima podršku za OpenCL i CUDA tehnologije), pa je iz tog
razloga alat vrlo brz. Neke od važnijih osobina ovog alata su
– Podrška za višejezgrene centralne procesore
– Podržano je više od 60 razliˇcitih vrsta lozinki, neke od njih su MD5, SHA1, WPA, RAR
arhive s lozinkama, dokumenti MS Office-a, particije kriptirane LUKS-om itd.
– Alat podržava spremanje i uˇcitavanje stanja (engl. session) što omogu´cava nesmetano nastavljanje rada od tamo gdje se stanje zadnji puta automatski spremilo, pa tako ako sluˇcajno
dode
¯ do rušenja programa to ne predstavlja nikakav problem.
– Alat podržava razbijanje više razliˇcitih hasheva istovremeno.
– Alat podržava korištenje ve´ceg broja grafiˇckih procesora istovremeno.
Trenutna verzija ovog alata je 0.3.1.
5.2.
oclHashcat
oclHashcat je najbrži alat za razbijanje md5crypt, phpass, mscash2 i WPA / WPA2 hasheva. Ovaj
alat kod izraˇcuna takoder
¯ može koristiti grafiˇcke procesore. Koriste se OpenCL i CUDA tehnologije. Neke važnije karakteristike ovog alata su:
– Besplatan je.
– Alat ima podršku za istovremeno korištenje do 128 grafiˇckih procesora.
– Alat radi na više platformi (podrška za OpenCL i CUDA tehnologije).
– oclHashcat ne troši puno memorijskih resursa.
– Alat podržava dictionary napade - kod ovih napada se koristi datoteka u kojoj se nalaze
najˇceš´ce korištene lozinke, rijeˇci odredenog
jezika i sl. te se provjerava da li je neka rijeˇc iz
¯
te datoteke korištena kao lozinka.
– Podržano je distribuirano razbijanje hasheva.
37
– Podržana je pohrana stanja.
– Podržan je velik broj razliˇcitih hasheva, od onih najpoznatijih pa sve do manje poznatih hash
funkcija.
5.3.
ˇ
Usporedba brzine izvodenja
na centralnom i grafickom
¯
procesoru
Prethodna dva navedena alata mogu se koristiti za razbijanje hasheva korištenjem grafiˇckog procesora i centralnog procesora raˇcunala. Korištenje grafiˇckog procesora daje znatno bolje performanse
od korištenja samog centralnog procesora raˇcunala. Pri tome je potrebno obratiti pozornost na sljede´ce dvije cˇ injenice kod korištenja dictionary napada
– Hashevi koji su dobiveni brzim algoritmima (MD4, MD5, NTLM i sl.) mogu se razbijati korištenjem grafiˇckog procesora uz pomo´c dictionary napada, ali to nije vrlo efikasno. Razlog
za to je cˇ injenica da c´ e prijenos rijeˇci iz rijeˇcnika u globalnu memoriju grafiˇckog procesora
trajati dovoljno dugo da je ipak isplativije i efikasnije koristiti samo CPU za razbijanje ove
vrste hasheva.
– Hashevi koji su dobiveni sporijim algoritmima s puno iteracija (md5crypt - 1000 iteracija,
phpass - do 8000 iteracija, i sl.) mogu se vrlo efikasno razbijati korištenjem grafiˇckog procesora jer su oni namjerno dizajnirani tako da se sporo izvršavaju (kako bi se otežalo njihovo
razbijanje), pa vrijeme potrebno za kopiranje rjeˇcnika u globalnu memoriju grafiˇckog procesora nema velikog utjecaja na ukupno utrošeno vrijeme za razbijanje hasheva.
Autor je testirao brzine alata hashcat i oclHashcat na svom osobnom raˇcunalu. Za testiranje su
korišteni programi oclHashcat-plus 0.14 i hashcat 0.45.
38
U sljede´coj tablici prikazana je usporedba brzina razbijanja hasheva korištenjem grafiˇckog procesora i centralnog procesora raˇcunala.
MD5
SHA1
NTLM
CPU
30.83M h/s 32.68M h/s 32.04M h/s
GPU
2036.8M h/s 924.5M h/s 2652.7M h/s
GPU / CPU
66.07
28.29
82.79
Tablica 5.1: Usporedba brzina razbijanja hasheva na centralnom i grafiˇckom procesoru
Slika 5.1: Usporedba brzina razbijanja hasheva na centralnom i grafiˇckom procesoru
Brzine raˇcunanja MD5, SHA1 i NTLM hasheva pomo´cu centralnog procesora iznose oko 30 milijuna hasheva u sekundi. Brzine raˇcunanja MD5, SHA1 i NTLM hasheva pomo´cu grafiˇckog
procesora variraju izmedu
¯ oko 900 milijuna hasheva u sekundi pa sve do oko 2.5 milijardi hasheva
u sekundi. Najmanje pove´canje brzine raˇcunanja izvodenjem
na grafiˇckom procesoru dobiva se za
¯
SHA1 algoritam, a pove´canje brzine u odnosu na brzinu izvodenja
na centralnom procesoru iznosi
¯
oko 30 puta. Najve´ce pove´canje brzine dobiva se za NTLM hash, a to pove´canje brzine iznosi oko
80 puta.
Zanimljivo je da su brzine raˇcunanja zadanih hasheva na centralnom procesoru otprilike iste, dok
kod raˇcunanja pomo´cu grafiˇckog procesora postoje znatne razlike. Vidljivo je da je ubrzanje znacˇ ajno, iako ono može varirati u ovisnosti o centralnom i grafiˇckom procesoru.
39
U sljede´coj tablici prikazana je usporedba brzina razbijanja nekih sporijih hasheva korištenjem
grafiˇckog procesora i centralnog procesora raˇcunala.
md5crypt
phpass
CPU
30.18k h/s 8.38k h/s
GPU
1380.5k h/s 894.9k h/s
GPU / CPU
45.74
106.79
Tablica 5.2: Usporedba brzina razbijanja sporijih hasheva na CPU i GPU
Slika 5.2: Usporedba brzina razbijanja sporijih hasheva na CPU i GPU
Brzina raˇcunanja md5crypt hasheva pomo´cu centralnog procesora iznosi oko 30 tisu´ca hasheva
u sekundi, a za phpass brzina iznosi oko 8 tisu´ca hasheva u sekundi. Brzina raˇcunanja md5crypt
hasheva pomo´cu grafiˇckog procesora autora iznosi oko 1.4 milijuna hasheva u sekundi, a za phpass
brzina iznosi oko 900 tisu´ca hasheva u sekundi. Ubrzanje koje se dobiva raˇcunanjem md5crypt
hasheva korištenjem grafiˇckog procesora umjesto korištenja centralnog procesora raˇcunala iznosi
oko 45 puta, a za phpass hash dobiva se ubrzanje od oko 106 puta.
Vidljivo je da je ubrzanje vrlo znaˇcajno, iako ono može varirati. Ove hash funkcije su zamišljene
tako da budu sporije od klasiˇcnih hash funkcija kako bi dulje trajalo odredivanje
lozinki na temelju
¯
poznatog hash-a. Lozinke se cˇ esto u baze podataka pohranjuju u obliku njihovog hash-a izraˇcunatog nekom hash funkcijom. Raˇcunala su sve brža i dodatno se pojavio GPGPU koji se može
koristiti za isprobavanje jako velikog broja potencijalnih lozinki u sekundi (npr. 2.6 milijarde u sekundi kao za primjer NTLM-a na prethodnoj stranici). Zato su za pohranu lozinki razvijeni sporiji
hashevi koji su cˇ ak i 1000 puta sporiji od klasiˇcnih hasheva. Klasiˇcni hashevi se i dalje koriste,
recimo za provjeru integriteta podataka i sliˇcno.
40
6. Implementacije algoritama u OpenCL-u
6.1.
ˇ
Metaprogramiranje za graficke
procesore
Postoje velike razlike izmedu
¯ skriptnih jezika i GPGPU-a. Programi u OpenCL-u i CUDA-i
– imaju visok stupanj paralelizacije,
– vrlo su ovisni o arhitekturi uredaja
¯ na kojem se izvršavaju,
– pisani su tako da se maksimizira broj izvršenih operacija u sekundi i propusnost memorije.
S druge strane, programi pisani u skriptnim jezicima se mogu izvršavati na razliˇcitim uredajima
i
¯
operacijskim sustavima i cˇ esto nisu optimizirani za brzinu.
Zbog toga je kombiniranje skriptnih jezika i programa pisanih za izvodenje
na grafiˇckim proceso¯
rima jako dobra ideja, jer se tako mogu iskoristiti glavne prednosti svake od tih tehnologija.
Najˇceš´ci naˇcini pisanja koda koji daje visoke performanse su
– pisanje koda u C, C++ ili Fortran programskom jeziku i
– korištenje dostupnih biblioteka.
Alternativni naˇcin na koji se mogu pisati programi koji imaju jako dobre performanse je korištenje
skriptnih jezika za manje zahtjevne dijelove programa, a za raˇcunski zahtjevne dijelove programa
može se koristiti GPGPU.
Kod metaprogramiranja za grafiˇcki procesor programski kod koji se izvršana na grafiˇckom procesoru (kernel) ne mora biti uvijek isti, nego može ovisiti o parametrima koji se proslijede skriptnom
jeziku. Ovisno o dobivenim parametrima skriptni jezik može generirati drukˇciji programski kod
koji c´ e se izvršiti na grafiˇckom procesoru ili sliˇcnom uredaju.
Jedan skriptni jezik koji se može
¯
koristiti u tu svrhu je Python — Python je dobar skriptni jezik i postoje moduli za Python koji
omogu´cavaju korištenje CUDA i OpenCL tehnologija.
Kao primjer metaprogramiranja za grafiˇcki procesor mogla bi se navesti Python skripta koja korisnika kod pokretanja pita da li želi sluˇcajno generirati polje brojeva s pomiˇcnim zarezom ili polje
cijelih brojeva, te nakon toga na temelju odabira korisnika generira polje i kernel kod za OpenCL
koji c´ e se koristiti za sortiranje tog polja brojeva. Na taj naˇcin se ne bi trebalo koristiti više skripti
za obavljanje vrlo sliˇcne aktivnosti, ve´c bi se jedna skripta mogla nadopuniti na nekim mjestima
tako da podržava sve mogu´ce tipove odredenog
problema.
¯
41
Slika 6.1: Korištenje Python programskog jezika za GPGPU
Izvor: (Klckner, 2009)
6.2.
PyOpenCL
PyOpenCL je modul za Python koji omogu´cava korištenje OpenCL-a u programskom jeziku Python. Neke od najvažnijih osobina ovog modula su
– PyOpenCL programeru daje na raspolaganje sve mogu´cnosti koje pruža OpenCL i omoguc´ ava korištenje svih funkcija koje su opisane u specifikacijama OpenCL-a
– PyOpenCL je vrlo brz, jer je dio modula koji je vezan direktno uz OpenCL pisan u C++
programskom jeziku
– Besplatan je za korištenje u komercijalne i nekomercijalne svrhe.
Jedna zanimljiva mogu´cnost korištenja PyOpenCL modula je korištenje OpenCL-a kao alternativni naˇcin pisanja Python koda koji daje dobre performanse. U nekim sluˇcajevima korištenje
PyOpenCL modula je jednostavnije od povezivanje Pythona s C ili C++ programskim jezikom
korištenjem SWIG wrappera, pa zbog toga korištenje PyOpenCL-a ili sliˇcnih modula može biti
alternativa korištenja SWIG-a.
Primjeri programa koji koriste OpenCL prikazani u nastavku ovog diplomskog rada pisani su u
Python programskom jeziku te koriste PyOpenCL modul. Za pisanje samih kernela koji se izvode
na grafiˇckom procesoru korišten je programski jezik C u varijanti prilagodenoj
za OpenCL kako je
¯
opisano u podpoglavlju 3.3.
42
Osnovne funkcije i mogu´cnosti PyOpenCL-a c´ e biti opisane pomo´cu programskog koda prikazanog u nastavku.
6.2.1.
Izgled jednostavnog programa u PyOpenCL-u
U nastavku je prikazan program koji sluˇcajno generira dva polja s podacima s pomiˇcnim zarezom
te koristi OpenCL kako bi zbrojio ta dva polja. Program je pisan u programskom jeziku Python te
koristi PyOpenCL modul koji omogu´cava korištenje OpenCL-a u Python-u.
1
import pyopencl as cl
2
import pyopencl.array as cl_array
3
import numpy
4
import time
5
6
count = 300
7
platforms = cl.get_platforms()
8
devices = platforms[0].get_devices(cl.device_type.GPU)
9
device = devices[0]
10
11
ctx = cl.Context([device])
12
queue = cl.CommandQueue(ctx)
13
14
15
a = numpy.random.rand(1024*1024*16).astype(numpy.float32)
b = numpy.random.rand(1024*1024*16).astype(numpy.float32)
16
17
t0 = time.time()
18
a_dev = cl_array.to_device(queue, a)
19
b_dev = cl_array.to_device(queue, b)
20
21
c_dev = cl_array.empty_like(a_dev)
22
23
prg = cl.Program(ctx, """
25
__kernel void Sum(__global const float * a,__global const float * b,
__global float * c)
26
{
24
27
int i = get_global_id(0);
28
c[i] = a[i] + b[i];
29
}
43
30
""").build()
31
t1 = time.time()
32
for i in range(count):
33
event = prg.Sum(queue, a.shape, (256,), a_dev.data, b_dev.data,
c_dev.data)
34
35
event.wait()
36
t2 = time.time()
37
c = c_dev.get()
38
t3 = time.time()
39
40
print t1 - t0 + (t2 - t1) / count + t3 - t2
41
print a
42
print b
43
print c
Program radi na sljede´ci naˇcin: Najprije se importiraju svi potrebni Python moduli kako bi se
kasnije mogle koristiti funkcije iz tih modula. Moduli koji se koriste su numpy, pyopencl i time.
Numpy modul se u ovoj Python skripti koristi za generiranje sluˇcajnih brojeva. PyOpenCL modul se koristi za rad s OpenCL-om, a modul time se koristi za utvrdivanja
vremena utrošenog na
¯
izvršavanje koda.
Nakon što se importiraju potrebni moduli, dohva´ca se popis dostupnih OpenCL uredaja
koji su
¯
grafiˇcki procesori (8. linija koda) te se odabire prvi takav dostupan uredaj
¯ (9. linija koda). Osim
grafiˇckih procesora mogu se koristiti i druge vrste uredaja,
kao što je na primjer centralni procesor
¯
raˇcunala. Naredbi get_devices() potrebno je proslijediti jedan od sljede´cih parametara, ovisno o
tipu OpenCL uredaja
¯ koji se želi koristiti:
– cl.device_type.ACCELERATOR,
– cl.device_type.ALL,
– cl.device_type.CPU,
– cl.device_type.GPU ili
– cl.device_type.DEFAULT.
Nakon toga se kreira OpenCL kontekst (11. linija koda) te se u sklopu konteksta kreira red naredbi
(12. linija koda). Pomo´cu numpy modula se generiraju dva polja s pseudosluˇcajnim brojevima
s pomiˇcnim zarezom u rasponu vrijednosti od 0 do 1 (14. i 15. linija koda), te se ta polja kopiraju u memoriju odabranoga OpenCL uredaja
¯ pomo´cu naredbe cl_array.to_device() (18. i 19.
44
linija koda). Naredbi cl_array.to_device se kao parametri prosljeduju
kreiran red naredbi te numpy
¯
polje koje se želi kopirati u globalnu memoriju OpenCL uredaja.
Osim toga pomo´cu naredbe
¯
cl_array.empty_like() u globalnoj memoriji OpenCL uredaja
¯ se kreira polje iste veliˇcine kao i prva
dva polja u koje c´ e se spremati zbrojevi vrijednosti elemenata prvog i drugog polja (21. linija
koda).
Osim pomo´cu funkcija cl_array.to_device() i cl_array.empty_like() podaci bi se u globalnu memoriju OpenCL uredaja
¯ mogli smjestiti pomo´cu sljede´cih naredbi:
mf = cl.mem_flags
a_dev = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR,
hostbuf = a)
b_dev = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR,
hostbuf = b)
c_dev = cl.Buffer(ctx, mf.WRITE_ONLY, size = b.nbytes)
U tom sluˇcaju bilo bi mogu´ce specificirati razne parametre za alocirani dio globalne memorije
OpenCL uredaja
¯ pomo´cu sljede´cih zastavica:
– mf.READ_ONLY,
– mf.READ_WRITE,
– mf.WRITE_ONLY,
– mf.ALLOC_HOST_PTR,
– mf.COPY_HOST_PTR i
– mf.USE_HOST_PTR.
Mogu´ce je odabrati jednu od prve tri navedene zastavice, a zadnje tri navedene zastavice mogu´ce
je kombinirati na pet naˇcina (Munshi, 2012):
– Bez zastavica,
– mf.COPY_HOST_PTR,
– mf.ALLOC_HOST_PTR,
– mf.ALLOC_HOST_PTR | mf.COPY_HOST_PTR,
– mf.USE_HOST_PTR.
45
Nakon toga se u C programskom jeziku definira OpenCL kernel (23. do 30. linija koda) te se on
izvrši na odabranom OpenCL uredaju
¯ onoliko puta koliko je zadano varijablom count (32. do 34.
linija koda) kako bi se moglo toˇcnije odrediti prosjeˇcno vrijeme potrebno da se izvrši kernel. Kod
jednog pokretanja kernela na OpenCL uredaju
¯ kernel se pokre´ce jednom za svaku radnu stavku. U
ovom primjeru radi se o ukupno 16M radnih stavki, što znaˇci da se pri svakom od count pokretanja
kernela kernel pokrene 16M puta.
Kod pokretanja ovog kernela potrebno je kao argumente proslijediti adrese polja a, b i c, red
naredbi u koji c´ e se dodati izvršavanje ovog kernela, veliˇcinu radne grupe i sl.
Dodavanje kernela u red naredbi se vidi u 33. i 34. liniji koda:
– Prvi argument je red naredbi u koji se dodaje akcija izvršavanja kernela.
– Drugi argument su dimenzije virtualne mreže nad kojom se izvršava kernel (u ovom sluˇcaju
mreža predstavlja jednodimenzionalno polje s brojevima).
– Tre´ci argument je veliˇcina radne grupe (256 je maksimalna veliˇcina ako se koristi jedna
dimenzija, a 16 × 16 bi bila maksimalna veliˇcina ako bi se koristile dvije dimenzije).
– Ostali argumenti su polja s brojevima i polje u koje c´ e se spremiti rezultat u skladu s deklariranim parametrima kernela u 24. i 25. liniji koda. Osim polja ovdje se mogu proslijediti i
ostali tipovi podataka koji su podržani u OpenCL-u.
Raˇcuna se prosjeˇcno vrijeme potrebno da se izvrši kernel nad svim radnim stavkama. Rezultat
izraˇcuna kernela se iz memorije OpenCL uredaja
¯ kopira u memoriju glavnog raˇcunala (37. linija
koda), te se ispisuje vrijeme utrošeno na izraˇcun i sva tri polja (40. do 43. linija koda).
Ovo je vrlo jednostavan program koji ne koristi naprednije mogu´cnosti OpenCL-a. Ne koristi
lokalnu memoriju kako bi se ubrzalo izvršavanje (ovdje to ni nema smisla jer se radi o vrlo jednostavnom algoritmu), ne koristi ugradene
matematiˇcke funkcije niti nikakve dodatne mogu´cnosti
¯
OpenCL-a. Jedina funkcija u kernelu koju ovdje treba spomenuti je funkcija get_global_id(), koja
u ovom sluˇcaju za svaki element polja za koji se pokre´ce dretva vra´ca jedinstveni ID radne stavke.
Kod ovog jednostavnog algoritma taj broj predstavlja indeks elementa u polju. Kod složenijih
programa koriste se i funkcije za vra´canje lokalnog ID-a, rednog broja radne grupe i sl.
6.3.
Množenje matrica
Množenje matrica jedan je od najˇceš´cih zadataka asociranih uz GPGPU jer velik broj matrica koje
se koriste u znanstvenim izraˇcunima velikih su dimenzija, a množenje matrica ukljuˇcuje provode¯
nje velikog broja raˇcunskih operacija bez složenih grananja u programskom kodu, pa je množenje
matrica idealno za implementaciju u OpenCL-u (Scarpino, 2011).
46
Algoritam množenja matrica implementiran u OpenCL-u prikazan u ovom podpoglavlju usporeden
¯ je po performansama s numpy.dot rutinom za množenje matrica te s jednostavnom rutinom za
množenje matrica implementiranom u programskom jeziku C. Razlog korištenja numpy.dot rutine
je cˇ injenica da je ta rutina znatno brža od jednostavne rutine za množenje matrica implementirane
u programskom jeziku C prikazane u ovom podpoglavlju. To je bitno jer je na taj naˇcin algoritam množenja matrica implementiran u OpenCL-u usporeden
¯ s jednostavnom rutinom s kakvom
se programeri cˇ esto susre´cu, ali je usporeden
¯ i s implementacijom množenja matrica iz specijalizirane biblioteke koje ima vrlo dobre performanse. Time se korištenje specijaliziranih biblioteka
funkcija za CPU prikazalo kao alternativa korištenju GPGPU-a. Nakon toga je u sljede´cem podpoglavlju prikazano poboljšanje implementacije množenja matrica u OpenCL-u kako bi se pokazalo
da se implementacije algoritama u OpenCL-u mogu poboljšati na razliˇcite naˇcine i time se mogu u
nekim sluˇcajevima dobiti implementacije za GPU koje su znatno brže od specijaliziranih biblioteka
funkcija za CPU.
6.3.1.
Programski kod
U nastavku je prikazan programski kod pisan u Python programskom jeziku koji koristi PyOpenCL
modul koji omogu´cava korištenje OpenCL-a u Pythonu. Kod služi za množenje dviju matrica.
1
import pyopencl as cl
2
from time import time
3
import numpy
4
5
KERNEL_CODE = """
6
__kernel void
7
8
matrixMul(__global float* C,
__global float* A,
__global float* B,
int wA, int wB)
9
10
11
{
12
int i = get_global_id(0);
13
int j = get_global_id(1);
14
15
float v = 0;
16
for (int k = 0; k < wA; ++k)
17
{
18
19
float vA = A[j * wA + k];
float vB = B[k * wB + i];
47
v += vA * vB;
20
}
21
22
C[j * wA + i] = v;
23
24
}
25
"""
26
27
BS = 16
28
count = 5 # broj ponavljanja izracuna za benchmark
29
30
platforms = cl.get_platforms()
31
devices = platforms[0].get_devices(cl.device_type.GPU)
32
device = devices[0]
33
34
ctx = cl.Context([device])
35
queue = cl.CommandQueue(ctx)
36
37
a_width = int(raw_input("Unesite sirinu matrice A: "))
38
a_height = int(raw_input("Unesite visinu matrice A: "))
39
b_width = int(raw_input("Unesite sirinu matrice B: "))
40
b_height = a_width
41
42
host_a = numpy.random.rand(a_height, a_width).astype(numpy.float32)
43
host_b = numpy.random.rand(b_height, b_width).astype(numpy.float32)
44
host_c = numpy.empty((a_height, b_width)).astype(numpy.float32)
45
46
prg = cl.Program(ctx, KERNEL_CODE,).build()
47
kernel = prg.matrixMul
48
49
# host -> device --------------------------------------------------
50
mf = cl.mem_flags
51
52
t1 = time()
53
54
55
56
57
device_a_buf = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR,
hostbuf=host_a)
device_b_buf = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR,
hostbuf=host_b)
48
58
device_c_buf = cl.Buffer(ctx, mf.WRITE_ONLY, size=host_c.nbytes)
59
60
push_time = time()-t1
61
62
# benchmark -------------------------------------------------------
63
t1 = time()
64
65
66
for i in range(count):
event = kernel(queue, host_c.shape[::-1],
67
(BS, BS), device_c_buf, device_a_buf,
68
device_b_buf, numpy.int32(a_width),
69
numpy.int32(b_width))
70
71
event.wait()
72
73
gpu_time = (time()-t1)/count
74
75
# device -> host --------------------------------------------------
76
t1 = time()
77
cl.enqueue_copy(queue, host_c, device_c_buf)
78
pull_time = time()-t1
79
80
# timing output ---------------------------------------------------
81
gpu_total_time = gpu_time+push_time+pull_time
82
83
print "GPU total [s]:", gpu_total_time
84
print "GPU push [s]:", push_time
85
print "GPU pull [s]:", pull_time
86
print "GPU compute (host-timed) [s]:", gpu_time
87
88
# cpu comparison --------------------------------------------------
89
t1 = time()
90
host_c_cpu = numpy.dot(host_a, host_b)
91
cpu_time = time()-t1
92
93
print
94
print "GPU==CPU:",numpy.allclose(host_c, host_c_cpu)
95
print
49
96
print "CPU time (s)", cpu_time
97
print
98
99
100
print "GPU speedup (with transfer): ", cpu_time/gpu_total_time
print "GPU speedup (without transfer): ", cpu_time/gpu_time
6.3.2.
Objašnjenje programskog koda
Kod kernela za množenje matrica definiran je u programskom kodu s poˇcetkom na 5. liniji i
završetkom na 25. liniji. Nakon toga definirane su varijable BS (block size) i count (27. i 28. linija
koda). Varijabla BS definira visinu i širinu dvodimenzionalne matrice koja predstavlja jednu radnu
grupu. Svaka radna grupa u ovom sluˇcaju ima 256 radnih stavki. Varijabla count definira koliko
puta se ponavlja izvršavanje kernela nad svim radnim stavkama radi toˇcnijeg izraˇcuna prosjeˇcnog
vremena utrošenog na izvodenje
kernela nad svim radnim stavkama.
¯
Nakon što su definirane potrebne varijable i kod kernela, kre´ce se s dohva´canjem popisa dostupnih
OpenCL uredaja,
odabirom OpenCL uredaja
¯
¯ koji c´ e se koristiti, kreiranjem konteksta te kreiranjem
reda naredbi u koji c´ e se dodavati akcije pokretanja kernela. Sve navedeno u ovom odlomku odnosi
se na 30. do 35. liniju koda.
Nakon toga korisnika se pita za unos dimenzija matrica A i B za koje c´ e se izraˇcunati umnožak (42.
do 44. linija koda). Matrice A i B moraju biti ulanˇcane kako bi se mogao odrediti njihov umnožak.
Bitno je za napomenuti da širina i visina matrica A i B moraju biti višekratnici vrijednosti varijable
BS, tj. u ovom sluˇcaju visina i širina moraju biti višekratnici broja 16. Nakon toga generiraju se
matrice A i B koje sadrže pseudosluˇcajne brojeve s pomiˇcnim zarezom (42. i 43. linija koda) te
prazna matrica C u koju c´ e se spremiti rezultat množenja matrica A i B (44. linija koda).
Generirane matrice se kopiraju u globalnu memoriju OpenCL uredaja
¯ (54. do 57. linija koda) te se
alocira dio globalne memorije OpenCL uredaja
¯ u koju c´ e se zapisati matrica C (58. linija koda).
Slijedi kreiranje kernela koje c´ e se izvršiti na temelju njegovog izvornog koda (46. i 47. linija
koda). Nakon što se u globalnu memoriju smjeste svi potrebni podaci slijedi pokretanje kernela za
izraˇcun umnoška matrica ve´ci broj puta kako bi se toˇcnije odredilo prosjeˇcno vrijeme potrebno za
izraˇcun umnoška zadanih matrica (65. do 71. linija koda).
Kernel kod pokretanja najprije dohva´ca globalne ID-eve te ih pohranjuje u varijable i i j (12. i
13. linija koda). Te dvije varijable predstavljaju indekse retka matrice A i stupca matrice B cˇ iji se
umnožak raˇcuna. Sam izraˇcun tog produkta nalazi se od 16. do 21. linije koda.
Nakon što je izraˇcunat umnožak on se zapisuje na poziciju i, j u izlaznu matricu C (23. linija
koda).
50
6.3.3.
Rezultati testiranja
Na sljede´cim slikama i u tablicama prikazano je vrijeme potrebno za izraˇcun produkta dviju matrica reda N pomo´cu centralnog procesora i grafiˇckog procesora raˇcunala. Prva slika i tablica daju
podatke za brzine izvodenja
za cjelobrojni tip podataka, a ostale slike i tablice daju podatke za tip
¯
podataka s pomiˇcnim zarezom.
N = 480
N = 720
N = 960
N = 1200
CPU (numpy.dot)
0.22 s
0.7999 s
9.3800 s
21.9100 s
GPU
CPU / GPU
0.0079 s
27.50
0.0240 s
33.33
0.0639 s
173.70
0.1179 s
223.57
Tablica 6.1: Usporedba brzine množenja matrica - cjelobrojni tip podataka - Phenom X4 955
BE 3.2 GHz i Radeon HD 6850
Slika 6.2: Usporedba brzine množenja matrica - cjelobrojni tip podataka - Phenom X4 955
BE 3.2 GHz i Radeon HD 6850
Iz tablice 6.1 i slike 6.2 je vidljivo da su razlike u brzini jako velike. Iz slika 6.2 i 6.3 je takoder
¯
vidljivo da razlike u performansama nisu jednake za podatke s pomiˇcnim zarezom i cjelobrojni tip
podataka. Razlog toga je cˇ injenica da funkcija za množenje matrica (numpy.dot) koja se izvršava
na procesoru radi puno brže s brojevima s pomiˇcnim zarezom, a dosta sporije za cjelobrojni tip
podataka, pa je zbog toga i velika razlika izmedu
¯ brzina množenja matrica cjelobrojnog tip podataka na grafiˇckom i centralnom procesoru raˇcunala. U oba sluˇcaja modul numpy koji se ovdje
koristi za množenje matrica ima samo množenje (i velik broj ostalih funkcija) implementirano u
programskom jeziku C, tako da za velike razlike u brzinama nije kriv Python.
51
N = 1200
N = 2400
N = 4800
CPU (numpy.dot)
GPU
CPU / GPU
0.21 s
0.1124 s
1.87
1.542 s
0.8199 s
1.88
11.9420 s
6.27 s
1.90
Tablica 6.2: Usporedba brzina množenja matrica - float32 - Phenom X4 955 BE 3.2 GHz i
Radeon HD 6850
CPU (numpy.dot)
GPU
N = 1200
0.8897 s
0.15589 s
N = 2400
7.552 s
1.1675 s
N = 4800
62.9125 s
9.1253 s
CPU / GPU
5.70
6.47
6.89
Tablica 6.3: Usporedba brzina množenja matrica - float32 - i7 720QM 1.6 GHz i Mobility
Radeon HD 5850
CPU (numpy.dot)
GPU
CPU / GPU
N = 1200
5.2559 s
1.135 s
4.63
N = 2400
41.8419 s
8.9357 s
4.68
Tablica 6.4: Usporedba brzina množenja matrica - float32 - E-350 APU 1.6 GHz s HD6310
Slika 6.3: Usporedba brzine množenja matrica - float32 - Phenom X4 955 BE 3.2 GHz i
Radeon HD 6850
52
Vidljivo je i da su razlike u performansama ovisne o brzini grafiˇckog i centralnog procesora raˇcunala. Tako je u tablicama 6.3 i 6.4 vidljivo da su razlike u performansama ve´ce kod nešto sporijeg
centralnog procesora (radi se o prijenosnim raˇcunalima).
Usporedbom rezultata za grafiˇcke procesore u tablicama 6.2 do 6.4, kao i tablicama 6.7 do 6.9 vidi
se da su performanse po prilici proporcionalne broju stream procesora.
Vidljivo je da razlike u performansama zaista postoje, cˇ ak i kod ove jednostavnije implementacije
množenja matrica u OpenCL-u koja se usporedivala
s vrlo optimiziranom numpy.dot rutinom za
¯
množenje matrica. Algoritam koji je autor implementirao u OpenCL-u bi se svakako još dao
optimizirati korištenjem lokalne memorije grafiˇckog procesora, tako da bi razlike u performansama
bile nešto ve´ce.
Ako implementirani algoritam u OpenCL-u usporedujemo
s jednostavnom C rutinom za množenje
¯
matrica, tada su razlike u performansama puno ve´ce (tablice 6.5 i 6.6).
CPU (C rutina)
GPU
CPU / GPU
N = 1200
4.3254 s
0.1073 s
40.31
N = 2400
36.3261 s
0.7965 s
45.61
Tablica 6.5: Usporedba brzina množenja matrica - float32 - Phenom X4 955 BE 3.2 GHz i
Radeon HD 6850
N = 1200
N = 2400
CPU (C rutina)
GPU
CPU / GPU
19.9522 s
1.1332 s
17.61
156.8220 s
8.9423 s
17.54
Tablica 6.6: Usporedba brzina množenja matrica - float32 - E-350 APU 1.6 GHz s HD6310
Kod usporedbe implementiranog OpenCL algoritma s C rutinom prikazanom u nastavku dobivaju
se znaˇcajna pove´canja brzina, skoro 50 puta (tablica 6.5), dok se kod usporedbe OpenCL algoritma
izvršenog na sporijem grafiˇckom procesoru s manjim brojem dostupnih stream procesora dobivaju
nešto manja, ali ipak znaˇcajna ubrzanja (tablica 6.6). Vrlo je bitna implementacija s kojom se usporeduje
¯ OpenCL algoritam, jer kao što se može zakljuˇciti na temelju rezultata testiranja numpy.dot
rutina je u nekim sluˇcajevima i više od 20 puta brža od obiˇcne C rutine za množenje matrica.
Potrebno je napomenuti da je C rutina prikazana u nestavku poretkom petlji (kij) optimirana za
korištenje priruˇcne memorije centralnog procesora, što donosi ubrzanje od oko 2 do 3 puta prema
obiˇcnoj (ijk) rutini.
2
float **matrix_multiply (int hA, int wB, int wA, float **A,
float **B)
3
{
1
4
int i, j, k;
53
5
float **C = (float**) malloc(sizeof(float*) * hA);
for (i = 0; i < hA; i++)
6
7
C[i] = (float*) malloc(sizeof(float) * wB);
8
9
for (k = 0; k < wA; k++)
10
for (i = 0; i < hA; i++)
11
for (j = 0; j < wB; j++)
12
C[i][j] += A[i][k] * B[k][j];
13
14
return C;
15
16
}
6.4.
6.4.1.
Množenje matrica uz korištenje lokalne memorije OpenCL
uredaja
¯
Programski kod
U nastavku je prikazan programski kod kernela za množenje matrica. Autor kernela je tvrtka
Nvidia. Kernel se može preuzeti na mrežnoj adresi https://developer.nvidia.com/
opencl, gdje se mogu prona´ci i mnogi drugi OpenCL kerneli. Prikazan je samo kernel bez koda
u Pythonu jer je ostatak koda jednak kao i Python kod pod 13.3.1.
1
2
3
#define BLOCK_SIZE 16
#define AS(j, i) As[i + j * BLOCK_SIZE]
#define BS(j, i) Bs[i + j * BLOCK_SIZE]
4
5
__kernel __attribute__((reqd_work_group_size(BLOCK_SIZE,BLOCK_SIZE,1)))
6
void
8
matrixMul( __global float* C, __global float* A, __global float* B,
int WA, int WB)
9
{
7
11
__local float As[BLOCK_SIZE*BLOCK_SIZE];
__local float Bs[BLOCK_SIZE*BLOCK_SIZE];
12
int HB = WA;
13
int WC = WB;
10
14
15
int bx = get_group_id(0);
54
int by = get_group_id(1);
16
17
18
int tx = get_local_id(0);
19
int ty = get_local_id(1);
20
22
int aBegin = WA * BLOCK_SIZE * by;
int aEnd = aBegin + WA - 1;
23
int aStep = BLOCK_SIZE;
21
int bBegin = BLOCK_SIZE * bx;
int bStep = BLOCK_SIZE * WB;
24
25
26
float Csub = 0.0f;
27
28
29
for (int a = aBegin, b = bBegin;
30
a <= aEnd;
31
a += aStep, b += bStep) {
32
AS(ty, tx) = A[a + WA * ty + tx];
BS(ty, tx) = B[b + WB * ty + tx];
33
34
35
barrier(CLK_LOCAL_MEM_FENCE);
36
37
for (int k = 0; k < BLOCK_SIZE; ++k)
38
Csub += AS(ty, k) * BS(k, tx);
39
40
barrier(CLK_LOCAL_MEM_FENCE);
41
}
42
43
C[get_global_id(1) * get_global_size(0)
+ get_global_id(0)] = Csub;
44
45
46
}
6.4.2.
Objašnjenje programskog koda
Python programski kod za ovu implementaciju množenja matrica u OpenCL-u je jednak kao i kod
prethodne implementacije množenja, pa c´ e autor ovdje samo opisati razlike koje se odnose na sam
kernel.
Kernel se razlikuje od prethodnog kernela po tome što koristi dvije pomo´cne matrice dimenzija
55
BS × BS. Te matrice nalaze se u lokalnoj memoriji svake radne grupe. One se definiraju u 10. i
11. liniji prikazanog koda, a s podacima se pune unutar jedne for petlje, što je vidljivo u 33. i 34.
liniji koda. Svaka radna stavka dijelove pomo´cne matrice u lokalnoj memoriji puni s odredenim
¯
podacima iz matrica A i B. Radne stavke unutar radne grupe dijele lokalnu memoriju pa svaka
radna stavka može u lokalnoj memoriji vidjeti sve podatke koje su ostale radne stavke pohranile u
pomo´cne matrice. Zato one ne moraju te podatke dohva´cati iz globalne memorije OpenCL uredaja
¯
što znatno pove´cava performanse ovog kernela u usporedbi s prethodnim.
Budu´ci da radne stavke koriste podatke iz lokalne memorije ukljuˇcuju´ci i podatke koje su u lokalnu memoriju pohranile druge radne stavke, potrebno je koristiti memorijske barijere (36. i 41.
linija koda). Memorijske barijere osiguravaju da su sve radne stavke pohranile potrebne podatke
u pomo´cne tablice u lokalnoj memoriji OpenCL uredaja,
a time se omogu´cuje ispravan rad ker¯
nela. Ako se ne bi koristile memorijske barijere, radne stavke ne bi cˇ ekale da ostale radne stavku
pohrane sve potrebne podatke u pomo´cne tablice, a to bi za posljedicu imalo netoˇcno izraˇcunati
umnožak matrica.
6.4.3.
Rezultati testiranja
N = 1200
N = 2400
N = 4800
CPU (numpy.dot)
GPU
0.23 s
0.04372 s
1.4970 s
0.3016 s
11.742 s
2.2406 s
CPU / GPU
5.26
4.96
5.24
Tablica 6.7: Usporedba brzina množenja matrica - float32 - Phenom X4 955 BE 3.2 GHz i
Radeon HD 6850
N = 1200
N = 2400
N = 4800
CPU (numpy.dot)
GPU
CPU / GPU
0.8582 s
0.0565 s
15.20
7.6354 s
0.4154 s
18.38
63.3533 s
3.1215 s
20.30
Tablica 6.8: Usporedba brzina množenja matrica - float32 - i7 720QM 1.6 GHz i Mobility
Radeon HD 5850
CPU (numpy.dot)
GPU
CPU / GPU
N = 1200
5.2390 s
0.6039 s
8.68
N = 2400
41.8379 s
4.6331 s
9.03
Tablica 6.9: Usporedba brzina množenja matrica - float32 - E-350 APU 1.6 GHz s HD6310
Prema prikazanim tablicama i slikama vidljivo je da ovaj kernel ima oko 3 puta bolje performanse
od prethodnog kernela koji ne koristi lokalnu memoriju OpenCL uredaja.
U nekim sluˇcajevima su
¯
brzine izvodenja
na grafiˇckom procesoru cˇ ak i 20 puta ve´ce od izvodenja
na centralnom procesoru
¯
¯
56
Slika 6.4: Usporedba brzine množenja matrica - float32 - Phenom X4 955 BE 3.2 GHz i
Radeon HD 6850
raˇcunala (tablica 6.8 ), a to ovisi o brzini centralnog i grafiˇckog procesora raˇcunala i funkcije s
kojom se usporeduje
OpenCL funkcija. U tablicama 6.7, 6.8 i 6.9 prikazani su rezultati dobiveni
¯
kod usporedivanja
implementiranog OpenCL algoritma i numpy.dot rutine, a u tablicama 6.10 i
¯
6.11 prikazani su rezultati dobiveni kod usporedivanja
implementiranog OpenCL algoritma i ve´c
¯
prethodno prikazane C rutine za množenje matrica. Na temelju tablica i slika prikazanih za ovaj
i prethodni kernel može se zakljuˇciti da kvaliteta i optimiziranost kernela ima veliki utjecaj na
brzinu njegovog izvršavanja.
Na temelju uoˇcenih razlika izmedu
¯ brzina jednostavnih C rutina i rutina iz optimiziranih biblioteka
te razlika u performansama izmedu
¯ optimiziranih i neoptimiziranih OpenCL kernela može se zakljuˇciti da je svakako dobra ideja isprobati postoje´ce biblioteke za CPU i vidjeti da li su njihove
performanse zadovoljavaju´ce, a ako nisu tada se može implementirati algoritam u OpenCL-u pri
cˇ emu se mogu koristiti metode optimizacije prikazane u ovom diplomskom radu.
CPU (C rutina)
GPU
CPU / GPU
N = 1200
4.3253 s
0.0455 s
95.12
N = 2400
36.3260 s
0.2968 s
122.40
Tablica 6.10: Usporedba brzina množenja matrica - float32 - Phenom X4 955 BE 3.2 GHz i
Radeon HD 6850
Na temelju podataka u tablici 6.12 može se zakljuˇciti da nema znatnih razlika u brzinama izvode¯
nja implementiranog OpenCL programa s obzirom na korišteni operacijski sustav jer odstupanja
trajanja izvodenja
po razliˇcitim operacijskim sustavima nikad nisu velika bez obzira na prosjeˇcno
¯
57
CPU (C rutina)
GPU
CPU / GPU
N = 1200
19.9522 s
0.5948 s
33.54
N = 2400
156.8220 s
4.6335 s
33.85
Tablica 6.11: Usporedba brzina množenja matrica - float32 - E-350 APU 1.6 GHz s HD6310
trajanje izvodenja
OpenCL programa.
¯
N = 1200
N = 2400
N = 4800
Windows 7
0.04372 s
0.3016 s
2.2406 s
Ubuntu 13.04 Arch Linux
0.05013 s
0.04392 s
0.29597 s
0.2978 s
2.1819 s
2.2013 s
Tablica 6.12: Usporedba brzina množenja matrica na razliˇcitim operacijskim sustavima
6.5.
Mandelbrot fraktal
Klasiˇcni algoritam za izraˇcun Mandelbrotovog skupa je vrlo prikladan za paralelizaciju, a time
i implementaciju u OpenCL-u ili nekoj sliˇcnoj tehnologiji. Razlog zbog cˇ ega je algoritam tako
prikladan za korištenje u sklopu GPGPU-a je cˇ injenica da algoritam ima vrlo mali broj ulaza te
da je izraˇcun za svaku toˇcku potpuno nezavisan o drugim toˇckama. Može se pretpostaviti da
c´ e implementacija u OpenCL-u biti puno brža od standardne implementacije koja se izvršava na
centralnom procesoru raˇcunala.
6.5.1.
Programski kod
2
# -*- coding: utf-8 -*import numpy as np
3
import time
4
import pyopencl as cl
5
import Tkinter as tk
6
import Image
7
import ImageTk
8
count = 10 # broj ponavljanja izracuna za benchmark
9
w = 1280
1
10
h = 720
11
broj_iteracija = 512
12
def izracunaj(q, broj_iteracija):
13
platforms = cl.get_platforms()
14
58
15
# može se zamijeniti CPU sa GPU
16
devices = platforms[0].get_devices(cl.device_type.GPU)
17
device = devices[0]
18
19
ctx = cl.Context([device])
20
queue = cl.CommandQueue(ctx)
21
22
output = np.empty(q.shape, dtype=np.uint16)
23
24
# host -> device --------------------------------------------
25
t1 = time.time()
26
mf = cl.mem_flags
27
q_opencl = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR,
hostbuf=q)
28
29
output_opencl = cl.Buffer(ctx, mf.WRITE_ONLY, output.nbytes)
30
31
32
33
prg = cl.Program(ctx, """
__kernel void mandelbrot(__global float2 *q,
__global ushort *output,
ushort const broj_iteracija)
34
35
{
36
int gid = get_global_id(0);
37
float x_pom;
38
float x = 0;
39
float y = 0;
40
int iteracija = 0;
41
output[gid] = 0;
42
43
44
while (iteracija < broj_iteracija && x*x + y*y <= 4.0f) {
x_pom = x*x - y*y + q[gid].x;
y = 2 * x*y + q[gid].y;
x = x_pom;
45
46
47
iteracija++;
48
49
}
50
51
52
if (iteracija >= broj_iteracija)
output[gid] = 0;
59
else
53
output[gid] = iteracija;
54
55
}
56
""").build()
57
58
t1 = time.time()
59
for i in range(count):
event = prg.mandelbrot(queue, output.shape, (256,), q_opencl,
60
output_opencl, np.uint16(broj_iteracija))
61
62
event.wait()
63
gpu_time = time.time() - t1
64
gpu_time /= count
65
66
# device -> host --------------------------------------------
67
t1 = time.time()
68
cl.enqueue_copy(queue, output, output_opencl).wait()
69
return output, gpu_time
70
71
x1, x2, y1, y2 = -2.13, 0.8, -1.3, 1.3
72
x_range = np.arange(x1, x2, (x2-x1)/w)
73
74
y_range = np.arange(y2, y1, (y1-y2)/h) * 1j
q = np.ravel(x_range + y_range[:, np.newaxis]).astype(np.complex64)
75
76
output, gpu_time = izracunaj(q, broj_iteracija)
77
print("Utroseno vrijeme", gpu_time)
78
t = tk.Tk()
79
t.title("Mandelbrot fraktal")
80
M = (output.reshape((h,w)) /
81
82
M -=
float(output.max()) * 255.).astype(np.uint8)
255
84
M *= -1
im = Image.fromarray(M)
85
image = ImageTk.PhotoImage(im)
86
label = tk.Label(t, image=image)
87
label.pack()
88
t.mainloop()
83
60
Slika 6.5: Mandelbrot fraktal generiran pomo´cu OpenCL-a
6.5.2.
Objašnjenje programskog koda
Program koristi Python module numpy, PyOpenCL, Image, Tkinter i ImageTk. Modul PyOpenCL
se koristi za rad sa samim OpenCL-om, dok se modul numpy koristi za rad s matricama, generiranje
matrica koje c´ e koristiti OpenCL i sl. Tkinter i ImageTk moduli se koriste kako bi se fraktal
koji je generiran pomo´cu OpenCL-a mogao prikazati na ekranu korisnika u posebnom prozoru
(Tkinter je modul koji se vrlo cˇ esto koristi za izradu grafiˇckih suˇcelja aplikacija pisanih u Pythonu).
Modul Image se koristi kako bi se matrica koja predstavlja fraktal pretvorila u Python objekt koji
predstavlja sliku.
Medu
¯ prvim linijama koda vidi se koje veliˇcine c´ e biti matrica koja c´ e predstavljati fraktal. Varijabla w predstavlja širinu, a varijabla h visinu te matrice (i na kraju slike koje se dobije kao jedan
od rezultata pokretanja Python skripte). O tim varijablama jako zavisi vrijeme koje c´ e biti potrebno za provodenje
izraˇcuna. Bitna je i varijabla broj_iteracija o kojoj ovisi toˇcnost izraˇcunatog
¯
fraktala, ali kao posljedica toga i vrijeme potrebno za izraˇcun. Kod izraˇcuna je korišten takozvani
escape time algoritam koji je vrlo popularan zbog svoje jednostavnosti. Dobro objašnjenje escape
time algoritma dostupno je na sljede´coj mrežnoj adresi: http://yozh.org/2010/12/01/
mset005/.
Najprije se definira broj ponavljanja izvršavanja kernela (kernel se izvršava više puta kako bi se
61
toˇcnije odredilo prosjeˇcno vrijeme potrebno za izraˇcun), dimenzije fraktala i broj iteracija (8. do
11. linija koda). Nakon toga definiraju se varijable x1, x2, y1 i y2 (71. linija koda) koje predstavljaju minimalnu x koordinatu, maksimalnu x koordinatu, minimalnu y koordinatu te maksimalnu
y koordinatu u cˇ ijem rasponu se raˇcuna Mandelbrotov fraktal.
Nastavlja se kreiranjem matrice q dimenzija h × w koja predstavlja toˇcke u koordinatnom sustavu
s vrijednostima x u rasponu od x1 do x2 i vrijednostima y u rasponu od y1 do y2. Umjesto da
se toˇcke u koordinatnom sustavu u matricu zapisuju kao uredeni
parovi, toˇcke se zapisuju kao
¯
kompleksni brojevi kako bi bilo lakše korištenje njihovih koordinata u OpenCL kernelu. Matrica
q se pretvara u jednodimenzionalnu matricu (tj. polje) sa w ∗ h elemenata. Sve što je opisano u
ovom odlomku odnosi se na 70. do 74. liniju koda.
Nakon što je kreirano polje q koje sadrži toˇcke za koje se raˇcuna pripadnost Mandelbrotovom skupu
pokre´ce se funkcija izracunaj (76. linija koda). U toj funkciji dohva´ca se popis OpenCL uredaja
¯ te
se nakon toga kreira OpenCL kontekst i red naredbi (13. do 20. linija koda). Slijedi kopiranje polja
q u globalnu memoriju OpenCL uredaja
¯ te kreiranje polja u globalnoj memoriji OpenCL uredaja
¯
u koje c´ e se spremiti rezultat izvršavanja kernela (26. do 29. linija koda). Nakon toga kernel
se izvršava nekoliko puta nad svim radnim stavkama te se raˇcuna prosjeˇcno vrijeme potrebno za
njegovo izvršavanje nad svim radnim stavkama (31. do 64. linija koda). Polje s rezultatima se
kopira u memoriju hosta (68. linija koda) te Python funkcija vra´ca to polje i prosjeˇcno vrijeme
potrebno za izvršavanje kernela nad svim radnim stavkama (69. linija koda).
Polje koje je dobiveno kao rezultat izraˇcuna sadrži w ∗ h elemenata koje imaju vrijednosti u mogu´cem rasponu od 0 do broj_iteracija - 1. To polje se pretvara u matricu dimenzija h × w (koja
c´ e predstavljati sliku koju c´ e se iscrtati). Vrijednosti u dobivenoj matrici se skaliraju i pretvaraju u
cijele brojeve tako da budu u rasponu od 0 do 255. Ovisno o tim vrijednostima iscrtat c´ e se slika,
0 znaˇci neka toˇcka nije element Mandelbrotovog skupa pa ta toˇcka bude bijela na rezultiraju´coj
slici. Pove´canjem vrijednosti do 255 toˇcka koja c´ e se iscrtati na slici je sve tamnija jer je sve ve´ca
vjerojatnost da je ta toˇcka u Mandelbrotovom skupu. Sve opisano u ovom odlomku te prikaz slike
odnosi se na 78. do 88. liniju koda.
Sam kernel je relativno jednostavan. Kao što je ve´c reˇceno, kernel koristi escape time algoritam
za raˇcunanje vrijednosti pripadnosti toˇcaka Mandelbrotovom skupu. Kernel se pokre´ce w ∗ h
puta, tj. za svaku toˇcku slike Mandelbrotovog fraktala pokrene se radna stavka koja izvrši kernel
(te se to više puta ponavlja kako bi se toˇcnije izraˇcunalo prosjeˇcno utrošeno vrijeme). Kernel
prilikom izvršavanja iz polja q uzima x i y koordinate toˇcke za koje treba provjeriti pripadnost
Mandelbrotovom skupu te vra´ca vrijednost u rasponu od od 0 do broj_iteracija - 1.
62
6.5.3.
Rezultati testiranja
OpenCL CPU OpenCL GPU CPU / GPU
256x256
0.01118 s
0.00108 s
10.36
640x480
0.051604 s
0.00407 s
12.68
1280x720
0.15016 s
0.01099 s
13.66
1920x1080
0.34508 s
0.02473 s
13.95
Tablica 6.13: Usporedba brzina raˇcunanja Mandelbrot fraktala - 512 iteracija
Slika 6.6: Usporedba brzina raˇcunanja Mandelbrot fraktala - 512 iteracija
Na temelju prikazane slike 6.6 i tablice 6.13 vidljivo je da raˇcunanje Mandelbrotovog fraktala
korištenjem centralnog procesora 6traje više od 10 puta dulje od obavljanja istih izraˇcuna na grafiˇckom procesoru, cˇ ak i kada se iskoriste sve jezgre procesora. Pove´canjem same veliˇcine matrice
koja predstavlja Mandelbrotov fraktal postepeno se pove´cava i razlika izmedu
¯ brzine provodenja
¯
izraˇcuna na grafiˇckom i centralnom procesoru raˇcunala.
Da se uzme dovoljno mala veliˇcina matrice mogu´ce je da bi se algoritam cˇ ak i izvršio brže na
centralnom procesoru jer se dio performansi kod izraˇcuna pomo´cu grafiˇckog procesora gubi na
prijenos samih podataka do grafiˇckog procesora i obrnuto. No, ako se žele provoditi jednostavniji
izraˇcuni na malom skupu onda niti nema smisla ni potrebe za korištenjem GPGPU-a jer je vrijeme
potrebno za provodenje
takvih izraˇcuna u ve´cini sluˇcajeva vrlo malo.
¯
Isti OpenCL program isproban je i na centralnom i grafiˇckom procesoru raˇcunala. Na taj naˇcin
su se kod izraˇcuna iskoristile sve dostupne jezgre centralnog procesora (jer su se kod izvodenje
¯
OpenCL koda na centralnom procesoru automatski iskoristile sve njegove jezgre). Najjednostavnija implementacija raˇcunanja Mandelbrotovog fraktala pisana u programskom jeziku C ili C++ bi
63
bez korištenja višedretvenog rada imala dosta niže performanse od OpenCL koda koji se izvršava
na centralnom procesoru raˇcunala, jer OpenCL kod iskorištava sve jezgre procesora. Pretpostavka
toga je da CPU ima više jezgri, što je istina za ve´cinu današnjih centralnih procesora. Ovo nas
dovodi do još jedne od važnijih prednosti korištenja OpenCL-a naspram uobiˇcajenog rada s više
dretvi uz sinkronizaciju sa semaforima i sliˇcnim mehanizmima operacijskog sustava, a ta prednost je cˇ injenica da (pogotovo ako se koristi unutar nekog skriptnog jezika) pisanje OpenCL koda
ponekad može biti jednostavnije od pisanja koda koji koristi više dretvi kod izraˇcuna.
Možemo zakljuˇciti da je korištenje OpenCL-a vrlo pogodno za raˇcunanje Mandelbrotovog (i sliˇcnih) fraktala. To znaˇci da je pretpostavka da c´ e korištenje OpenCL-a za raˇcunanje fraktala biti
mnogo brže od korištenja standardnih algoritama za CPU bila dobra.
6.6.
Sortiranje
Korištenje GPGPU-a za sortiranje daje odredeno
pove´canje performansi u odnosu na klasiˇcno
¯
sortiranje korištenjem centralnog procesora raˇcunala, ali pove´canja brzina nisu velika. Razlog za
to je cˇ injenica da grafiˇcki procesori nisu najpogodniji za sortiranje zbog potrebe za velikim brojem
pristupa globalnoj memoriji kod sortiranja. Ipak, pove´canje performansi se može dobiti zbog toga
jer grafiˇcki procesori imaju puno ve´cu procesorsku mo´c od klasiˇcnih centralnih procesora raˇcunala.
Pri tome je potrebno napomenuti da brzina sortiranja na grafiˇckom procesoru jako ovisi o samom
algoritmu koji se koristi i kvaliteti implementacije tog algoritma, što je istina i za sve ostale vrste
problema koji se mogu rješavati uz pomo´c GPGPU-a.
Mogu´ce je da sortiranje pomo´cu grafiˇckog procesora bude puno sporije od sortiranja korištenjem
centralnog procesora raˇcunala ako se koristi algoritam koji nije prikladan za paralelizaciju ili se
usporeduju
algoritmi za sortiranje razliˇcitih složenosti (npr. Quicksort i selection sort). Primjer
¯
jednog sporijeg algoritma za sortiranje je selection sort algoritam, koji daje jako loše performanse
kod implementacije u OpenCL-u i u bilo kojem drugom programskom jeziku jer je njegova složenost jednaka O(n2 ). Selection sort se ne koristi u praksi zbog svoje sporosti, ali autor je ovaj
algoritam odabrao i implementirao u OpenCL-u radi njegove jednostavnosti — algoritam je jednostavan za paralelizaciju i pogodan je za razne eksperimente s optimizacijama OpenCL kernela, što
c´ e se vidjeti u nastavku. Razne optimizacije isprobane na selection sort algoritmu u principu bi se
mogle primjeniti i na složenije algoritme, a na temelju selection sort algoritma one se mogu lakše
shvatiti, što je takoder
¯ jedan od razloga za odabir ovog algoritma.
S druge strane, autor je odabrao bitonic sort algoritam za implementaciju u OpenCL-u jer daje
dosta dobre performanse kod izvodenja
na GPU zbog toga što je dizajniran tako da se može iz¯
voditi paralelno, pa je vrlo prikladan za implementaciju u OpenCL-u te se može koristiti u praksi
zbog dobrih performansi. Algoritam takoder
¯ daje jako dobre performanse kod implementacije na
CPU jer njegova složenost iznosi O(n log n), zbog cˇ ega je algoritam puno brži od selection sort
64
algoritma što c´ e se vidjeti i po rezultatima testiranja u nastavku. Osim tih jednostavnih algoritama
sortiranja, postoje i mnogi drugi složeniji algoritmi cˇ ijom implementacijom u OpenCL-u bi se vjerojatno postigle dobre performanse. Primjer jednog takvog algoritma je Duane Merrill Radix sort
(Merrill & Grimshaw, 2011),
Potrebno je napomenuti da razlike u performansama izmedu
¯ sortiranja na CPU i GPU jako ovise
o kvaliteti implementacija algoritama koji se koriste, što se može vidjeti i po rezultatima testiranja
u nastavku na temelju velike razlike izmedu
¯ brzine jednostavne implementacije bitonic sort algoritma u programskom jeziku C i numpy.sort rutine iz numpy modula za Python (iako oba algoritma
za sortiranje imaju složenost O(n log n)) .
Zbog toga se treba koristiti najbolja implementacija prikladnih algoritama (npr. implementacija iz
neke biblioteke funkcija) ako se žele posti´ci najbolji rezultati. Dapaˇce, mogu´ce je da se korištenjem kvalitetnije implementacije algoritama za CPU postignu puno ve´ce brzine od nekih lošijih
implementacija za GPU, pa u nekim sluˇcajevima niti nema potrebe za korištenjem GPGPU-a. Ipak,
ako se žele maksimizirati performanse, korištenje dobre implementacije za GPU je opcija koja bi
se svakako trebala uzeti u obzir.
6.6.1.
Selection sort
U nastavku je prikazan programski kod selection sort (Knuth, 1998) algoritma implementiranog u
OpenCL-u.
2
# -*- coding: utf-8 -*import pyopencl as cl
3
import pyopencl.array as cl_array
4
import numpy
5
import numpy.linalg as la
6
import time
7
import diplomski
1
8
9
10
N = eval(raw_input("Unesite N: "))
a = numpy.random.rand(N).astype(numpy.float32)
11
12
platforms = cl.get_platforms()
13
devices = platforms[0].get_devices(cl.device_type.GPU)
14
device = devices[0]
15
16
ctx = cl.Context([device])
17
queue = cl.CommandQueue(ctx)
65
18
19
# host -> device ------------------------------------------------
20
t = time.time()
21
a_dev = cl_array.to_device(queue, a)
22
dest_dev = cl_array.empty_like(a_dev)
23
push = time.time() - t
24
# ---------------------------------------------------------------
25
26
t0 = time.time()
27
prg = cl.Program(ctx, """
29
__kernel void ParallelSelection(__global const float * in,
__global float * out)
30
{
28
31
int i = get_global_id(0);
32
int n = get_global_size(0);
33
float a = in[i];
34
int pos = 0;
35
for (int j = 0; j < n; j++)
36
{
37
float b = in[j];
38
bool manji = (b < a) || (b == a && j < i);
39
pos += (manji) ? 1 : 0;
40
}
41
out[pos] = a;
42
}
43
""").build()
44
45
t = time.time()
46
event = prg.ParallelSelection(queue, a.shape, (256,),
a_dev.data, dest_dev.data)
47
48
event.wait()
49
gpu_time = time.time() - t
50
51
# device -> host ------------------------------------------------
52
t = time.time()
53
sorted_a = dest_dev.get()
54
pull = time.time() - t
55
66
56
# ---------------------------------------------------------------
57
gpu_total = gpu_time + push + pull
58
t0 = time.time()
59
a = diplomski.selection_sort(N, a.tolist())
60
t1 = time.time()
61
a = numpy.matrix(a).astype(numpy.float32).tolist()[0][:N]
62
#a.sort()
63
cpu_time = t1-t0
64
print "GPU total [s]:", gpu_total
65
print "GPU push [s]:", push
66
print "GPU pull [s]:", pull
67
print "GPU compute (host-timed) [s]:", gpu_time
68
69
# cpu comparison ------------------------------------------------
70
print
71
print "GPU==CPU:",all(a == sorted_a)
72
print
73
print "CPU time (s)", cpu_time
74
print
75
76
print "GPU speedup (with transfer): ", cpu_time/gpu_total
77
print "GPU speedup (without transfer): ", cpu_time/gpu_time
Dijelovi koda vezani uz kreiranje konteksta i reda naredbi, odabira OpenCL uredaja,
kopiranja
¯
podataka u globalnu memoriju OpenCL uredaja
¯ i sliˇcnih akcija su vrlo sliˇcni ve´c prethodno objašnjenim kodovima, pa ih autor ovdje ne´ce detaljno opisivati.
Ovdje je najbitnije opisati sam OpenCL kernel koji je definiran u programskom kodu s poˇcetkom
na 27. liniji koda i završetkom na 43. liniji koda. Kernel se pokre´ce jednom za svaki od N brojeva
koje treba sortirati. Kad se pokrene kernel za odredenu
radnu stavku on dohva´ca globalni ID radne
¯
stavke te ukupan broj svih radnih stavki (tj. broj N ) (31. i 32. linija koda).
Nakon toga pokre´ce se petlja koja se izvršava N puta koja prolazi po svim elementima polja koje
treba sortirati te odreduje
¯ koliko brojeva unutar polja je manje od broja kojeg predstavlja trenutna
radna stavka (35. do 40. linija koda). Odmah je oˇcito da je ovaj algoritam priliˇcno loš zbog jako
velikog broja pristupa globalnoj memoriji OpenCL uredaja.
¯
Na temelju ukupnog broja vrijednosti koje su manje od vrijednosti koju predstavlja trenutna radna
stavka odredeuje
se pozicija u sortiranom polju na koju treba smjestiti vrijednost koju predstavlja
¯
trenutna radna stavka te se ta vrijednost zapisuje u sortirano izlazno polje (41. linija koda).
67
Brzine sortiranja na GPU usporedene
su s brzinama sortiranja na centralnom procesoru raˇcunala, a
¯
u oba sluˇcaja korišten je selection sort algoritam. Sljede´ci kod predstavlja selection sort algoritam
koji se izvršavao na centralnom procesoru raˇcunala implementiran u programskom jeziku C.
1
2
float* selection_sort(int N, float* A){
int i, j, Max;
3
float pom;
4
for (i = N; i > 1; i--) {
5
Max = 0;
6
for (j = 1; j < i; j++)
if (A[j] > A[Max]) Max
7
8
pom = A[i-1];
9
A[i-1] = A[Max];
A[Max] = pom;
10
11
}
12
return A;
13
= j;
}
Implementacija sortiranja u OpenCL-u prikazana u ovom dijelu diplomskog rada daje priliˇcno loše
performanse, kao i implementacija tog algoritma na CPU zbog složenosti selection sort algoritma
koja iznosi O(n2 ), što se može zakljuˇciti i na temelju sljede´cih tablica koje prikazuju rezultate
testiranja ovog algoritma na razliˇcitim grafiˇckim i centralnim procesorima. U ovom poglavlju
prikazana je i implementacija bitonic sort algoritma u OpenCL-u koja daje puno bolje performanse.
N = 16K
N = 32K
N = 64K
N = 128K
N = 256K
GPU
CPU
0.0364 s 0.1890 s
0.0737 s 0.6916 s
0.2945 s 2.7325 s
1.0843 s 10.8105 s
4.2843 s 43.5514 s
CPU / GPU
5.19
9.38
9.28
9.97
10.17
Tablica 6.14: Usporedba brzina sortiranja selection sort algoritmom - Phenom X4 955 BE 3.2
GHz i Radeon HD 6850
Iako je selection sort algoritam jedan od lošijih algoritama za sortiranje te ne daje najbolje performanse, implementacija tog algoritma u OpenCL-u daje bolje performanse od implementacije
na CPU. Ubrzanje dobiveno korištenjem grafiˇckog procesora umjesto centralnog procesora ovisi o
broju stream procesora kojima raspolaže GPU, a kod GPU-ova s ve´cim brojem stream procesora
ubrzanje je ve´ce (tablice 6.14 i 6.15). Rezultati provedenih testiranja pokazuju ubrzanje i do 10
puta, dok ubrzanja na sporijem grafiˇckom procesoru iznose oko 2 puta (tablica 6.16). Ipak, izmjenama u OpenCL kernelu mogu se posti´ci i znatno ve´ca ubrzanja, što c´ e biti prikazano u nastavku.
68
N = 16K
N = 32K
N = 64K
N = 128K
N = 256K
GPU
CPU
0.0326 s 0.2145 s
0.1104 s 0.8427 s
0.4138 s 3.2758 s
1.6128 s 15.6254 s
6.3352 s 61.7627 s
CPU / GPU
6.57
7.64
7.92
9.69
9.75
Tablica 6.15: Usporedba brzina sortiranja selection sort algoritmom - i7 720QM 1.6 GHz i
Mobility Radeon HD 5850
GPU
N = 16K
0.3383 s
N = 32K
1.2835 s
N = 64K
4.9626 s
N = 128K 19.7430 s
N = 256K 78.7903 s
CPU
CPU / GPU
0.62702 s
1.85
2.3934 s
1.86
9.4942 s
1.91
38.1019 s
1.92
157.1929 s
1.99
Tablica 6.16: Usporedba brzina sortiranja selection sort algoritmom - E-350 APU 1.6 GHz s
HD6310
6.6.2.
Selection sort — poboljšanje korištenjem lokalne memorije
U nastavku je prikazan modificirani OpenCL kernel selection sort algoritma koji je izmijenjen tako
da koristi lokalnu memoriju OpenCL uredaja.
Prikazan je samo programski kod kernela pisan u
¯
modificiranom C jeziku kojeg koristi OpenCL bez koda u Pythonu jer je ostatak koda koji je pisan
u Python programskom jeziku jednak kao i ve´c prikazani programski kod osnovnog selection sort
algoritma implementiranog u OpenCL-u. Sliˇcno vrijedi i za ostala poboljšanja kernela prikazana
u nastavku, pa c´ e i tamo biti samo prikazani kerneli bez programskog koda u Pythonu.
2
__kernel void ParallelSelection(__global const float * in,
__global float * out)
3
{
1
4
__local float pom[256];
5
int i = get_global_id(0);
6
int li = get_local_id(0);
7
int n = get_global_size(0);
8
float a = in[i];
9
int pos = 0;
10
for (int j = 0; j < n; j+= 256)
11
{
12
pom[li] = in[j + li];
13
barrier(CLK_LOCAL_MEM_FENCE);
14
for (int k = 0; k < 256; k++) {
69
15
float b = pom[k];
16
bool manji = (b < a) || (b == a && (j+k) < i);
17
pos += (manji) ? 1 : 0;
18
}
19
barrier(CLK_LOCAL_MEM_FENCE);
20
}
21
out[pos] = a;
22
}
Kernel je izmjenjen tako da koristi lokalnu memoriju na naˇcin da svaka radna skupina u lokalnoj
memoriji ima dostupno polje od 256 elemenata (4. linija koda), gdje 256 predstavlja veliˇcinu
radne skupine. Kernel radi tako da se u lokalnu memoriju sprema 256 elemenata poˇcetnog polja
koje treba sortirati (12. linija koda), nakon toga svaka radna stavka provjerava koliko od tih 256
vrijednosti je ve´ce od vrijednosti koju predstavlja ta radna stavka (14. do 18. linija koda), te se
nakon toga prelazi na sljede´cih 256 vrijednosti poˇcetnog polja. Taj postupak se ponavlja N/256
puta, gdje N predstavlja broj vrijednosti koje treba sortirati.
Svaka radna stavka unutar radne grupe u lokalnu memoriji spremi jednu od 256 vrijednosti (12.
linija koda). Da bi se osiguralo da su sve radne stavke vrijednosti pohranile u lokalnu memoriju
koristi se memorijska barijera (13. linija koda). Radne stavke unutar radne grupe nakon toga mogu
pristupiti vrijednostima koje su ostale radne stavke pohranile u lokalnu memoriju. Budu´ci da se
iz polja vrijednosti koje treba sortirati redosljedom uzima po 256 vrijednosti te se te vrijednosti
spremaju u lokalnu memoriju OpenCL uredaja,
potrebno je koristiti memorijsku barijeru kako bi
¯
se osiguralo da vrijednosti pohranjene u lokalnu memoriju predstavljaju vrijednosti iz iste skupine
od 256 vrijednosti iz polja kojeg treba sortirati (19. linija koda).
Korištenjem lokalne memorije smanjuje se ukupan broj pristupa globalnoj memoriji, a to rezultira
poboljšanjem performansi ovog kernela u usporedbi s prethodno prikazanim kernelom.
N = 16K
N = 32K
N = 64K
N = 128K
N = 256K
GPU
CPU
0.0077 s 0.1889 s
0.0343 s 0.6916 s
0.0844 s 2.7320 s
0.3334 s 10.8103 s
1.2746 s 43.5513 s
CPU / GPU
24.44
20.19
32.37
32.43
34.17
Tablica 6.17: Usporedba brzina sortiranja selection sort algoritmom - Phenom X4 955 BE 3.2
GHz i Radeon HD 6850
Prema rezultatima iz tablica 6.17, 6.18 i 6.19 može se zakljuˇciti da ovaj OpenCL kernel daje znatno
bolje performanse od selection sort algoritma implementiranog za CPU. Ubrzanja dostižu cˇ ak i do
oko 35 puta na grafiˇckim procesorima s ve´cim brojem dostupnih stream procesora (tablice 6.17 i
70
N = 16K
N = 32K
N = 64K
N = 128K
N = 256K
GPU
CPU
0.0109 s 0.2113 s
0.0387 s 0.8089 s
0.1285 s 3.3676 s
0.4802 s 15.2511 s
1.8918 s 62.5134 s
CPU / GPU
19.41
20.88
26.21
31.76
33.04
Tablica 6.18: Usporedba brzina sortiranja selection sort algoritmom - i7 720QM 1.6 GHz i
Mobility Radeon HD 5850
GPU
N = 16K
0.0981 s
N = 32K
0.3752 s
N = 64K
1.4804 s
N = 128K 5.9043 s
N = 256K 23.5969 s
CPU
CPU / GPU
0.7692 s
7.84
2.9842 s
7.95
11.8765 s
8.02
47.9504 s
8.12
199.0612 s
8.44
Tablica 6.19: Usporedba brzina sortiranja selection sort algoritmom - E-350 APU 1.6 GHz s
HD6310
6.18), dok na sporijem grafiˇckom procesoru ubrzanje iznosi oko 8 puta (tablica 6.19). U usporedbi
s prethodnim kernelom ovaj kernel ima znatno bolje performanse, pa možemo zakljuˇciti da se
korištenjem lokalne memorije može znaˇcajno skratiti vrijeme potrebno za izvršavanje kernela, a
time i vrijeme potrebno za rješavanje odredenog
problema korištenjem OpenCL-a.
¯
6.6.3.
Selection sort — poboljšanje smanjenjem ukupnog broja radnih
stavki
U nastavku je prikazan kernel najjednostavnije verzije selection sort algoritma implementiranog
u OpenCL-u koji je modificiran tako da je smanjen ukupan broj radnih stavki, cˇ ime je ujedno
smanjen i ukupan broj pristupa globalnoj memoriji OpenCL uredaja.
Zbog smanjenog broja pris¯
tupa globalnoj memoriji ovaj kernel daje znatno bolje performanse od prvog opisanog kernela koji
implementira selection sort algoritam.
2
__kernel void ParallelSelection(__global const float * in,
__global float * out)
3
{
4
#define NB %d
1
5
6
int i = get_global_id(0);
7
int n = get_global_size(0);
8
9
float a[NB];
71
int pos[NB];
10
11
for (int k = 0; k < NB; k++) {
12
a[k] = in[NB * i + k];
pos[k] = 0;
13
14
}
15
16
for (int j = 0; j < NB * n; j++)
{
17
18
19
float b = in[j];
20
for (int k = 0; k < NB; k++) {
bool manji = (b < a[k]) || (b == a[k] && j < NB * i + k);
pos[k] += (manji) ? 1 : 0;
21
22
}
23
}
24
25
for (int k = 0; k < NB; k++) out[pos[k]] = a[k];
26
27
}
Smanjenje broja radnih stavki, a time i broja pristupa globalnoj memoriji postignuto je tako da su
varijable a i pos zamijenjene poljima veliˇcine NB (9. i 10. linija koda). Vrijednosti NB koje su
korištene u testiranju su: 1, 2, 4, 8 i 16. Prema rezultatima iz tablica 6.20, 6.21 i 6.22 može se
zakljuˇciti da kernel u ve´cini sluˇcajeva daje najbolje performanse kad je vrijednost NB jednaka 8.
Budu´ci da su varijable a i pos zamijenjene poljima veliˇcine NB, akcije pohrane i cˇ itanja podataka
iz varijabli a i pos zamijenjene su for petljama koje iste akcije provode nad cijelim poljima a i pos
(14. do 17. linija koda, 25. do 28. linija koda i 33. linija koda).
GPU
N = 128K, NB = 1 1.0831 s
N = 128K, NB = 2 0.5628 s
N = 128K, NB = 4 0.3360 s
N = 128K, NB = 8 0.2958 s
N = 128K, NB = 16 0.3852 s
CPU
10.7964 s
10.7783 s
10.8107 s
10.8134 s
10.8271 s
CPU / GPU
9.97
19.15
32.17
36.56
28.11
Tablica 6.20: Usporedba brzina sortiranja selection sort algoritmom - Phenom X4 955 BE 3.2
GHz i Radeon HD 6850
Performanse ovog kernela kad je NB = 8 vrlo su sliˇcne performansama prethodnog kernela koji
koristi lokalnu memoriju OpenCL uredaja,
a u oba sluˇcaja vidi se znatno pove´canja performansi u
¯
usporedbi s prvim i najjednostavnijim kernelom koji implementira selection sort algoritam. Vidljivo je i da se pove´canjem vrijednosti NB pove´cavaju i performanse kernela, no vrijednost NB se
72
GPU
N = 128K, NB = 1 1.6167 s
N = 128K, NB = 2 0.8255 s
N = 128K, NB = 4 0.4667 s
N = 128K, NB = 8 0.4510 s
N = 128K, NB = 16 0.5435 s
CPU
15.6132 s
15.5699 s
15.5923 s
15.6774 s
15.6445 s
CPU / GPU
9.66
18.86
33.41
34.76
28.79
Tablica 6.21: Usporedba brzina sortiranja selection sort algoritmom - i7 720QM 1.6 GHz i
Mobility Radeon HD 5850
GPU
CPU
N = 128K, NB = 2 9.8690 s 38.1140 s
N = 128K, NB = 4 5.4970 s 37.9513 s
N = 128K, NB = 8 5.0250 s 38.1058 s
CPU / GPU
3.86
6.90
7.58
Tablica 6.22: Usporedba brzina sortiranja selection sort algoritmom - E-350 APU 1.6 GHz s
HD6310
ne može pove´cavati u nedogled kako bi se pove´cale performanse ovog kernela jer nakon odredene
¯
vrijednosti NB (najˇceš´ce 8) performanse ovog kernela znatno opadaju zbog ograniˇcene lokalne
memorije OpenCL uredaja.
¯
6.6.4.
Selection sort — kombinacija 1. i 2. poboljšanja
U nastavku je prikazan kernel koji implementira oba prethodno opisana poboljšanja OpenCL implementacije selection sort algoritma (korištenje lokalne memorije i smanjenje ukupnog broja radnih stavki).
2
__kernel void ParallelSelection(__global const float * in,
__global float * out)
3
{
1
4
#define NB %d
5
6
int i = get_global_id(0);
7
int n = get_global_size(0);
8
int li = get_local_id(0);
9
10
float a[NB];
11
int pos[NB];
12
__local float pom[256];
13
14
for (int k = 0; k < NB; k++) {
73
a[k] = in[NB * i + k];
pos[k] = 0;
15
16
}
17
18
for (int j = 0; j < NB * n; j+= 256)
{
19
20
21
pom[li] = in[j + li];
22
barrier(CLK_LOCAL_MEM_FENCE);
23
for (int l = 0; l < 256; l++) {
24
float b = pom[l];
25
for (int k = 0; k < NB; k++) {
bool manji = (b < a[k]) || (b == a[k] && (j+l) < NB * i + k);
pos[k] += (manji) ? 1 : 0;
26
27
}
28
29
}
30
barrier(CLK_LOCAL_MEM_FENCE);
}
31
32
for (int k = 0; k < NB; k++) out[pos[k]] = a[k];
33
34
}
Korištena je lokalna memorija kao i u prvom poboljšanju kako bi se smanjio ukupan broj pristupa
globalnoj memoriji, pa je u tu svrhu definirano lokalno polje od 256 elemenata (12. linija koda)
u koje se redom sprema po 256 vrijednosti ulaznog polja koje sadrži vrijednosti koje treba sortirati. Osim toga varijable a i pos su zamijenjene poljima veliˇcine NB kao i u drugom prikazanom
poboljšanju osnovne implementacije selection sort algoritma u OpenCL-u (10. i 11. linija koda).
GPU
CPU
CPU / GPU
N = 128K, NB = 1 0.2994 s
10.8144 s
36.12
N = 128K, NB = 2 0.2666 s
10.8088 s
40.54
N = 128K, NB = 4 0.2420 s
10.8120 s
44.68
N = 128K, NB = 8 0.2217 s
10.8500 s
48.57
N = 128K, NB = 16 0.2212 s
10.8204 s
48.93
N = 256K, NB = 8 0.8070 s
43.5502 s
53.97
N = 512K, NB = 8 3.2179 s 175.17976 s
54.44
Tablica 6.23: Usporedba brzina sortiranja selection sort algoritmom - Phenom X4 955 BE 3.2
GHz i Radeon HD 6850
Prema rezultatima iz tablica 6.23, 6.24 i 6.25 vidljivo je da kombinacija prvog i drugog poboljšanja
selection sort algoritma u OpenCL-u daje performanse bolje od svakog poboljšanja zasebno te da
performanse variraju ovisno o vrijednosti NB, a najbolje performanse se dobivaju kad je N B = 8.
74
GPU
CPU
N = 128K, NB = 1 0.4485 s 15.5288 s
N = 128K, NB = 2 0.3730 s 15.7044 s
N = 128K, NB = 4 0.3323 s 15.5116 s
N = 128K, NB = 8 0.3214 s 15.6791 s
N = 128K, NB = 16 0.3609 s 15.6851 s
N = 256K, NB = 8 1.1828 s 62.4519 s
N = 512K, NB = 8 4.7233 s 256.1932 s
CPU / GPU
34.63
42.10
46.68
48.78
43.46
52.80
54.24
Tablica 6.24: Usporedba brzina sortiranja selection sort algoritmom - i7 720QM 1.6 GHz i
Mobility Radeon HD 5850
GPU
N = 128K, NB = 1
5.5416 s
N = 128K, NB = 2
4.5612 s
N = 128K, NB = 4
4.0379 s
N = 128K, NB = 8
3.7085 s
N = 256K, NB = 16 14.8604 s
CPU
CPU / GPU
37.9940 s
6.86
37.9932 s
8.33
37.9934 s
9.41
38.0608 s
10.26
156.7870 s
10.55
Tablica 6.25: Usporedba brzina sortiranja selection sort algoritmom - E-350 APU 1.6 GHz s
HD6310
Poboljšanja zasebno daju ubrzanja od oko 30 do 35 puta kad je N ≥ 128K i N B = 8 na testiranim
grafiˇckim procesorima s ve´cim brojem stream procesora, dok na testiranom grafiˇckom procesoru
s manjim brojem dostupnih stream procesora ubrzanja iznose oko 8 puta. Ubrzanje ove implementacije selection sort algoritma u OpenCL-u s oba poboljšanja u usporedbi s implementacijom
na CPU iznose oko 50 puta za N ≥ 128K i NB = 8 na testiranim grafiˇckim procesorima s vec´ im brojem stream procesora, dok na testiranom grafiˇckom procesoru s manjim brojem dostupnih
stream procesora ubrzanja iznose oko 10 puta.
6.6.5.
Usporedba implementiranih verzija selection sort algoritma
Prvo poboljšanje je oko 4 puta brže od osnovne implementacije selection sort algoritma u OpenCLu, dok je drugo poboljšanje takoder
¯ oko 4 puta brže od osnovne implementacije. Kombinacija
prvog i drugog poboljšanja je oko 5 puta brža od osnovne implementacije, što pokazuje da se
kombiniranjem raznih poboljšanja može dodatno dobiti na performansama.
75
Slika 6.7: Usporedba brzina sortiranja korištenjem razliˇcitih implementacija selection sort
algoritma - Radeon HD 6850
6.6.6.
Bitonic sort
U ovom dijelu rada bit c´ e obraden
¯ bitonic sort (Batcher, 1968) algoritam implementiran u OpenCLu koji sortira cijelo polje od N brojeva. U nastavku je prikazan programski kod bitonic sort algoritma implementiranog u OpenCL-u.
2
# -*- coding: utf-8 -*import pyopencl as cl
3
import pyopencl.array as cl_array
4
import numpy
5
import time
6
import diplomski
1
7
8
N = eval(raw_input("Unesite N: "))
9
a = numpy.random.rand(N).astype(numpy.float32)
10
11
platforms = cl.get_platforms()
12
devices = platforms[0].get_devices(cl.device_type.GPU)
13
device = devices[0]
14
ctx = cl.Context([device])
15
queue = cl.CommandQueue(ctx)
16
17
prg = cl.Program(ctx, """
76
18
19
__kernel void ParallelBitonic_A(__global const float * in,
__global float * out,
20
const int inc,
21
const int dir)
22
{
23
int i = get_global_id(0);
24
int j = i ^ inc;
25
26
float a = in[i];
27
float b = in[j];
28
29
bool manji = (b < a) || ( b == a && j < i );
30
bool swap = manji ^ (j < i) ^ ((dir & i) != 0);
31
out[i] = (swap) ? b : a;
32
33
}
34
""").build()
35
36
push = 0
37
gpu_time = 0
38
pull = 0
39
count = 10
40
for i in range(count):
41
# host -> device --------------------------------------------
42
t = time.time()
43
a_dev = cl_array.to_device(queue, a)
44
dest_dev = cl_array.empty_like(a_dev)
45
push += time.time() - t
46
# -----------------------------------------------------------
47
48
t0 = time.time()
49
n = a.shape[0]
50
length = 1
51
while length < n:
52
inc = length
53
while inc > 0:
54
55
event = prg.ParallelBitonic_A(queue, a.shape, (256,),
a_dev.data,
77
56
dest_dev.data,
57
numpy.int32(inc),
58
numpy.int32(length << 1))
59
event.wait()
60
q = a_dev
61
a_dev = dest_dev
62
dest_dev = q
63
inc >>= 1
length <<= 1
64
65
66
t1 = time.time()
67
gpu_time += t1 - t0
68
69
70
# device -> host --------------------------------------------
71
t = time.time()
72
sorted_a = a_dev.get()
73
pull += time.time() - t
74
75
gpu_time /= count
76
push /= count
77
pull /= count
78
79
# ---------------------------------------------------------------
80
b = a[:]
81
gpu_total = gpu_time + push + pull
82
t0 = time.time()
83
a = diplomski.bitonic_sort(N, a.tolist())
84
t1 = time.time()
85
cpu_time = t1-t0
86
t0 = time.time()
87
b = b.sort()
88
t1 = time.time()
89
cpu_time2 = t1-t0
90
91
print "GPU total [s]:", numpy.round(gpu_total, 4)
92
print "GPU push [s]:", push
93
print "GPU pull [s]:", pull
78
94
print "GPU compute (host-timed) [s]:", gpu_time
95
96
# cpu comparison ------------------------------------------------
97
print
98
print "GPU==CPU:",all(a == sorted_a)
99
print
100
print "CPU time (C - bitonic sort) (s)", numpy.round(cpu_time, 4)
101
print "CPU time (numpy.sort) (s)", numpy.round(cpu_time2, 4)
102
print
103
104
print "GPU speedup (with transfer) - C, bitonic sort: ", \
105
numpy.round(cpu_time/gpu_total, 2)
106
print "GPU speedup (without transfer) - C, bitonic sort: ", \
107
cpu_time/gpu_time
108
109
print "GPU speedup (with transfer) - numpy.sort: ", \
110
numpy.round(cpu_time2/gpu_total, 2)
111
print "GPU speedup (without transfer) - numpy.sort: ",
112
cpu_time2/gpu_time
\
Jednako kao i kod prethodnog programskog koda dijelovi koda vezani uz kreiranje konteksta i
reda naredbi, odabira OpenCL uredaja,
kopiranja podataka u globalnu memoriju OpenCL uredaja
¯
¯
i sliˇcnih akcija su vrlo sliˇcni ve´c prethodno objašnjenim kodovima, pa ih autor ovdje ne´ce detaljno
opisivati.
Postoje bitne razlike izmedu
¯ ovog i prethodnog programskog koda. U ovoj implementaciji bitonic
sort algoritma pokretanje kernela nad svim radnim stavkama nalazi se unutar dviju while petlji (51.
do 64. linija koda). Vanjska petlja se vrti tako dugo dok je varijabla length manja od N , s time da
se u svakoj iteracija varijabla length množi s brojem 2 (64. linija koda). Unutarnja petlja se odvija
tako dugo dok je varijabla inc ve´ca od 0, s time da se u svakoj iteraciji varijabla inc dijeli s brojem
2 (63. linija koda). Poˇcetna vrijednost varijable inc je jednaka trenutnoj vrijednosti varijable length
(52. linija koda). Kod pokretanja kernela prosljeduju
se izmedu
¯
¯ ostalog varijable inc i length.
Nakon što se kernel izvrši zamijene se pokazivaˇci na izlazno polje i polje koje se sortira (60. do
62. linija koda). Na taj naˇcin izlazno polje brojeva koje je nastalo kao rezultat izvršavanja kernela
postaje ulazno polje nad kojim c´ e se u sljede´coj iteraciji izvršavati novi kernel, a prethodno ulazno
polje postaje izlazno polje za sljede´cu iteraciju. Na taj naˇcin nije potrebno ponovno kreiranje
novih polja te njihovo kopiranje u globalnu memoriju OpenCL uredaja
¯ pa se time znatno dobiva na
performansama. Sav opisan kod u biti predstavlja bitonic sort algoritam prilagoden
¯ za izvršavanje
na grafiˇckom procesoru raˇcunala i kod je sliˇcan kodu standardnog bitonic sort algoritma.
79
Standardni bitonic sort algoritam implementiran u programskom jeziku C koji je korišten kod
testiranja i usporedba brzina prikazan je u nastavku.
1
const int ASCENDING = 1;
2
const int DESCENDING = 0;
3
4
5
void compare(int i, int j, int dir, float* A)
{
if (dir==(A[i]>A[j]))
6
{
7
8
float h=A[i];
9
A[i]=A[j];
A[j]=h;
10
}
11
12
}
13
14
15
void bitonicMerge(int lo, int cnt, int dir, float *A)
{
if (cnt>1)
16
{
17
18
int k=cnt/2;
19
int i;
20
for (i=lo; i<lo+k; i++)
21
compare(i, i+k, dir, A);
22
bitonicMerge(lo, k, dir, A);
23
bitonicMerge(lo+k, k, dir, A);
}
24
25
}
26
27
28
29
30
void bitonicSort(int lo, int cnt, int dir, float *A)
{
if (cnt>1)
{
31
int k=cnt/2;
32
bitonicSort(lo, k, ASCENDING, A);
33
bitonicSort(lo+k, k, DESCENDING, A);
34
bitonicMerge(lo, cnt, dir, A);
35
}
80
36
}
37
38
39
float* bitonic_sort(int N, float *A)
{
41
float *B = malloc(4 * N);
int i;
42
for (i = 0; i < N; i++) B[i] = A[i];
43
bitonicSort(0, N, ASCENDING, B);
44
return B;
40
45
}
U sljede´cim tablicama prikazani su rezultati dobiveni testiranjem implementiranog algoritma.
GPU (bitonic sort) CPU (C - bitonic sort) CPU / GPU
N = 1M
0.0727 s
0.693 s
9.53
N = 4M
0.2109 s
3.2363 s
15.34
N = 8M
0.4268 s
6.9739 s
16.34
N = 12M
0.60 s
9.9207 s
16.53
Tablica 6.26: Usporedba brzina sortiranja - Phenom X4 955 BE 3.2 GHz i Radeon HD 6850
N = 1M
N = 4M
N = 8M
N = 12M
GPU (bitonic sort) CPU (numpy.sort) CPU / GPU
0.0727 s
0.0876 s
1.21
0.2109 s
0.3874 s
1.84
0.4268 s
0.8143 s
1.91
0.60 s
1.2443 s
2.07
Tablica 6.27: Usporedba brzina sortiranja bitonic sort algoritmom implementiranog u
OpenCL-u sa sortiranjem pomo´cu numpy.sort rutine - Phenom X4 955 BE 3.2 GHz i Radeon HD 6850
Prema tablici 6.27 i slici 6.8 vidljivo je da bitonic sort algoritam implementiran u OpenCL-u i izvršen na grafiˇckom procesoru Radeon HD 6850 ima oko 2 puta ve´cu brzinu od sortiranja na centralnom procesoru Phenom X4 955 BE 3.2 GHz ukoliko se kod sortiranja na CPU koristi numpy.sort
rutina u Python programskom jeziku. Ubrzanje u usporedbi s bitonic sort algoritmom implementiranom u programskom jeziku C iznosi oko 15 puta (tablica 6.26), što je sasvim zadovoljavaju´ce.
Možemo zakljuˇciti da je bitonic sort algoritam prikladan za implementaciju u OpenCL-u.
Ako se usporeduju
brzine sortiranja izmedu
¯
¯ centralnog procesora i grafiˇckog procesora s manjim
brojem dostupnih stream procesora (tablice 6.28 i 6.29) tada je korištenje OpenCL-a manje isplativo. U testiranjima su se postigla ubrzanja do oko 4 puta kada se OpenCL algoritam usporedivao
¯
s bitonic sort algoritmom implementiranim u programskom jeziku C (tablica 6.28), dok je implementirani OpenCL algoritam nešto sporiji od korištenja numpy.sort rutine (tablica 6.29).
81
Slika 6.8: Usporedba brzina sortiranja na CPU (numpy.sort rutina) i GPU - Phenom X4 955
BE 3.2 GHz i Radeon HD 6850
GPU (bitonic sort) CPU (C - bitonic sort) CPU / GPU
N = 1M
0.6343 s
2.3482 s
3.7
N = 4M
2.3704 s
10.8494 s
4.58
Tablica 6.28: Usporedba brzina sortiranja - E-350 APU 1.6 GHz s HD6310
GPU (bitonic sort) CPU (numpy.sort) CPU / GPU
N = 1M
0.6343 s
0.2468 s
0.39
N = 4M
2.3704 s
1.0947 s
0.46
Tablica 6.29: Usporedba brzina sortiranja bitonic sort algoritmom implementiranog u
OpenCL-u sa sortiranjem pomo´cu numpy.sort rutine - E-350 APU 1.6 GHz s HD6310
Vidljiva je vrlo velika razlika u performansama u odnosu na selection sort implementiran u OpenCLu, a glavni razlog za to je cˇ injenica da selection sort algoritam ima složenost O(n2 ), dok bitonic
sort algoritam ima složenost O(n log n).
Ovaj algoritam sortiranja oˇcekivano daje puno bolje rezultate od prethodno implementiranog selection sort algoritma. Algoritam je i brži od sortiranja pomo´cu centralnog procesora raˇcunala,
barem kad se sortiraju ve´ca polja brojeva i kada se koristi grafiˇcki procesor s dovoljnim brojem
dostupnih stream procesora.
82
ˇ
7. Zakljucak
Jednostavne implementacije algoritama za GPU u OpenCL-u su obiˇcno znatno brže od jednostavnih implementacija za CPU u C-u. To se u ovom radu pokazalo kod usporedbe jednostavnih
implementacija množenja matrica, selection sort i bitonic sort algoritma u OpenCL-u s jednostavnim implementacijama tih algoritama u C-u. Medutim,
za CPU su dostupne izuzetno optimirane
¯
rutine (na primjer numpy.sort i numpy.dot) koje tu razliku praktiˇcki anuliraju. Tek optimiranjem
OpenCL implementacije korištenjem lokalne memorije GPU-a, smanjenjem broja radnih stavki i
sliˇcno (što je u ovom diplomskom radu prikazano na primjerima množenja matrica i sortiranja),
dakle uz znatan napor na implementaciji mogu´ce je posti´ci i znatno ubrzanje na GPU u usporedbi
s optimiranim implementacijama na CPU.
Prema svemu dosad objašnjenom i prikazanom u ovom diplomskom radu može se zakljuˇciti da
GPGPU ima smisla koristiti za rješavanje odredenih
vrsta problema, dok za odredene
vrste pro¯
¯
blema GPGPU jednostavno nije prikladan. Prema provedenim testiranjima u poglavlju 6. može se
zakljuˇciti da se GPGPU isplati koristiti za izraˇcun Mandelbrotovog fraktala jer algoritam koji se
koristi u tu svrhu ima vrlo mali broj ulaza i svaka toˇcka koju treba izraˇcunati potpuno je nezavisna
o drugim toˇckama. GPGPU se takoder
¯ isplati koristiti za množenje matrica, dok je kod sortiranja
isplativost korištenja GPGPU-a nešto niža.
Ve´cina aplikacija ima dijelova koji se mogu puno brže izvršiti na centralnom procesoru raˇcunala.
Zbog toga je kod izrade aplikacija bitno koristiti centralni procesor za izvršavanje takvih dijelova
programa, a grafiˇcki procesor je potrebno koristiti za rješavanje problema za cˇ ije rješavanje je GPU
pogodniji tako da se GPU i CPU medusobno
nadopunjuju i time se dobivaju najbolje mogu´ce
¯
performanse. PyOpenCL modul za Python omogu´cava jednostavno pisanje OpenCL programa uz
podršku za sve funkcionalnosti OpenCL-a, što znaˇci da se Python programski jezik može iskoristiti
za brzo i jednostavno pisanje programa raznih namjena koji c´ e u procesorski zahtjevnim dijelovima
koristiti OpenCL.
Potrebno je napomenuti da je autor sve implementirane algoritme testirao na više operacijskih
sustava (Arch Linux, Ubuntu 13.04 i Windows 7) i zakljuˇcio da nema znaˇcajnih razlika u brzinama
izvodenja
s obzirom na operacijski sustav ukoliko se koriste najnovije verzije drivera.
¯
83
8. Literatura
1. Batcher, K. E. (1968). Sorting networks and their applications. In Proceedings of the April
30–May 2, 1968, spring joint computer conference (pp. 307–314). Dostupno na
https://www.cs.duke.edu/courses/fall08/cps196.1/Literature/
bitonic_sort.pdf
2. Gaster, B., Howes, L., Kaeli, D. R., Mistry, P., & Schaa, D. (2011). Heterogeneous computing
with OpenCL (1st ed.). Morgan Kaufmann.
3. Gaster, B. R., & Howes, L. (2013). The future of the APU braided parallelism. Dostupno na
http://developer.amd.com/wordpress/media/2013/06/2901_final
.pdf
4. Kirk, D. B., & mei W. Hwu, W. (2010). Programming massively parallel processors: A
hands-on approach (applications of GPU computing series) (1st ed.). Morgan Kaufmann.
5. Klckner, A. (2009, 9). GPU metaprogramming using PyCUDA: Methods & applications.
Dostupno na
http://mathema.tician.de/news.tiker.net/files/pycuda-nvidia
.pdf
6. Klockner, A. (2010, 9). Pycuda: Even simpler GPU programming with Python. Dostupno na
http://mathema.tician.de/news.tiker.net/files/main.pdf
7. Knuth, D. E. (1998). Art of computer programming, volume 3: Sorting and searching (2nd
edition). Addison-Wesley Professional.
8. Merrill, D., & Grimshaw, A. (2011). High performance and scalable radix sorting: A case
study of implementing dynamic parallelism for GPU computing. Parallel Processing Letters,
21(02), 245–272. Dostupno na
http://back40computing.googlecode.com/svn-history/r665/wiki/
documents/PplGpuSortingPreprint.pdf
9. Munshi, A. (2012, 9). The OpenCL specification. Dostupno na
http://www.khronos.org/registry/cl/specs/opencl-1.2.pdf
10. Munshi, A., Gaster, B., Mattson, T. G., Fung, J., & Ginsburg, D. (2011). OpenCL programming guide (1st ed.). Addison-Wesley Professional.
11. Scarpino, M. (2011). OpenCL in action: How to accelerate graphics and computations.
Manning Publications.
84