Akcelerace výpočtů prostřednictvím GPU

Podobné dokumenty
Nvidia CUDA Paralelní programování na GPU

Nvidia CUDA Paralelní programování na GPU

Pohled do nitra mikroprocesoru Josef Horálek

GPGPU. Jan Faigl. Gerstnerova Laboratoř pro inteligentní rozhodování a řízení České vysoké učení technické v Praze

Matematika v programovacích

Úvod do GPGPU J. Sloup, I. Šimeček

Pokročilé architektury počítačů

Co je grafický akcelerátor

Koncepce (větších) programů. Základy programování 2 Tomáš Kühr

Paralelní výpočty ve finančnictví

Přehled paralelních architektur. Dělení paralelních architektur Flynnova taxonomie Komunikační modely paralelních architektur

GPU A CUDA HISTORIE GPU CO JE GPGPU? NVIDIA CUDA

GPGPU Aplikace GPGPU. Obecné výpočty na grafických procesorech. Jan Vacata

OPS Paralelní systémy, seznam pojmů, klasifikace

CUDA J. Sloup a I. Šimeček

Pokročilé architektury počítačů

Vlákno (anglicky: thread) v informatice označuje vlákno výpočtu neboli samostatný výpočetní tok, tedy posloupnost po sobě jdoucích operací.

VirtualBox desktopová virtualizace. Zdeněk Merta

Architektura počítačů

Procesy a vlákna (Processes and Threads)

Čtvrtek 8. prosince. Pascal - opakování základů. Struktura programu:

FORTANNS. 22. února 2010

Zobrazování terénu. Abstrakt. 1. Úvod. 2. Vykreslování terénu

Cvičení MI-PAP I. Šimeček, M. Skrbek, J. Trdlička

MATLABLINK - VZDÁLENÉ OVLÁDÁNÍ A MONITOROVÁNÍ TECHNOLOGICKÝCH PROCESŮ

REALIZACE SUPERPOČÍTAČE POMOCÍ GRAFICKÉ KARTY

Real Time programování v LabView. Ing. Martin Bušek, Ph.D.

TÉMATICKÝ OKRUH Softwarové inženýrství

Uživatelská příručka

Přednáška 1. Katedra počítačových systémů FIT, České vysoké učení technické v Praze Jan Trdlička, 2012

Architektura rodiny operačních systémů Windows NT Mgr. Josef Horálek

Vyuºití GPGPU pro zpracování dat z magnetické rezonance

Práce s knihovnami. Karel Richta a kol. katedra počítačů FEL ČVUT v Praze. Karel Richta, Martin Hořeňovský, Aleš Hrabalík, 2016

Windows a real-time. Windows Embedded

Předměty. Algoritmizace a programování Seminář z programování. Verze pro akademický rok 2012/2013. Verze pro akademický rok 2012/2013

MS WINDOWS I. řada operačních systémů firmy Microsoft *1985 -? Historie. Práce ve Windows XP. Architektura. Instalace. Spouštění

Nové jazykové brány do Caché. Daniel Kutáč

2.8 Procesory. Střední průmyslová škola strojnická Vsetín. Ing. Martin Baričák. Název šablony Název DUMu. Předmět Druh učebního materiálu

Projektč.3dopředmětuIZP. Maticové operace

- kvalitní dokumentace k SW je vyžadovaným STANDARDEM. vzájemná provázanost SW (IS) ve velkých společnostech. aktuální přehledná srozumitelná

Uživatelský manuál. Aplikace GraphViewer. Vytvořil: Viktor Dlouhý

INDIVIDUÁLNÍ PROJEKT 1 - ZPRACOVÁNÍ GNSS SIGNÁLU V GPU

Obsah. Kapitola 1 Hardware, procesory a vlákna Prohlídka útrob počítače...20 Motivace pro vícejádrové procesory...21

Disková pole (RAID) 1

Prostředí pro výuku vývoje PCI ovladačů do operačního systému GNU/Linux

Zpracování obrazu v FPGA. Leoš Maršálek ATEsystem s.r.o.

ŠVP Gymnázium Ostrava-Zábřeh Úvod do programování

Vyučovací hodina. 1vyučovací hodina: 2vyučovací hodiny: Opakování z minulé hodiny. Procvičení nové látky

Obsah. 1) Rozšířené zadání 2) Teorie zásuvných modulů a) Druhy aplikací používajících zásuvné moduly b) Knihovny c) Architektura aplikace d) Výhody

Maturitní otázky z předmětu PROGRAMOVÁNÍ

PROCESOR. Typy procesorů

ZÁKLADY PROGRAMOVÁNÍ. Mgr. Vladislav BEDNÁŘ /14

Implementace systémů HIPS: historie a současnost. Martin Dráb

Obecné výpočty na GPU v jazyce CUDA. Jiří Filipovič

Operační systémy. Cvičení 3: Programování v C pod Unixem

C2115 Praktický úvod do superpočítání

Základní pojmy informačních technologií

VÝUKOVÝ MATERIÁL. Bratislavská 2166, Varnsdorf, IČO: tel Číslo projektu

Identifikátor materiálu: ICT-1-17

Operační systémy. Jednoduché stránkování. Virtuální paměť. Příklad: jednoduché stránkování. Virtuální paměť se stránkování. Memory Management Unit

GPU a CUDA. Historie GPU. Co je GPGPU? Nvidia CUDA

Přednáška. Vstup/Výstup. Katedra počítačových systémů FIT, České vysoké učení technické v Praze Jan Trdlička, 2012

Programování v jazyce C a C++

GPU a CUDA. Historie GPU. Co je GPGPU? Nvidia CUDA

Paralelní architektury se sdílenou pamětí typu NUMA. NUMA architektury

Vývoj programů. ÚVOD DO OPERAČNÍCH SYSTÉMŮ

Transformace digitalizovaného obrazu

Základy informatiky. 2. Přednáška HW. Lenka Carr Motyčková. February 22, 2011 Základy informatiky 2

Inovace výuky prostřednictvím ICT v SPŠ Zlín, CZ.1.07/1.5.00/ Vzdělávání v informačních a komunikačních technologií

Přednáška. Správa paměti II. Katedra počítačových systémů FIT, České vysoké učení technické v Praze Jan Trdlička, 2012

09. Memory management. ZOS 2006, L.Pešička

Pokročilé architektury počítačů

ODBORNÝ VÝCVIK VE 3. TISÍCILETÍ. MEIV Windows server 2003 (seznámení s nasazením a použitím)

Operační systémy: funkce

Semestrální práce KIV/PC Řešení kolizí frekvencí sítě vysílačů Zdeněk Bečvář A14B0466P 10. ledna 2016

Masarykova střední škola zemědělská a Vyšší odborná škola, Opava, příspěvková organizace

Sítě SFN Systém pro analýzu a vizualizaci pokrytí a rušení vysílacích sítí

AIDA64 Extreme. Příručka k nastavení. v

Obsah. Zpracoval:

VÝUKOVÝ MATERIÁL. Bratislavská 2166, Varnsdorf, IČO: tel Číslo projektu

Formy komunikace s knihovnami

Vzdálená správa v cloudu až pro 250 počítačů

8 Třídy, objekty, metody, předávání argumentů metod

Program pro tvorbu technických výpočtů. VIKLAN - Výpočty. Uživatelská příručka. pro seznámení se základními možnostmi programu. Ing.

GPU jako levný výpočetní akcelerátor pro obrazovou JPEG2000 kompresi. ORS 2011 Karviná,

Paralení programování pro vícejádrové stroje s použitím OpenMP. B4B36PDV Paralelní a distribuované výpočty

Pokročilé architektury počítačů

Představení a vývoj architektur vektorových procesorů

Operační systémy 2: Zápočtové úkoly

Téma 8: Konfigurace počítačů se systémem Windows 7 IV

VÝUKOVÝ MATERIÁL. Bratislavská 2166, Varnsdorf, IČO: tel Číslo projektu

Přidělování paměti II Mgr. Josef Horálek

Gymnázium Vysoké Mýto nám. Vaňorného 163, Vysoké Mýto

12 Metody snižování barevného prostoru

MATURITNÍ OTÁZKY ELEKTROTECHNIKA - POČÍTAČOVÉ SYSTÉMY 2003/2004 PROGRAMOVÉ VYBAVENÍ POČÍTAČŮ

monolitická vrstvená virtuální počítač / stroj modulární struktura Klient server struktura

Lekce 7 IMPLEMENTACE OPERAČNÍHO SYSTÉMU LINUX DO VÝUKY INFORMAČNÍCH TECHNOLOGIÍ

Transkript:

Mendelova univerzita v Brně Provozně ekonomická fakulta Akcelerace výpočtů prostřednictvím GPU Bakalářská práce Vedoucí práce: Ing. David Procházka, Ph.D. Karel Zídek Brno 2012

2

zadání práce

4

Děkuji vedoucímu mé práce, panu Ing. Davidu Procházkovi, Ph.D., za vedení, ochotný přístup, trpělivost a odbornou pomoc během psaní této práce.

6

Prohlašuji, že jsem tuto bakalářskou práci vypracoval samostatně pod vedením Ing. Davida Procházky, Ph.D. a uvedl všechny literární prameny a publikace, ze kterých jsem čerpal. V Brně dne 25. května 2012....................................................

8

9 Abstract Zídek, Karel. GPU acceleration of calculations. Bachelor thesis. Brno, 2012. This bachelor thesis deals with a comparison of solutions for general purpose algorithms on GPU implementation. Emphasis is placed on platforms OpenCL and CUDA. Both platforms are compared from the development and performance pointof-view. They are tested using selected graphical algorithms. Tested were OpenCL and CUDA GPU implementation with CPU version of the algorithms and the algorithm implementation with an optimized version of the OpenCV library. Keywords image processing, GPGPU, CUDA, OpenCV, OpenCL Abstrakt Zídek, Karel. Akcelerace výpočtů prostřednictvím GPU. Bakalářská práce. Brno, 2012. Tato bakalářská práce se zabývá porovnáním řešení pro realizaci obecných výpočtů na GPU. Důraz je kladen na platformy OpenCL a CUDA. V práci jsou obě platformy porovnány jak z pohledu efektivity vývoje, tak jejich výkonu. Testování je provedeno prostřednictvím implementace vybraných grafických algoritmů. Testována byla GPU implementace v CUDA a OpenCL s CPU verzí algoritmů a implementací pomocí optimalizované knihovny OpenCV. Klíčová slova zpracování obrazu, GPGPU, CUDA, OpenCV, OpenCL

10

OBSAH 11 Obsah 1 Úvod a cíl práce 13 1.1 Úvod práce................................ 13 1.2 Cíl práce.................................. 13 2 Architektura GPU 14 3 OpenCV 16 3.1 Instalace.................................. 16 3.1.1 Případová studie......................... 16 3.1.2 Problémy případové studie.................... 16 3.2 GPU akcelerace.............................. 17 4 Nvidia CUDA 18 4.1 Požadavky pro práci s CUDA...................... 18 4.2 Instalace.................................. 18 4.2.1 Případová studie......................... 19 4.3 Překlad.................................. 19 4.3.1 Oddělení C++ a CUDA kódu.................. 20 4.3.2 Překlad CUDA projektu..................... 21 4.4 Bloky a vlákna.............................. 22 4.4.1 Dimenze mřížky, bloků a vláken................. 23 4.4.2 Zpracování dat v jednorozměrné mřížce............. 24 4.4.3 Zpracování dat v dvojrozměrné mřížce............. 25 5 OpenCL 27 5.1 Požadavky pro práci s OpenCL..................... 27 5.2 Instalace.................................. 27 5.2.1 Případová studie......................... 28 5.2.2 Oddělení C++ a OpenCL kódu................. 28 5.2.3 Překlad OpenCL programu................... 28 5.2.4 Překlad OpenCL kernelu..................... 29 5.3 Rozdělení zpracování dat......................... 29 6 Porovnání implementací 32 6.1 Rozdílné implementace prahování.................... 32 6.1.1 CPU................................ 33 6.1.2 OpenCV CPU........................... 33 6.1.3 OpenCV GPU........................... 34 6.1.4 CUDA............................... 35 6.1.5 OpenCL.............................. 38 6.2 Výkonové porovnání prahování..................... 42 6.3 Implementace konvoluční masky..................... 44

12 OBSAH 6.3.1 CUDA............................... 45 6.3.2 OpenCL.............................. 48 6.4 Výkonové porovnání konvoluční masky................. 51 7 Závěr 52 8 Literatura 53 Přílohy 55 A Obsah přiloženého CD 56

1 ÚVOD A CÍL PRÁCE 13 1 Úvod a cíl práce 1.1 Úvod práce Ještě dnes, léta poté co jsem jej slyšel poprvé, mě fascinuje příběh Novozélandského motocyklového závodníka Burta Munroa. Muže, který si v roce 1926 zakoupil motocykl značky Indian s konstrukční rychlostí 89 km/h. Bez peněz, bez prostředků a zcela jistě s velmi vlažnou podporou svých přátel se nikdy nevzdal svého snu. Trvalo mu pouhých 41 let úprav a bezesných nocí, aby v roce 1967 na stejném modelu motocyklu vytvořil neoficiální světový rychlostní rekord dosažením rychlosti 331 km/h. Stejně tak, jako se dříve Burt Munroa propracovával kilometr za kilometrem za svým snem, mi dnes připadá práce programátorů a vývojářů, kteří se zabývají oblastí akcelerace výpočtů, zvláště výpočtů na grafických kartách. Jejich měřítkem nejsou kilometry za hodinu, ale milisekundy a výsledky jejich práce, během bezesných nocí, zcela jistě vědomě ocení jen velmi malé množství partnerů či přátel. Přesto nakonec mohou spatřit úsměvy na tvářích obyčejných lidí, kterým jejich oblíbená hra běží o trochu rychleji, video pro mobilní telefon je připraveno v kratším čase nebo operační systém reaguje o trochu lépe. Stejně tak mohou pozorovat nadšení ze strany vědců a inženýrů, když získají výsledky svých pokusů o něco dříve, ať již se jedná o černé díry či simulovaná vozidla. 1.2 Cíl práce Cílem práce je porovnat dvě nejrozšířenější architektury dnes dostupné pro GPGPU (General-purpose computing on graphics processing units) s ohledem na úlohy zaměřené na zpracování obrazu. Jedná se o platformu Nvidia CUDA, která se snaží prosadit svým hardwarem a jednoduchou implementací balíku CUDA v oblasti vědeckých výpočtů. A také o rychle se rozvíjející platformu OpenCL, která sází zejména na svoji otevřenost a multiplatformnost. Za účelem porovnání těchto platforem bude po prostudování odborné literatury a dostupných zdrojů implementováno několik příkladů, které umožní srovnání obou platforem nejenom z pohledu kódu, rychlosti a výhodnosti samotné GPGPU architektury oproti CPU implementacím, ale zhodnotí i rozdíly mezi oběma hlavními platformami. Pro práci s obrazovými daty bude použita knihovna OpenCV, která je často využívaná v oblasti počítačového vidění.

14 2 ARCHITEKTURA GPU 2 Architektura GPU Pro dobré pochopení a implementaci obecného kódu na GPU je dobrým pomocníkem znalost architektury GPU, tedy znalost jakým způsobem se data na grafické kartě zpracovávají. Základem grafických karet, které podporují výpočty GPGPU je tzv. multiprocesor. Multiprocesor (MPU) je variantou klasického procesoru, zaměřenou na paralelní zpracování dat. Idea multiprocesoru vzešla z potřeb zpracování aritmetických operací při vykreslování 3D scén. První grafické karty využívaly pevně daných hardwarových funkcí. Revoluce nastala okolo roku 2000 s příchodem tzv. shader jednotek. Ty umožnily programátorům a grafikům provádět ve 3D aplikacích výpočty nad vrcholy (vertex shadery) a pixely (pixel shadery). Využití shader jednotek začalo, zvláště vlivem 3D her, brzy narážet na problém způsobený HW implementací. Vykreslování některých scén dokázalo plně vytížit pouze jeden typ shaderů, zatímco výpočetní potenciál druhého zůstával nevyužit. Řešením pro tento problém bylo vybudovat unifikovanou architekturu se skalárními jádry, kde jsou všechny shader výpočty prováděny pomocí stejného procesoru. (Rege, 2012) Koncem roku 2006 se na trh začaly dostávat první grafické karty (GeForce 8), které již využívaly unifikované shader jednotky. Srdcem GPU se stala sada multiprocesorů. Ty jsou do značné míry odlišné od klasických CPU. Běžné procesory jsou navržené s ohledem na minimální odezvu s nízkým objemem práce za čas (low latency low throughput processors). GPU jsou naopak navrženy pro maximální objem práce za čas i s horší odezvou (high latency high throughput processors). (Rege, 2012) Z toho vyplývají podstatné rozdíly mezi architekturou CPU a GPU (obrázek 1). CPU disponuje velkou cache pamětí a instrukční jednotkou, což umožňuje dobře optimalizovat vykonávání instrukcí. U GPU jde spíše o hrubou sílu. V rámci jednoho multiprocesoru je používána řídící jednotka pro několik ALU (nazývaných stream procesory) a velmi malá cache paměť, což téměř vylučuje jakékoliv optimalizace vykonávání instrukcí. Obr. 1: Porovnání architektury CPU a GPU zobrazující rozdíly v počtech aritmetických a řídících jednotek na procesor (Zdroj: CUDA Programming Guide)

2 ARCHITEKTURA GPU 15 Každý stream procesor v rámci multiprocesoru zpracovává totožný úkol (jako jedno vlákno) pouze s rozdílnými daty. Např. při zpracování obrazu každý stream procesor zpracovává v rámci svého vlákna hodnotu jednoho pixelu, což je velmi výhodné u moderních karet, které umožňují zpracování až 10 000 vláken paralelně. (Rege, 2012) V tabulce 1 jsou uvedeny počty stream procesorů na multiprocesor u GPU různých zaměření firmy Nvidia. Tab. 1: Počty multiprocesorů (MPU) a stream procesorů v grafických kartách firmy Nvidia (Zdroj: CUDA Programming Guide) Grafická karta MPU stream procesorů stream procesorů na MPU Quadro NVS 320M 4 32 8 Geforce 9800 GT 14 112 8 Quadro 6000 14 448 32 GeForce GTX 460 7 336 48

16 3 OPENCV 3 OpenCV Knihovna OpenCV, neboli Open Source Computer Vision Library, je C++ balíkem nástrojů a funkcí zaměřených do oblasti počítačového vidění (computer vision). Knihovna je dostupná jako open source a je využitelná pod systémy Windows, Linux a Mac OS. Knihovnu je možné využívat zvláště pro aplikace zpracovávající obraz v reálném čase pro účely rozpoznávání a analýzy obrazu a počítačovou interakci. 3.1 Instalace Instalační soubory nejnovější verze jsou k dispozici na oficiálních stránkách projektu na adrese http://opencv.willowgarage.com/wiki/. Instalace je rozsáhle popsána pro všechny podporované operační systémy v návodu OpenCV Installation Guide dostupném na adrese http://opencv.willowgarage.com/wiki/installguide. 3.1.1 Případová studie Jako testovací systém byla zvolena Linuxová distribuce Ubuntu 11.10 (32-bit, Kernel Linux 3.0.0-17-generic) s proprietárním ovladačem Nvidia verze 280.13 a překladačem gcc verze 4.6.1 v základní instalaci. Oficiální repositáře Ubuntu v době testování obsahovaly zastaralou verzi OpenCV která byla, vzhledem k zlepšující se podpoře HW akcelerace v novějších verzích, pro účely testování nevhodná. Proto byly použity repositáře spravované Gijsem Molenaarem. Ty obsahují instalační balíky OpenCV 2.3.1 pro Ubuntu 11.10, které byly použity pro testování. Instalace z uvedeného repositáře byla provedena pomocí příkazů uvedených v kódu 1. Balíky jsou backportem z repositáře Debian Unstable. Repositář je dostupný skrze webové stránky projektu Launchpad na adrese https: //launchpad.net/~gijzelaar/. Adresa repositáře je ppa:gijzelaar/cuda. Vzhledem k integraci HW akcelerace do nových verzí OpenCV je do instalace zahrnuta taktéž instalace Nvidia CUDA. Kód 1: Příkazy pro instalaci OpenCV 2.3.1 pod systémem Ubuntu 11.10 $ sudo add apt r e p o s i t o r y ppa : g i j z e l a a r / cuda $ sudo add apt r e p o s i t o r y ppa : g i j z e l a a r / opencv2. 3 $ sudo apt get update $ sudo apt get i n s t a l l l i b c v dev 3.1.2 Problémy případové studie Ubuntu 11.10 obsahuje v základní verzi knihovnu ffmpeg, která je nekompatibilní s OpenCV 2.3.1. Pro korektní funkčnost bylo třeba staré balíky ze systému odstranit a nahradit novějšími. Pro tento účel byly použity balíky spravované Jonem Severinssonem. Repositář s balíky je dostupný na launchpadu na

3.2 GPU akcelerace 17 adrese https://launchpad.net/~jon-severinsson/. Adresa repositáře je ppa: jon-severinsson/ffmpeg. 3.2 GPU akcelerace Knihovna OpenCV implementuje některé ze svých funkcí pro zpracování obrazu nejen v optimalizované CPU verzi. Od září 2010 dochází v rámci projektu k implementaci GPU alternativ klíčových funkcí na platformě Nvidia CUDA. (Bradski, Kaehler 2010) Příklad CPU a GPU volání funkce prahování je ukázán v kódu 2. Kód 2: Ukázka volání CPU a GPU akcelerované funkce knihovny OpenCV (funkce jsou uvedeny bez parametrů) cv : : t h r e s h o l d ( ) ; // Prahování prováděné na CPU cv : : gpu : : t h r e s h o l d ( ) ; // Prahování prováděné na GPU

18 4 NVIDIA CUDA 4 Nvidia CUDA CUDA, celým názvem Compute Unified Device Architecture, je architektura vyvinutá a propagovaná Kalifornskou společností Nvidia. Technologie byla představena v roce 2006 a vychází z idei využívat GPU nejen pro vykreslování obrazu, ale využít potenciálu jednostranně zaměřené paralelní výpočetní architektury pro výpočty obecné. V obecné rovině takovýto přístup nazýváme GPGPU (General-purpose computing on graphics processing units), tedy přístup, který má za cíl výpočetní úlohy dříve spouštěné na CPU provádět na GPU. Dříve byla architektura GPU navržená pouze pro rasterizaci a později pro specifické grafické operace (3D graphics pipeline), ale s ohledem na stále kvalitnější a náročnější grafické efekty byla postupně rozšířena o programovatelné pixel a shader jednotky. Ty, ač stále určené pro práci s obrazovými daty, se staly základem pro zpřístupnění obecných výpočtů na GPU. Moderní GPU jsou již automaticky navrhovány primárně pro výpočty obecné a klasické funkce jsou implementovány v rámci obecnější architektury. Tam, kde bylo dříve třeba samostatných pixel shaderů a vertex shaderů, je dnes k dispozici dané množství CUDA jader (stream procesorů), které provádí potřebné výpočty bez ohledu na typ operace. Skrze platformu CUDA je tak pro programátora zpřístupněn potenciál všech dostupných jader. 4.1 Požadavky pro práci s CUDA Vzhledem k těsnému propojení CUDA architektury s grafickými kartami Nvidia je prvním požadavkem pro spouštění jakéhokoliv kódu právě grafická karta zmíněné společnosti. Všechny grafické karty postavené na architektuře G80 a novější již architekturu CUDA podporují. Je však samozřejmé, že profesionální Quadro karta bude dosahovat poněkud jiných výsledků, než běžná spotřebitelská GeForce. Stejně jako grafické karty i samotná CUDA se vyvíjí a tak, ač je mezi verzemi udržována zpětná kompatibilita, vzhledem k množství nově přidávaných funkcí, není možné udržovat kompatibilitu dopřednou. Mezi další požadavky patří, jak pod Linuxovými, tak pod Windows systémy, korektně nainstalovaný ovladač a vývojářský balíček CUDA development toolkit, obojí volně dostupné z webových stránek společnosti Nvidia na adrese http:// developer.nvidia.com/cuda-downloads. Posledním požadavkem pro vývoj je běžný C/C++ překladač. Zvláště pod systémy Linux, ale samozřejmě i Windows, je možné využít překladačů gcc nebo g++. 4.2 Instalace Získání a instalace ovladače grafické karty pod operačními systémy Windows je záležitostí, se kterou se setkala většina pokročilých uživatelů. Ovladače jsou v podobě instalačních balíků pro konkrétní verzi systému dostupné na oficiálních stránkách firmy Nvidia na adrese http://www.nvidia.com. Instalace pod operačními systémy

4.3 Překlad 19 Linux, zvláště u rozšířenějších distribucí, již také probíhá bez větších obtíží. Ač je ovladač proprietárním softwarem a je tedy problémem je distribuovat přímo jako součást distribuce, po prvním spuštění grafického rozhraní updatovací systém nabídne, po odsouhlasení licence, automatické stažení ovladače z konkrétního repositáře. V případě problémů, nestandardní konfigurace či unikátní distribuce, je možné stáhnout ovladače přímo z webových stránek firmy Nvidia v připravených balících pro nejběžnější distribuce. Následuje instalace CUDA Toolkit a CUDA SDK. Pro nové vývojáře poskytuje firma Nvidia velmi rozsáhlou a propracovanou podporu, která zahrnuje podobný popis instalace potřebných součástí pod systémy Windows, Linux a Mac dostupnou na adrese http://developer.nvidia.com/nvidia-gpu-computing-documentation. Výhodou některých linuxových distribucí je možnost využit připravené repositáře a nainstalovat tak CUDU velmi pohodlně, nevýhodou bývá, že takovéto repositáře mnohdy obsahují starší verze, než poskytuje oficiální stránka Nvidie. 4.2.1 Případová studie Jako testovací systém byl použit notebook HP8710p. Tento notebook je vybavený grafickou kartou Nvidia Quadro 320M, která podporuje architekturu Nvidia CUDA ve verzi 1.1, disponuje výkonem 32 CUDA jader a obsahuje 512MB interní paměti. Jako operační systém byla použita Linuxová distribuce Ubuntu 11.10 (32-bit, Kernel Linux 3.0.0-17-generic) v základní instalaci s proprietárním ovladačem grafické karty Nvidia verze 280.13 a s nainstalovaným překladačem gcc verze 4.6.1. Pro instalaci CUDA Toolkit, jak ukazuje kód 3, byly použity repositáře spravované Gijsem Molenaarem. Repositář je dostupný skrze webové stránky projektu Launchpad na adrese https://launchpad.net/~gijzelaar. Konkrétní adresa repositáře je ppa:gijzelaar/cuda. Jedná se o backport z repositářů Debian Testing. Správce repositáře se snaží poskytovat veřejnosti zvláště balíky pro OpenCV verze 2.3 (oficiální repositáře OpenCV pod Ubuntu reagují na novinky v neesenciálních balících se zpožděním). Vzhledem k GPU akceleraci knihovny OpenCV skrze platormu CUDA taktéž udržuje kompatibilní balíky pro instalaci CUDA Toolkit, aby bylo možné provozovat knihovnu OpenCV s podporou HW akcelerace bez větších problémů. Kód 3: Příkazy pro instalaci platformy CUDA pod systémem Ubuntu 11.10 sudo add apt r e p o s i t o r y ppa : g i j z e l a a r / cuda sudo apt get update sudo apt get i n s t a l l n v i d i a cuda t o o l k i t 4.3 Překlad CUDA C je rozšířením jazyka C. Mezi podstatné novinky patří zavedení několika nových kvalifikátorů funkci. Ty odlišují, zda je kód určený pro GPU (device) a nebo

20 4 NVIDIA CUDA pro CPU (host). Dále jsou zavedeny i interní proměnné, které pomáhají v orientaci mezi vlákny na GPU a při rozložení zpracování GPU kódu do více dimenzí. Pro vytvoření funkčního GPU kernelu, tedy kódu, který se bude paralelně pouštět a provádět požadovaný výpočet, použijeme kvalifikátor global. Ten udává, že kód bude kompilován pro GPU. Volání takto vytvořeného kernelu v rámci ostatního kódu je podmíněno doplněním dvou parametrů. (Nvidia, 2012) Parametry jsou specifikovány v deklaraci zadané trojitými špičatými závorkami. Příklad deklarace a zavolání funkce kernelu je demonstrováno v kódu 4. Pokud vytváříme projekt v C/C++ a potřebujeme využívat platformu CUDA, je vhodné v projektu udržovat C/C++ a CUDA kód odděleně. Důvodem je nejenom větší přehlednost, ale zvláště potencionální problémy překladače nvcc s knihovnou STL. Na problémy lze narazit i při použití C++ knihovny OpenCV. 4.3.1 Oddělení C++ a CUDA kódu Kód CUDA kernelu je vhodné psát do samostatného souboru. Běžně je takovýto zdrojový soubor identifikován za pomocí přípony.cu. Zdrojový soubor obsahující kód CUDA kernelu by měl být díky takovémuto oddělení bez problémů přeložitelný samostatně pomocí překladače nvcc. Ukázka implementace v kódu 4 deklaruje funkci kernelu a exportuje vytvořenou funkci pomocí deklarace export pro volání kernelu z C/C++ projektu. Kód 4: Deklarace a export CUDA kernelu pro přehlednější volání z projektu #i n c l u d e k e r n e l c u d a. h // D e k l a r a c e f u n k c e CUDA k e r n e l u g l o b a l v o i d g p u k e r n e l ( u n s i g n e d char s r c d a t a, u n s i g n e d char d s t d a t a ){ // kód k e r n e l u } // Export f u n k c e pro C++ p r o j e k t e x t e r n C v o i d c u d a g p u k e r n e l ( u n s i g n e d char s r c d a t a, u n s i g n e d char d s t d a t a ){ g p u k e r n e l <<<1,1>>>( s r c d a t a, d s t d a t a ) ; } Odpovídající hlavičkový soubor, který demonstruje kód 5, obsahuje potřebné CUDA deklarace pro překlad kernelu a umožňuje volat exportovanou funkci kernelu z běžného C/C++ projektu. Kód 5: Hlavičkový soubor s exportovanou funkcí CUDA kernelu #i n c l u d e <cuda. h> #i n c l u d e <c u d a r u n t i m e a p i. h> e x t e r n C v o i d c u d a g p u k e r n e l ( ) ;

4.3 Překlad 21 V rámci samotného C/C++ projektu, jak demonstruje kód 6, je možné běžně používat libovolné C/C++ knihovny (např. OpenCV) a také, vzhledem k připojenému hlavičkovému souboru CUDA kódu, volat i CUDA funkce a vytvořený kernel. Zdrojový kód je překládán pomocí C/C++ překladače (zde g++). Demonstrované rozdělení připraví projekt do podoby, kde je možné zvlášť přeložit kernel pro GPU (za pomocí nvcc) a běžný C++ a OpenCV kód. Tím je možné předejít případným problémům s překladačem nvcc. Kód 6: Příklad OpenCV projektu, který zároveň využívá funkcí platformy CUDA #i n c l u d e k e r n e l c u d a. h // Knihovna OpenCV #i n c l u d e opencv2 / h i g h g u i / h i g h g u i. hpp i n t main ( i n t argc, char argv [ ] ) { // V o l á n í CUDA k e r n e l u c u d a g p u k e r n e l ( ) ; } 4.3.2 Překlad CUDA projektu Vzhledem k rozdělení projektu na části obsahující C++ kód, kód OpenCV a kód pro CUDA je třeba rozdělit i překlad na několik částí. Nejdříve je nutné přeložit kernel pro platformu CUDA. Kód je překládám pomocí překladače nvcc. Vzhledem k tomu, že se nejedná o spustitelný binární soubor, je třeba při překladu použít parametr -c. Ten zajistí, že dojde k překladu zdrojových souborů, jak demonstruje kód 7, ale nedojde k sestavení (linking) a vytvoření spustitelného souboru. Výstupní soubor bývá z důvodů rozpoznatelnosti zakončován příponou.o. Pro korektní překlad je třeba připojit (include) lokaci zdrojových souborů CUDA nainstalovaných spolu s balíkem CUDA Toolkit. Kód 7: Překlad CUDA kernelu pomocí překladače nvcc nvcc I / u s r / l o c a l / cuda / i n c l u d e c o $ (CUDAFILE ). o $ (CUDAFILE ). cu Pro překlad OpenCV kódu je možné použít C++ překladač g++. Taktéž, jak ukazuje kód 8, přeložíme projekt s parametrem -c, aby jsme dočasně zabránili překladači o sestavení binárního souboru. Vzhledem k tomu, že v programu jsou použity jak funkce knihovny OpenCV tak i funkce CUDA je třeba připojit (include) zdrojové soubory obou balíků. g++ Kód 8: Překlad části projektu, která obsahuje jak OpenCV tak CUDA kód I / u s r / l o c a l / cuda / i n c l u d e pkg c o n f i g opencv c f l a g s pkg c o n f i g opencv l i b s c o $ ( CPPFILE ). o $ ( CPPFILE ). cpp

22 4 NVIDIA CUDA Pro finální sestavení spustitelného souboru provedeme pomocí překladače g++. Ten složí, pomocí příkazu uvedeného v kódu 9, obě předpřeložené části do výsledného binárního souboru. Pro korektní funkčnost je opět třeba připojit potřebné zdrojové soubory a taktéž knihovny nutné pro spuštění. Kód 9: Spojení předpřeloženého kernelu a CUDA/OpenCV projektu do spustitelného souboru g++ o $ (OUT) $ ( CPPFILE ). o $ (CUDAFILE ). o pkg c o n f i g opencv c f l a g s pkg c o n f i g opencv l i b s L/ u s r / l o c a l / cuda / l i b l c u d a r t lm Výstupem všech popsaných kroků je binární spustitelný soubor s vytvořeným programem, který využívá jak OpenCV tak CUDA. 4.4 Bloky a vlákna Při spouštění CUDA kernelu jsou povinnou součástí volání parametry, které umožňují nastavit optimální rozdělení výpočtů mezi tzv. bloky a vlákna jak je zobrazeno na obrázku 2. Vlákno je samostatný proces spuštěný na jednom ze stream procesorů, který řeší zadaný výpočet. Každé vlákno je jednoznačně identifikováno pomocí interních proměnných. Tato identifikace má podobnou funkci jako iterátor v cyklu a zajišťuje, že je možné úlohu zpracovávat paralelně. Jednotlivá vlákna jsou sdružována do bloků. Důvodem pro tvoření bloků je nutnost obejít omezení spočívající v přítomnosti pouze jedné řídící jednotky na více stream procesorů. Jeden blok je vždy přiřazen jednomu multiprocesoru, v rámci kterého všechny stream procesory vykonávají stejný výpočet nad různými daty. Špatné rozdělení (např. jedno vlákno na blok, tedy v rámci multiprocesoru zpracovává data pouze jeden stream procesor) sice nezpůsobí nefunkčnost napsaného programu, ale dojde k razantnímu zpomalení výpočtu. Vlastní bloky jsou organizovány do mřížky. Jak název napovídá, dle vstupních dat je možné jak vlákna tak i bloky uvažovat řazené nejen jednorozměrně, ale i rozdělené do více rozměrů, což usnadňuje zpracování dat, např. při zpracování matic. Každý blok má do interní paměti načtenou relevantní část dat ke zpracování, která je rychle přístupná (cache paměť daného multiprocesoru) ostatní data je nutné (pokud je třeba) načítat ze sdílené paměti GPU, což je výrazně pomalejší. Vlákna v rámci bloku mohou mezi sebou sdílet zpracovávaná data, bloky jsou však nezávislé. Optimální rozdělení na bloky a vlákna v rámci mřížky je jedním z klíčových bodů optimalizovaného GPU kódu a usnadňuje programátorovi orientaci a implementaci výpočetního algoritmu.

4.4 Bloky a vlákna 23 Obr. 2: Ilustrace 2D mřížky s rozdělením zpracování dat na bloky a vlákna pomocí v rámci implementace CUDA (Zdroj: Karel Zídek) 4.4.1 Dimenze mřížky, bloků a vláken Mřížka je základním prvkem v rámci kterého které jsou uspořádány bloky. Mřížka se nejčastěji využívá jako dvojrozměrné pole bloků. Rozměry mřížky zjistíme pomocí proměnné griddim s parametrem osy, pro kterou chceme údaj získat. Proměnná griddim.x tak obsahuje údaj o počtu bloků na ose x a proměnná griddim.y obsahuje údaj o počtu bloků na ose y. Informace o dimenzích a umístění bloku můžeme získat pomocí dvou dalších proměnných. Proměnná blockidx udává pozici v mřížce na parametrem zadané ose (blockidx.{x,y}). Proměnná blockdim udává počet vláken na osu v rámci bloku. Na rozdíl od bloků, které mohou být uspořádány pouze dvojrozměrně, je možné vlákna v rámci bloku uspořádat až ve třech dimenzích proto proměnná blockdim poskytuje, v případě takového rozdělení, údaje až o třech osách (blockdim.{x,y,z}). Bloky v mřížce je možné od verze CUDA 1.0 až 1.3 seřadit pouze do dvojrozměrného pole. Proměnná griddim poskytuje pouze informace o osách x a y. Od CUDA verze 2.x došlo k rozšíření tohoto limitu a bloky je možné v mřížce řadit i ve třech dimenzích. Maximální počet bloků, které je možné na každou z os přiřadit, je až do CUDA verze 3.0 hodnota 65535. Od verze 3.0 je možné na osu rozprostřít až 2 31 1 bloků. Pro identifikaci vlákna slouží proměnná threadidx, která udává pozici na parametrem zvolené ose v rámci bloku. Stejně jako v případě blockdim poskytuje proměnná informace o pozici pro všechny dostupné osy (threadidx.{x,y,z}). Rozložení vláken v bloku, které je udávané proměnnou blockdim je ve všech verzích CUDY možné třídit vlákna až do trojrozměrného bloku. Do verze 1.3 (včetně) je limit počtu

24 4 NVIDIA CUDA vláken na osu x a y 512. Od verze 2.0 je možné na obě osy přiřadit až 2014 vláken. Osa z má v bloku pevný limit 64 vláken. Při programování je však třeba hlídat limit počtu vláken na blok. Ten je, paralelně s hodnotou počtu vláken na osu, od verze 1.0 až po verzi 1.3 nastavený, z důvodů omezení hardwaru, na hodnotu 512. Od verze 2.0 je možné používat až 1024 vláken v bloku. Tento limit je třeba při rozdělování vláken do bloků hlídat. 4.4.2 Zpracování dat v jednorozměrné mřížce Využití jednorozměrné mřížky bloků je výhodné ke zpracování dat, která jsou uspořádaná v jednorozměrném poli, jak je vidět v kódu 10. Každý blok je rozdělen na 256 vláken seřazených pouze v jednom rozměru, což je dané datovým typem proměnné threadsperblock. Taktéž bloky jsou pomocí datového typu proměnné blockspergrid řazeny pouze v jednom rozměru. Kód 10: Rozdělení zpracování dat do jednorozměrné mřížky bloků a vláken // N a s t a v e n í 256 v l á k e n na b l o k s t a t i c i n t t h r e a d s P e r B l o c k = 256; // R o z d ě l e n í zpracovávaných dat do 1D bloků po 256 v l á k n e c h s přesahem i n t b l o c k s P e r G r i d = ( d a t a S i z e+t h r e a d s P e r B l o c k 1) / t h r e a d s P e r B l o c k ; // S p u š t ě n í GPU k e r n e l u se z v o l e n ý m i parametry g p u k e r n e l <<< b l o c k s P e r G r i d, t h r e a d s P e r B l o c k >>> ( ) ; Příklad v kódu 11, demonstruje, jak vypadá využití interních proměnných pro výpočet korektního indexu vlákna při paralelním zpracování dat, pokud má mřížka bloků a blok vláken pouze jeden rozměr. Kód 11: Příklad využití interních proměnných pro identifikaci indexu vlákna g l o b a l v o i d g p u k e r n e l ( i n t v e l i k o s t d a t, u n s i g n e d char data ){ // P o z i c e == č í s l o v l á k n a + ( č í s l o bloku rozměr bloku ) i n t p o z i c e v p o l i = t h r e a d I d x. x + b l o c k I d x. x blockdim. x ; i f ( p o z i c e v p o l i <v e l i k o s t d a t ){ / z p r a c o v á n í dat / } } Každý blok, jak ukazuje obrázek 3, má přidělenou poměrnou část pole dat, kterou paralelně v rámci vláken zpracovává. Identifikátor bloku blockidx udává o který blok se jedná v rámci mřížky, identifikátor blockdim udává dimenzi bloku (v případě jednorozměrného pole se jedná přímo o počet vláken) a identifikátor vlákna threadidx vrací číslo vlákna v rámci konkrétního bloku. Pokud budeme předpokládat, že každé vlákno zpracovává jednu buňku vstupního pole dat jem možné z těchto identifikátorů v každém vlákně získat výpočtem threadidx.x + blockidx.x blockdim.x unikátní číslo pozice, které koresponduje s buňkou pole a tak zaručit, že se pole zpracuje celé.

4.4 Bloky a vlákna 25 Obr. 3: Zpracování pole dat a hodnoty interních proměnných při jednorozměrném řazení bloků a vláken (Zdroj: Karel Zídek) 4.4.3 Zpracování dat v dvojrozměrné mřížce Deklarace vícerozměrné mřížky nebo bloku je zadávána pomocí datového typu dim3 jak je demonstrováno v kódu 12. Ten umožňuje pomocí počtu parametrů nadefinovat až trojrozměrnou proměnnou, která se předává kernelu a určuje rozdělení bloků a vláken při zpracovávání dat na GPU. Kód 12: Deklarace dvojrozměrné mřížky a trojrozměrného bloku vláken // 3D r o z d ě l e n í v l á k e n do k r y c h l e dim3 ThreadsPerBlock ( 5, 2, 2 ) ; // 2D r o z d ě l e n í bloků do mřížky dim3 B l o c k s P e r G r i d ( 1 6, 1 6 ) ; Zpracování dat ve dvojrozměrné mřížce, jak ukazuje kód 13, je velmi výhodné ke zpracování obrazových dat vzhledem k usnadněné zjištění indexu jednotlivých pixelů. Obraz, který chceme na GPU upravit, je třeba rozdělit v dvojrozměrné mřížce do bloků tak, aby ideálně na výpočet jednoho pixelu vycházelo jedno vlákno a minimální počet vlákenv bloku dosahoval doporučených hodnot pro optimální výkon. Doporučený počet je min. 32 vláken na blok. (Sanders, Kandrot, 2011)

26 4 NVIDIA CUDA Kód 13: Příklad kernelu, který rozděluje zpracování do 2D mřížky bloku po 512 vláknech // 2D r o z d ě l e n í 512 v l á k e n na b l o k dim3 ThreadsPerBlock ( 1 6, 1 6 ) ; // Mřížka 16 x16 bloků dim3 B l o c k s P e r G r i d ( 1 6, 1 6 ) ; g p u k e r n e l <<< B l o c k s P e r G r i d, ThreadsPerBlock >>> ( ) ; Deklaraci a rozdělení je možné spočítat při volání funkce kernelu podle rozměrů aktuálního vstupního souboru jak ukazuje kód 14. V demonstrovaném případě bude mít každý pixel přiděleno své vlastní vlákno. Kód 14: Příklad výpočtu pozice v kernelu, který zpracovává data ve dvojrozměrné mřížce g l o b a l v o i d g p u k e r n e l ( i n t v e l i k o s t d a t, u n s i g n e d char data ){ // Výpočet p o z i c e v 2D p o l i bloku a v l á k e n // P o z i c e na ose x // x p o z i c e v l á k n a v bloku + ( x rozměr bloku x č í s l o bloku ) i n t c o l x = t h r e a d I d x. x + b l o c k I d x. x blockdim. x ; // P o z i c e na ose y // y p o z i c e v l á k n a v bloku + ( y rozměr bloku y č í s l o bloku ) i n t rowy = t h r e a d I d x. y + b l o c k I d x. y blockdim. y ; // Výpočet i n d e x u a k t u á l n í h o v l á k n a i n t n = c o l x + rowy blockdim. x griddim. x ; } i f ( n<v e l i k o s t d a t ) { // z p r a c o v á n í dat } Bloky jsou seřazeny do dvojrozměrné mřížky. Pozici na ose x je možné získat pomocí výpočtu threadidx.x + blockidx.x blockdim.x. Tedy přičtením čísla vlákna v bloku (threadidx.x) k počtu vláken v blocích před aktuálním blokem na zvolené ose (blockidx.x blockdim.x). Stejným způsobem je možné spočítat pozici na ose y. K určení konkrétního indexu samotného vlákna je třeba využít oba předcházející výpočty. Ve výpočtu n = pozice x + pozice y blockdim.x griddim.x je třeba sečíst aktuální pozici na ose x (pozice x) s vypočtenou hodnotou všech buněk, které se nacházejí nad aktuálním řádkem. Hodnota vznikne vynásobením počtu řádků (pozice y) s počtem bloků na ose x (griddim.x) a s počtem vláken v bloku (blockdim.x).

5 OPENCL 27 5 OpenCL OpenCL, celým názvem Open Computing Language, je standardizované rozšířením jazyků C a C++ o datové typy, datové struktury a funkce (Scarpino, 2012), které usnadňuje paralelní programování nejenom na GPU, ale i na běžných CPU a dalších zařízeních. Standard vznikl jako snaha firmy Apple o vytvoření jednotného rozhraní pro programování hardware od svých dodavatelů. Do vývoje OpenCL se zapojily firmy Nvidia, AMD, Intel a IBM a v průběhu roku 2008 vznikl první návrh OpenCL. Pro zkvalitnění vývoje byla vytvořena pracovní skupina zabývající se OpenCL a zařazena mezi projekty v rámci tzv. Khronos Group (konsorcia firem, které se snaží o pokrok v oblasti grafiky). Výsledkem práce skupiny byla na konci téhož roku uveřejněná specifikace OpenCL 1.0. Od té doby byla specifikace vylepšena v roce 2010, kdy byla zveřejněna specifikace OpenCL 1.1, a v roce 2011 se zveřejněním specifikace OpenCL 1.2. Zveřejnění specifikace OpenCL 2.0 se očekává nejdříve v roce 2012. Podpora OpenCL u hlavních výrobců grafických karet, jakožto nejvhodnějšího hardware pro GPGPU, se stala jednou z priorit. V roce 2009 firma Nvidia zveřejnila SDK, které zpřístupnilo podporu OpenCL na svých grafických kartách. Firma AMD (dříve ATI) vydala svoje SDK ve stejném roce pouze o několik měsíců později. Firma Apple taktéž implementovala podporu OpenCL v rámci operačního systému Mac OS 10.6 (Snow Leopard). 5.1 Požadavky pro práci s OpenCL Stejně jako v případně platformy CUDA je pro vývoj v OpenCL vhodné vlastnit grafickou kartu s podporou OpenCL. Narozdíl od platformy CUDA však není hardware pro provoz OpenCL omezen pouze na grafické karty. Podporu OpenCL je tak možné najít jak v GPU od Nvidie (řady GeForce, Quadro, Tesla) a AMD (řady Radeon, Fusion, FirePro), tak i v moderních procesorech Intel, AMD a serverech IBM Power a BladeCenter. Mezi další požadavky patří korektní instalace ovladače, který pro zvolený hardware zpřístupňuje podporu OpenCL a odpovídající SDK dodané poskytovatelem hardwaru. Posledním požadavkem pro vývoj je běžný C/C++ překladač. Zvláště pod systémy Linux ale samozřejmě i Windows je možné využít překladače g++. 5.2 Instalace Instalace je specifická pro konkrétní hardware. V případě grafických karet Nvidia je podpora zahrnuta v CUDA Toolkit (viz. kapitola Instalace CUDA). Pro grafické karty AMD je dostupné tzv. AMD APP SDK, který je k dispozici ke stažení na adrese http://developer.amd.com/sdks/amdappsdk/ downloads/pages/default.aspx. AMD taktéž poskytuje podporu pro instalaci

28 5 OPENCL a rozsáhlou dokumentaci na adrese http://developer.amd.com/sdks/amdappsdk/ documentation/pages/default.aspx. Grafické karty firmy Intel mají SDK a stránky s podporou dostupné na adrese http://software.intel.com/en-us/articles/vcsource-tools-opencl-sdk/. 5.2.1 Případová studie Testovací systém a jeho parametry jsou totožné se systémem uvedeným v případové studii týkající se podpory Nvidia CUDA popsané v rámci kapitoly o instalaci Nvidia CUDA. V instalaci zmíněná grafická karta Nvidia Quadro 320M podporuje OpenCL ve verzi 1.0. Vzhledem k podpoře OpenCL v rámci CUDA Toolkit proběhla instalace potřebných balíků a knihoven pro korektní práci s OpenCL v rámci instalace potřebných součástí pro Nvidia CUDA. Pro instalaci OpenCL byly použity, stejně jako pří instalaci CUDA Toolkit, repositáře spravované Gijsem Molenaarem dostupné skrze webové stránky projektu Launchpad na adrese https://launchpad. net/~gijzelaar. Podrobný postup je popsán v kódu 3. 5.2.2 Oddělení C++ a OpenCL kódu Podobě jako v případě CUDY je možné psát kód OpenCL kernel v samostatném souboru. Vzhledem k odlišnému přístupu ke kompilaci kernelu (CUDA se kompiluje při vývoji pomocí překladače nvcc, OpenCL se kompiluje pro konkrétní zařízení i za běhu aplikace pomocí určené funkce) je možné se setkat s rozdílnými přístupy k uložení OpenCL kernelu. V případě uložení v samostatném zdrojovém je takovýto zdrojový soubor běžně identifikován za pomocí přípony.cl. U malých kernelů se můžeme setkat s případy, kdy je kód kernelu uložen jako textový řetězec či případně pole řetězců v rámci hlavního kódu. Ač má takovýto způsob svoje výhody (není třeba zdrojový kód načítat) zvolený způsob uložení zhoršuje jeho úpravy a debugování. Ukázková implementace v kódu 15 demonstruje, jak může vypadat jednoduchá funkce OpenCL kernelu. Při porovnání s kódem kernelu v CUDA (kód 4) zjistíme, že oba přístupy jsou velmi podobné. } Kód 15: Příklad implementace OpenCL kernelu v samostatném souboru k e r n e l v o i d g p u k e r n e l ( g l o b a l u n s i g n e d char s r c d a t a, g l o b a l u n s i g n e d char d s t d a t a ){ // kód k e r n e l u 5.2.3 Překlad OpenCL programu Překlad OpenCL projektu probíhá pomocí standardního C++ překladače. V příkladu kódu 16 je použit překladač g++. Vzhledem k tomu, že projekt využívá knihovnu OpenCV, je jediný relevantní parametr překladače pro úspěšné přeložení

5.3 Rozdělení zpracování dat 29 OpenCL parametr -lopencl. Samotný překlad se oproti překladu projektu napsaném v CUDA může zdát jednodušší. Demonstrovaný překlad však provede pouze překlad programu samotného, překlad OpenCL kernelu je nutné řešit jiným způsobem. Kód 16: Překlad OpenCL projektu spolu s knihovnu OpenCV g++ $ ( CPPFILE ). cpp o $ (OUT) pkg c o n f i g opencv c f l a g s pkg c o n f i g opencv l i b s lopencl 5.2.4 Překlad OpenCL kernelu Narozdíl od CUDY, která je limitovaná pouze na hardware firmy Nvidia (a tedy je jednodušší překládat kernel pro konkrétní zařízení), se OpenCL potýká s množstvím hardwarových platforem. Kernel se buď přímo přeloží pro konkrétní hardware (v případě, že víme, kde bude program spouštěn), nebo se kernel přeloží pro konkrétní uživatelskou platformu při prvním spuštěním programu. V kódu 17 je předvedeno vytvoření programu pro překlad z textového řetězce obsahujícího zdrojový kód OpenCL kernelu (proměnná m openclkernelsource) za pomoci funkce clcreateprogramwithsource(). Takto vytvořený program je přeložen za pomocí funkce clbuildprogram(). Kód 17: Překlad OpenCL kernelu za běhu OpenCL aplikace // Z í s k á n í z d r o j o v é h o kódu OpenCL k e r n e l u z t e x t o v é h o ř e t ě z c e c o n s t char k e r n e l S o u r c e = m openclkernelsource. c s t r ( ) ; // V y t v o ř e n í OpenCL programu c l p r o g r a m o p e n c l p r o g r a m = clcreateprogramwithsource ( context, 1, ( c o n s t char )& k e r n e l S o u r c e, 0,& e r r o r ) ; // P ř e k l a d OpenCL programu e r r o r = c l B u i l d P r o g r a m ( opencl program, 0, 0, 0, 0, 0 ) ; 5.3 Rozdělení zpracování dat Podobně jako na platformě CUDA můžeme rozdělovat, pro usnadnění algoritmizace uvnitř kernelu, zpracování dat do více dimenzí. Stejné možnosti nastavení lze nalézt i při použití platofrmy OpenCL. Při porovnání s CUDA je možné uvažovat, že lokální rozdělení do skupin (group) pomocí proměnné localworksize je podobné rozdělení do bloků. Stejně tak globální rozdělení pomocí proměnné globalworksize se podobá rozdělení v mřížce. Jednorozměrné rozdělení zpracování dat je demonstrováno v kódu 18.

30 5 OPENCL Kód 18: Nastavení zpracování dat do jednorozměrné mřížky bloků a vláken // N a s t a v e n í jednorozměrného z p r a c o v á n í dat i n t work dim = 1 ; s i z e t g l o b a l W o r k S i z e [ 1 ] = { m sourceimagesize } ; s i z e t l o c a l W o r k S i z e [ 1 ] = { 1 } ; // S p u š t ě n í k e r n e l u e r r o r = clenqueuendrangekernel ( commandqueue, o p e n c l g p u k e r n e l, work dim, 0, globalworksize, l o c a l W o r k S i z e, 0, 0, 0 ) ; V případě jednorozměrného kernelu, jak je vidět v kódu 19, je možné získat aktuální index vlákna zpracovávajícího vstupní data pomocí interní funkce get global id(0). Parametrem funkce je číselná hodnota, která udává dimenzi, pro kterou hodnotu zjišťujeme. Funkce get global id(0) tedy vrací globální index na ose x rozdělené na vlákna. Kód 19: Příklad využití interních funkcí kernelu pro identifikaci indexu vlákna // D e k l a r a c e OpenCL k e r n e l u k e r n e l v o i d g p u k e r n e l ( i n t d a t a s i z e, g l o b a l u n s i g n e d char s r c d a t a, g l o b a l u n s i g n e d char d s t d a t a, ) { // I d e n t i f i k a c e p o z i c e v paměti i n t n = g e t g l o b a l i d ( 0 ) ; // K o n t r o l a mezí i f ( n < d a t a s i z e ) { / z p r a c o v á n í dat / } } ; Pro případ rozdělení na dvojrozměrné zpracování, jak ukazuje kód 20, je nejprve třeba deklarovat rozdělení vláken ve skupině pomocí proměnné localworksize do čtverce (zde 16 16). Globální rozdělení využívá jako parametrů proměnné řádků (rows) a sloupců (columns) vstupních dat. Ty jsou zaokrouhleny funkcí RoundUp() (uvedené v kódu 21) a slouží k prevenci chyby, která by vznikla v případě, že by vstupní data nebyla dělitelná příslušnou velikostí hrany skupiny. Kód 20: Inicializace a dvojrozměrné rozložení vláken při spouštění kernelu OpenCL i n t work dim = 2 ; // 2D r o z l o ž e n í // L o k á l n í r o z l o ž e n í na 16 x16 v l á k e n s i z e t l o c a l W o r k S i z e [ 2 ] = { 16, 16 } ; // G l o b á l n í r o z l o ž e n í p o č í t a n ý c h dat do dvou d i m e n z í s i z e t g l o b a l W o r k S i z e [ 2 ] = { RoundUp (16, columns ), RoundUp (16, rows ) } ; // S p u š t ě n í k e r n e l u se zvoleným r o z l o ž e n í m z p r a c o v á n í dat e r r o r = clenqueuendrangekernel ( / parametry / ) ;

5.3 Rozdělení zpracování dat 31 Funkce RoundUp() uvedená v kódu 21 slouží k zaokrouhlení zpracovávaných dat i pro jiné vstupní rozměry, než ty, které jsou dělitelné rozměrem hrany skupiny. (Munshi a kol., 2012) Kód 21: Funkce RoundUp() pro zaokrouhlení dat pro dvojrozměrné rozložení vláken OpenCL kernelu (Zdroj: OpenCL programming guide) i n t RoundUp ( i n t groupsize, i n t g l o b a l S i z e ){ i n t r = g l o b a l S i z e % g r o u p S i z e ; i f ( r == 0) r e t u r n g l o b a l S i z e ; e l s e r e t u r n g l o b a l S i z e + g r o u p S i z e r ; } Pro identifikaci pozice v dvojrozměrném rozložení zpracování dat, jak ukazuje kód 22, je možné využít podobných funkcí, jako na platformě CUDA. Pozici na ose x můžeme získat pomocí výpočtu get local id(0) + (get group id(0) get local size(0)). Tedy přičtením lokální pozice ve skupině k pozici začátku skupiny, vypočítané vynásobením velikostí lokální skupiny (funkce get local size(0)), a pozice lokální skupiny na zvolené ose (funkce get group id(0)). Obdobným způsobem je možné vypočítat pozice na ostatních osách. Hodnota konkrétního indexu vznikne přičtením pozice na ose x k vypočtené hodnotě všech buněk, které se nacházejí nad aktuálním řádkem (pozice x + pozice y get num groups(0) get local size(0)). Kód 22: Příklad využití interních funkcí kernelu pro identifikaci indexu vlákna při rozložená zpracování dat do dvou dimenzí // D e k l a r a c e OpenCL GPU k e r n e l u k e r n e l v o i d g p u k e r n e l ( i n t d a t a s i z e, g l o b a l u n s i g n e d char s r c d a t a, g l o b a l u n s i g n e d char d s t d a t a ){ } ; // Výpočet p o z i c e v 2D p o l i s k u p i n a v l á k e n // P o z i c e na ose x // x p o z i c e v l á k n a ve s k u p i n ě + ( x rozměr s k u p i n y x č í s l o s k u p i n y ) i n t c o l x = g e t l o c a l i d ( 0 ) + g e t g r o u p i d ( 0 ) g e t l o c a l s i z e ( 0 ) ; // P o z i c e na ose y // y p o z i c e v l á k n a ve s k u p i n ě + ( y rozměr s k u p i n y y č í s l o s k u p i n y ) i n t rowy = g e t l o c a l i d ( 1 ) + g e t g r o u p i d ( 1 ) g e t l o c a l s i z e ( 1 ) ; // Výpočet i n d e x u a k t u á l n í h o v l á k n a i n t n = c o l x + rowy g e t l o c a l s i z e (0) get num groups ( 0 ) ; // K o n t r o l a mezí i f ( n < d a t a s i z e ) { / z p r a c o v á n í dat / }

32 6 POROVNÁNÍ IMPLEMENTACÍ 6 Porovnání implementací Pro porovnání důležitých aspektů jednotlivých implementací byl kód jednotlivých příkladů implementován v rámci třídy. Tento přístup nemusí, s ohledem na kód příkladů, vždy respektovat nejlepší programátorské zvyklosti, ale umožňuje celý kód rozdělit do soukromých metod, které jsou svým obsahem velmi jednoduše porovnatelné. Takovéto rozdělení je pro čtenáře přínosem a umožňuje jednodušší orientaci v problematice. Obecný návrh třídy, společný pro všechny příklady, využívá pro práci s obrazovými daty knihovnu OpenCV. Ta poskytuje pohodlné nástroje pro načítání a zpracování obrazových dat. Příklady jsou koncipovány tak, aby umožnily snadné proniknutí do popisované problematiky a ukázaly všechny důležité kroky, které vedou k funkčnímu řešení. Z tohoto důvodu jsou v některých místech záměrně vynechány možnosti kontroly potenciálních chyb. Zvláště v implementacích CUDA a OpenCL každá z prováděných funkcí nějakým způsobem informuje programátora o úspěšnosti svého provedení. Ve vážně brané implementaci je proto doporučeno těchto informací využít a důsledně všechny místa vzniku potenciálních chyb kontrolovat. Výsledný kód s implementací, která kontroluje většinu potenciálních chyb by bohužel v případě následujících demonstrací pouze znepřehledňoval podstatu problematiky. 6.1 Rozdílné implementace prahování Prahování je zcela základní operací, kterou lze provádět s libovolnou maticí obrazových bodů. Jednoduchost této operace umožňuje srovnat množství přístupů, aniž by došlo k vlivu na porozumění problematiky složitostí algoritmu samotného. Prahování taktéž umožňuje porovnat rozdílnost v rychlosti zpracování jednotlivých implementací bez nutnosti přizpůsobování algoritmu pro specifika každé z porovnávaných platforem. Obr. 4: Algoritmus prahování vytvoří obrázek využívající dvě barvy (Zdroj: Karel Zídek)

6.1 Rozdílné implementace prahování 33 Princip prahování (obrázek 4) spočívá v jednoduchém rozhodování nad daty vstupního obrazu. Vstupní šedý obrázek má jednotlivé pixely uspořádané v poli hodnot typu unsigned char. Pro samotnou operaci je stanovena mezní hranice (v případě příkladů jde o hodnotu 64). Pixely s hodnotou větší než stanovená hranice jsou nastaveny na maximální hodnotu, tedy na hodnotu bílé barvy, pixely hodnoty menší nebo rovné stanovené hranici získají hodnotu černé barvy. Výsledný obraz je tak zbaven šedé škály a je zobrazen pouze za pomoci dvou tónu. Ačkoliv se tato operace může zdát neznalému oku bezvýznamná, jde o velmi důležitý algoritmus, který se používá zvláště k zjednodušení vstupních obrazových dat pro další analýzu. Praktické uplatnění tak nalezneme například při detekci markerů v projektech týkajících se rozšířené reality. 6.1.1 CPU Implementace prahování pouze za použití CPU demonstruje zcela běžný přístup k řešení této problematiky bez jakékoliv optimalizace. V rámci obecné třídy, použité pro načtení a zobrazení obrazových dat i u ostatních implementací, je nad maticí bodů provedeno prahování. Postup spočívá v projítí všech bodů obrazu ve dvou cyklech a provedení rozhodování u každého konkrétního bodu. Jak je demonstrováno v kódu 23 cyklus zpracovává načtená obrazová data (proměnná m sourceimage datového typu cv::mat). Implementace datového typu cv::mat knihovny OpenCV umožňuje jednoduše přistupovat k hodnotám jednotlivých pixelů za pomoci metody at(). Obraz je modifikován dle mezní hodnoty stanovené proměnnou m threshold. Obrazová data není třeba, jak je běžnější u GPU implementací, ukládat do druhé matice, ale je možné modifikovat přímo vstupní matici. Kód 23: CPU implementace prahování s využitím knihovny OpenCV f o r ( i n t i =0; i <m sourceimage. rows ; i ++) f o r ( i n t j =0; j <m sourceimage. c o l s ; j ++) { i f ( m sourceimage. at<uchar >( i, j ) > m t h r e s h o l d ) m sourceimage. at<uchar >( i, j )=255; e l s e m sourceimage. at<uchar >( i, j )=0; } 6.1.2 OpenCV CPU Knihovna OpenCV je knihovnou určenou pro zpracování obrazových dat a je tedy velmi dobře optimalizovaná. Prahování je v rámci knihovny dostupné jako samostatná funkce. Implementace v rámci demonstrační třídy je totožná s příkladem běžného CPU algoritmu. Kód, kde na CPU cyklicky procházíme jednotlivé obrazové body, je pouze nahrazen funkcí cv::threshold(). Funkce demonstrovaná v kódu 24 již potřebuje, vzhledem k větší obecnosti, jako parametr výstupní matici bodů

34 6 POROVNÁNÍ IMPLEMENTACÍ (proměnná m array dst). Zbylé dva parametry jsou podrobně popsány v dokumentaci knihovny OpenCV dostupné na adrese http://opencv.willowgarage.com/ documentation/. První z nich stanovuje maximální hodnotu pro pixel a druhý konkretizuje typ prahování. Kód 24: CPU implementace prahování pomocí funkce knihovny OpenCV cv : : t h r e s h o l d ( m sourceimage, m a r r a y d s t, m t h r e s h o l d, 2 5 5. 0, CV THRESH BINARY ) ; 6.1.3 OpenCV GPU Jak bylo zmíněno v kapitole o OpenCV, knihovna ve shodě se současným trendem převádění CPU výpočtů na grafické karty nabídla ve své nové verzi možnost využívat ekvivalenty optimalizovaných CPU funkcí implementované s pomocí akcelerace na GPU (platforma Nvidia CUDA). GPU verze funkce prahování je na první pohled parametricky totožná se svým CPU ekvivalentem jak ukazuje kód 25. Kód 25: GPU implementace prahování pomocí funkce knihovny OpenCV copydatatogpu ( ) ; cv : : gpu : : t h r e s h o l d ( m g p u a r r a y s r c, m g p u a r r a y d s t, m t h r e s h o l d, 2 5 5. 0, CV THRESH BINARY ) ; getdatafromgpu ( ) ; Podstatným rozdílem oproti CPU verzi je nutnost nejdříve obrazová data nahrát do grafické karty a po provedení algoritmu data opět okopírovat do hostitelského zařízení. Tento postup je klíčový i pro ostatní GPU implementace, a proto je i u tohoto jednoduchého příkladu zobrazen pomocí metod copydatatogpu() a getdatafromgpu(). Tyto změny jsou zohledněny u parametrů funkce prahování. Proměnné m gpu array src a m gpu array dst jsou deklarované jako datový typ cv::gpu::gpumat. Způsob práce s GPU pamětí v implementaci knihovny OpenCV je demonstrován v kódu 26.

6.1 Rozdílné implementace prahování 35 Kód 26: Metody copydatatogpu() a getdatafromgpu() OpenCV GPU implementace // Nahrání obrazových dat do paměti GPU v o i d CvThreshold : : copydatatogpu ( ) { m g p u a r r a y s r c. upload ( m sourceimage ) ; } // Z í s k á n í obrazových dat z paměti GPU v o i d CvThreshold : : getdatafromgpu ( ) { m sourceimage = m g p u a r r a y d s t ; } 6.1.4 CUDA Implementace na platformě CUDA je o něco složitější, než předchozí příklady. Z tohoto důvodu, a taktéž pro lepší porovnání rozdílnosti s implementací v OpenCL, je možné si v kódu 27 prohlédnout celý hlavičkový soubor demonstrační třídy. Třída při vytváření přebírá parametry cesty souboru a hodnotu, která bude použita na prahování. Nejdůležitější kód celé třídy je obsažen v metodě runkernel(). V jejímž rámci se provádí spuštění samotného prahování na GPU. Jelikož CUDA kernel je kompilován společně se zbytkem projektu, je kernel připojen direktivou #include kernel cuda.h k projektu pomocí vlastního hlavičkového souboru. Kód 27: Hlavičkový soubor implementace třídy pro prahování pomocí platformy CUDA #i f n d e f CUDATH #d e f i n e CUDATH #i n c l u d e k e r n e l c u d a. h #i n c l u d e <i o s t r e a m > #i n c l u d e opencv2 / h i g h g u i / h i g h g u i. hpp c l a s s CudaThreshold { p r i v a t e : cv : : Mat m sourceimage ; i n t m s ourceimagesize ; u n s i g n e d char m g p u a r r a y s r c ; u n s i g n e d char m g p u a r r a y d s t ; b o o l m s h o w D e t a i l I n f o ; i n t m t h r e s h o l d ; p r i v a t e : v o i d copydatatogpu ( ) ; // Nahrání obrazových dat do paměti GPU v o i d getdatafromgpu ( ) ; // Z í s k á n í obrazových dat z paměti GPU p u b l i c : CudaThreshold ( c o n s t s t d : : s t r i n g imagepath, c o n s t i n t t h r e s h o l d, b o o l s h o w D e t a i l I n f o ) ; v o i d r u n K e r n e l ( ) ; v o i d showresult ( ) ; } ; #e n d i f

36 6 POROVNÁNÍ IMPLEMENTACÍ V rámci kernelu (kód 28) je nejdříve spočítána pozice v poli vstupních dat pro konkrétní vlákno za pomoci interních proměnných. Jde o proměnné pro identifikaci bloku (blockidx.x), rozměru bloku (blockdim.x) a identifikaci vlákna (threadidx.x). Výsledná proměnná n udává konkrétní pozici v poli vstupních dat src data. U proměnné n je následně zkontrolováno, zda bude odkazovat na konkrétní data ve vstupním poli, tedy zda hodnota nepřekročí maximální možný index pro vstupní pole dat (hodnota rows cols). Poté je provedeno prahování a výsledek algoritmu uložen do výstupního pole dat dst data. Kód 28: Implementace CUDA kernelu řešícího prahování #i n c l u d e k e r n e l c u d a. h // D e k l a r a c e CUDA k e r n e l u g l o b a l v o i d g p u k e r n e l ( i n t rows, i n t c o l s, u n s i g n e d char s r c d a t a, u n s i g n e d char d s t d a t a, i n t t h r e s h o l d ){ // I d e n t i f i k a c e p o z i c e v paměti i n t n = b l o c k I d x. x blockdim. x + t h r e a d I d x. x ; // K o n t r o l a mezí i f ( n > c o l s rows ) r e t u r n ; } // Prahování i f ( s r c d a t a [ n]> t h r e s h o l d ) d s t d a t a [ n ] = 255; e l s e d s t d a t a [ n ] = 0 ; Kernel je v kódu 29 spouštěn s parametry blockspergrid a threadsperblock. Ty udávají, jakým způsobem budou zpracovávaná data organizovaná do bloků a vláken. Pro jednoduchost je hodnota threadsperblock, udávající počet vláken na blok, stanovena na 256 vláken. Vstupní data jsou v proměnné threadsperblock rozpočítána tak, aby počet bloků pokryl veškerá vstupní data. Rozložení zpracovávaných dat na GPU je tak pouze jednorozměrné. Přičítání hodnoty threadsperblock snížené o jedna při výpočtu bloků zajistí, že dojde k výpočtu nad celým obrázkem, ač v některých blocích může být většina vláken spuštěna s indexem mimo rozsah obrazových dat. Z tohoto důvodu je v kernelu kontrolováno, zda index pro konkrétní vlákno nepřekračuje velikost dat.

6.1 Rozdílné implementace prahování 37 Kód 29: Inicializace CUDA kernelu s rozdělením na vlákna a bloky // Export f u n k c e CUDA k e r n e l u e x t e r n C v o i d c u d a g p u k e r n e l ( i n t rows, i n t c o l s, u n s i g n e d char s r c d a t a, u n s i g n e d char d s t d a t a, i n t t h r e s h o l d ){ // N a s t a v e n í 256 v l á k e n na b l o k s t a t i c i n t t h r e a d s P e r B l o c k = 256; // R o z d ě l e n í zpracovávaných dat do bloků po 256 v l á k n e c h s přesahem i n t b l o c k s P e r G r i d = ( ( rows c o l s )+ t h r e a d s P e r B l o c k 1) / t h r e a d s P e r B l o c k ; // S p u š t ě n í GPU k e r n e l u se z v o l e n ý m i parametry g p u k e r n e l <<< b l o c k s P e r G r i d, t h r e a d s P e r B l o c k >>> ( rows, c o l s, s r c d a t a, d s t d a t a, t h r e s h o l d ) ; } Při spouštění CUDA kódu je třeba nejprve nalézt vhodné zařízení. V kódu 30 nejdříve dojde k detekci grafické karty s podporou CUDA. Poté je zavolána metoda copydatatogpu(), která se postará o nakopírování obrazových dat do GPU pro zpracování kernelem. Následně dojde ke spuštění funkce samotného kernelu. Po provedení kernelu je třeba pomocí funkce cudathreadsynchronize() počkat na doběhnutí všech spuštěných vláken. Nakonec můžeme metodou getdatafromgpu() získat zpracovaná data z grafické karty. Kód 30: Metoda runkernel() CUDA implementace, která zajišťuje spuštění kernelu v o i d CudaThreshold : : r u n K e r n e l ( ) { // Detekce CUDA z a ř í z e n í cudadeviceprop prop ; i n t count ; cudagetdevicecount ( &count ) ; f o r ( i n t i =0; i < count ; i ++){ c u d a G e t D e v i c e P r o p e r t i e s ( &prop, i ) ; } copydatatogpu ( ) ; // Nahrání obrazových dat do paměti GPU // S p u š t ě n í CUDA k e r n e l u c u d a g p u k e r n e l ( m sourceimage. rows, m sourceimage. c o l s, m g p u a r r a y s r c, m g p u a r r a y d s t, m t h r e s h o l d ) ; // S y n c h r o n i z a c e v l á k e n cudathreadsynchronize ( ) ; getdatafromgpu ( ) ; // Z í s k á n í obrazových dat z paměti GPU }

38 6 POROVNÁNÍ IMPLEMENTACÍ Alokace paměti na grafické kartě je o něco složitější, než použití jednoduché funkce v rámci knihovny OpenCV. CUDA se přesto ve svých funkcích snaží, jak je vidět v kódu 31, vše co nejvíce programátorům usnadnit, a tak funkce pro práci s pamětí kopírují klasické funkce malloc(), memcpy() a free() známe z jazkya C++. Pomocí funkce cudamalloc() dojde k alokování příslušného bloku paměti na grafické kartě. Poté je možné pomocí funkce cudamemcpy() data kopírovat. Kopie dat do GPU a z GPU se liší pouze prohozenými parametry vstupního a výstupního pole a parametry cudamemcpyhosttodevice (v případě kopírování do GPU) a cudamemcpydevicetohost (v případě kopírování z GPU), které udávají směr kopírování. Nakonec je třeba dříve alokovanou pamět korektně uvolnit pomocí funkce cudafree(). Kód 31: Metody copydatatogpu() a getdatafromgpu() CUDA implementace v o i d CudaThreshold : : copydatatogpu ( ) { // Alokace paměti v GPU cudamalloc ( ( v o i d ) &m g p u a r r a y s r c, m s ourceimagesize s i z e o f ( u n s i g n e d char ) ) ; cudamalloc ( ( v o i d ) &m g p u a r r a y d s t, m s ourceimagesize s i z e o f ( u n s i g n e d char ) ) ; // Nahrání obrazových dat do paměti GPU cudamemcpy ( m g p u a r r a y s r c, m sourceimage. data, m s ourceimagesize s i z e o f ( u n s i g n e d char ), cudamemcpyhosttodevice ) ; } v o i d CudaThreshold : : getdatafromgpu ( ) { // Z í s k á n í obrazových dat z paměti GPU cudamemcpy ( m sourceimage. data, m g p u a r r a y d s t, m s ourceimagesize s i z e o f ( u n s i g n e d char ), cudamemcpydevicetohost ) ; // U v o l n ě n í a l o k o v a n é paměti cudafree ( m g p u a r r a y s r c ) ; cudafree ( m g p u a r r a y d s t ) ; } 6.1.5 OpenCL Implementace na platformě OpenCL, jak je možné vidět na hlavičkovém souboru v kódu 32, je z uváděných příkladů nejrozsáhlejší. Hlavní příčinou je odlišný způsob práce s kernelem oproti implementaci na platformě CUDA. Demonstrační třída z těchto důvodů obsahuje navíc metody loadopenclkernel() a storeopenclkernel(), které umožňují načtení kernelu z jeho zdrojového souboru, aby mohl být před započetím výpočtu přeložen pro konkrétní nalezené zařízení. Stejně tak odlišný způsob práce s dostupnými zařízeními zvyšuje, při zachování srovnatelného konceptu

6.1 Rozdílné implementace prahování 39 demonstrační třídy s ostatními implementacemi, počty parametrů předávané jednotlivým metodám. Kód 32: Hlavičkový soubor implementace třídy pro prahování pomocí platformy OpenCL #i f n d e f OPENCLIP #d e f i n e OPENCLIP #i n c l u d e <i o s t r e a m > #i n c l u d e <fstream > #i n c l u d e <CL/ c l. h> #i n c l u d e opencv2 / h i g h g u i / h i g h g u i. hpp c l a s s OpenClImageProcessing { p r i v a t e : s t d : : s t r i n g m openclkernelpath ; s t d : : s t r i n g m openclkernelsource ; cv : : Mat m sourceimage ; i n t m s ourceimagesize ; cl mem m g p u a r r a y s r c ; cl mem m g p u a r r a y d s t ; b o o l m s h o w D e t a i l I n f o ; i n t m t h r e s h o l d ; p r i v a t e : // N a č t e n í OpenCL k e r n e l u j a k o ř e t ě z c e v o i d loadopenclkernel ( ) throw ( i n t ) ; // Metody pro p r á c i s pamětí GPU v o i d copydatatogpu ( c o n s t c l c o n t e x t context, c l i n t & e r r o r ) ; v o i d getdatafromgpu ( c o n s t cl command queue commandqueue, c l i n t & e r r o r ) ; p u b l i c : OpenClImageProcessing ( c o n s t s t d : : s t r i n g imagepath, c o n s t i n t t h r e s h o l d, b o o l s h o w D e t a i l I n f o ) ; v o i d s t o r e O p e n C l K e r n e l ( c o n s t s t d : : s t r i n g o p e n C l K e r n e l ) ; v o i d r u n K e r n e l ( ) ; v o i d showresult ( ) ; } ; #e n d i f Implementace OpenCL kernelu v kódu 33 je téměř totožná s implementací na platformě CUDA. Rozdíly jsou pouze v deklaracích datových typů a kernelu samotného, což je platformě specifická záležitost, stejně tak v použití rozdílné interní předdefinované funkce pro určení pozice ve vstupním poli (funkce get global id()).

40 6 POROVNÁNÍ IMPLEMENTACÍ Kód 33: Implementace OpenCL kernelu řešícího prahování // D e k l a r a c e OpenCL k e r n e l u k e r n e l v o i d g p u k e r n e l ( i n t rows, i n t c o l s, g l o b a l u n s i g n e d char s r c d a t a, g l o b a l u n s i g n e d char d s t d a t a, i n t t h r e s h o l d ){ // I d e n t i f i k a c e p o z i c e v paměti i n t n = g e t g l o b a l i d ( 0 ) ; // K o n t r o l a mezí i f ( n > c o l s rows ) r e t u r n ; // Prahování i f ( s r c d a t a [ n]> t h r e s h o l d ) d s t d a t a [ n ] = 255; e l s e d s t d a t a [ n ] = 0 ; } ; Metoda runkernel() v kódu 34 obsahuje nejdůležitější část samotné implementace. Nejdříve je třeba detekovat platformu na které bude kód kernelu vykonáván. Detekce je komplexnější (na rozdíl od CUDY), neboť je třeba brát v potaz větší spektrum hardware na kterém je možné OpenCL kód spustit. Nedostatečně obecně napsaná detekce může způsobit, že výsledný program bude fungovat pouze na specifické skupině zařízení. Metoda loadopenclkernel() zajistí načtení zdrojového souboru kernelu. Takto načtený kernel je následně ve funkci clbuildprogram(), po náležitém zpracování, přeložen pro zvolené zařízení. Stejně jako u platformy CUDA je třeba do GPU nahrát potřebná data pomocí metody copydatatogpu(). Poté jsou přeloženému kernelu přiřazeny potřebné parametry. To je nutné, s ohledem na způsob překladu, řešit pomocí funkce clcreatekernel(), kde zvolíme konkrétní kernel, který chceme používat, a přiřadíme mu správné argumenty pomocí funkce clsetkernelarg(). V této části implementace je velmi lehké se přehlédnou, zvláště u číslování parametrů, a po zkompilování celého projektu skončit s nefunkčním programem. Před spuštěním kernelu samotného je třeba vytvořit frontu příkazů pro zvolené zařízení za pomoci clcreatecommandqueue(). Spuštění kernelu je provedeno funkcí clenqueuendrangekernel(). Proměnná globalworksize, která je parametrem funkce spouštějící kernel, udává objem zpracovávaných dat a umožňuje nastavit vícerozměrné zpracování dat spolu s proměnnou localworksize, která bývá nastavena jako následující parametr za globalworksize (příklad toto nastavení nedemonstruje). Po provedení kernelu jsou data zkopírována z GPU zpět pomocí metody getdatafromgpu(). Nakonec je třeba uzavřít frontu příkazů vytvořenou pro spuštění kernelu funkcí clfinish().

6.1 Rozdílné implementace prahování 41 Kód 34: Metoda runkernel() OpenCL implementace v o i d OpenClImageProcessing : : r u n K e r n e l ( ) { c l i n t e r r o r ; c l p l a t f o r m i d p l a t f o r m i d ; c l d e v i c e i d d e v i c e ; c l u i n t p l a t f o r m s, d e v i c e s ; // Detekce p l a t o f r m y e r r o r = c l G e t P l a t f o r m I D s ( 1, &p l a t f o r m i d, &p l a t f o r m s ) ; e r r o r = c l G e t D e v i c e I D s ( p l a t f o r m i d, CL DEVICE TYPE ALL, 1, &d e v i c e, &d e v i c e s ) ; c l c o n t e x t p r o p e r t i e s p r o p e r t i e s [ ] = {CL CONTEXT PLATFORM, ( c l c o n t e x t p r o p e r t i e s ) p l a t f o r m i d, 0 } ; // V y t v o ř e n í k o n t e x t u z a ř í z e n í c l c o n t e x t c o n t e x t=c l C r e a t e C o n t e x t ( p r o p e r t i e s, 1, &d e v i c e, 0, 0, &e r r o r ) ; // N a č t e n í z d r o j o v é h o kódu OpenCL k e r n e l u loadopenclkernel ( ) ; // V y t v o ř e n í programu ze z d r o j o v é h o souboru c o n s t char k e r n e l S o u r c e = m openclkernelsource. c s t r ( ) ; c l p r o g r a m o p e n c l p r o g r a m = clcreateprogramwithsource ( context, 1, ( c o n s t char )& k e r n e l S o u r c e, 0, &e r r o r ) ; // P ř e k l a d OpenCL k e r n e l u e r r o r = c l B u i l d P r o g r a m ( opencl program, 0, 0, 0, 0, 0 ) ; // Nahrání p o t ř e b n ý c h dat do GPU copydatatogpu ( context, e r r o r ) ; // N a s t a v e n í parametrů k e r n e l u a v y t v o ř e n í k e r n e l u c l k e r n e l o p e n c l g p u k e r n e l=c l C r e a t e K e r n e l ( opencl program, g p u k e r n e l, &e r r o r ) ; c l S e t K e r n e l A r g ( o p e n c l g p u k e r n e l, 0, s i z e o f ( i n t ), &m sourceimage. rows ) ; c l S e t K e r n e l A r g ( o p e n c l g p u k e r n e l, 1, s i z e o f ( i n t ), &m sourceimage. c o l s ) ; c l S e t K e r n e l A r g ( o p e n c l g p u k e r n e l, 2, s i z e o f ( cl mem ), &m g p u a r r a y s r c ) ; c l S e t K e r n e l A r g ( o p e n c l g p u k e r n e l, 3, s i z e o f ( cl mem ), &m g p u a r r a y d s t ) ; c l S e t K e r n e l A r g ( o p e n c l g p u k e r n e l, 4, s i z e o f ( i n t ), &m t h r e s h o l d ) ; // V y t v o ř e n í f r o n t y p ř í k a z ů cl command queue commandqueue = clcreatecommandqueue ( context, d e v i c e, 0, &e r r o r ) ; // U r č e n í množství dat ke z p r a c o v á n í s i z e t g l o b a l W o r k S i z e [ 1 ] = { m sourceimagesize } ; // S p u š t ě n í k e r n e l u e r r o r = clenqueuendrangekernel ( commandqueue, o p e n c l g p u k e r n e l, 1, 0, globalworksize, 0, 0, 0, 0 ) ; // N a č t e n í dat z GPU getdatafromgpu ( commandqueue, e r r o r ) ; // Ukončení f o n t y p ř í k a z ů e r r o r=c l F i n i s h ( commandqueue ) ; }

42 6 POROVNÁNÍ IMPLEMENTACÍ Alokace paměti v kódu 35 se taktéž podobá alokaci paměti v CUDA. Funkce clcreatebuffer() alokuje patřičné místo v paměti GPU. Pomocí druhého parametru funkce specifikujeme hodnotou CL MEM READ ONLY, nebo CL MEM WRITE ONLY, zda jde o oblast paměti určené k zápisu či ke čtení. Hodnota CL MEM COPY HOST PTR ve druhém parametru umožňuje navíc ve čtvrtém parametru funkce specifikovat data, která budou na GPU okopírovaná. Pro okopírování dat z GPU slouží funkce clenqueuereadbuffer(). Alokovanou paměť je vhodné po skončení výpočtu uvolnit pomocí funkce clreleasememobject(). Kód 35: Metody copydatatogpu() a getdatafromgpu() OpenCL implementace // Alokace a o k o p í r o v á n í dat do GPU v o i d OpenClImageProcessing : : copydatatogpu ( c o n s t c l c o n t e x t context, c l i n t & e r r o r ){ m g p u a r r a y s r c=c l C r e a t e B u f f e r ( context, CL MEM READ ONLY CL MEM COPY HOST PTR, m sourceimagesize s i z e o f ( u n s i g n e d char ), m sourceimage. data, &e r r o r ) ; m g p u a r r a y d s t=c l C r e a t e B u f f e r ( context, CL MEM WRITE ONLY, m sourceimagesize s i z e o f ( u n s i g n e d char ), 0, &e r r o r ) ; } // Z í s k á n í dat z GPU v o i d OpenClImageProcessing : : getdatafromgpu ( c o n s t cl command queue commandqueue, c l i n t & e r r o r ){ e r r o r=clenqueuereadbuffer ( commandqueue, m g p u a r r a y d s t, CL TRUE, 0, m sourceimagesize s i z e o f ( u n s i g n e d char ), m sourceimage. data, 0, 0, 0 ) ; // U v o l n ě n í paměti clreleasememobject ( m g p u a r r a y s r c ) ; clreleasememobject ( m g p u a r r a y d s t ) ; } 6.2 Výkonové porovnání prahování V tabulce 2 jsou k dispozici naměřené hodnoty doby provádění algoritmu prahování pro popsané platformy. Při porovnání možností zpracování obrazu různými dostupnými metodami snižuje jednoduchost algoritmu chybu měření způsobenou zcela odlišnými metodami implementace. Pro měření hodnot byly v případě platformy CUDA a OpenCL použity interní profilovací nástroje. V případě implementací na CPU a v OpenCV se běžné měření času ukázalo jako nedostatečně přesné. Proto byl pro měření použit systém událostí dostupný v rámci platformy CUDA.

6.2 Výkonové porovnání prahování 43 Porovnání bylo provedeno na notebooku HP8710p, který disponuje grafickou kartou Nvidia Quadro 320M s podporou CUDA a OpenCL. Jako operační systém byla použita Linuxová distribuce Ubuntu 11.10 (32-bit, Kernel Linux 3.0.0-17- generic) s proprietárním ovladačem Nvidia verze 280.13 v základní instalaci a s nainstalovaným překladačem gcc verze 4.6.1. Tab. 2: Porovnání doby provádění algoritmu prahování (hodnoty v ms) Rozlišení CPU OpenCV CPU OpenCV GPU CUDA OpenCL 320 200 1,11 0,05 0,07 0,25 0,25 640 400 4,17 0,30 0,12 0,89 0,93 1280 800 16,42 0,85 0,39 3,52 3,67 2560 1600 64,58 4,08 1,50 14,31 14,58 5120 3200 253,72 18,15 6,00 56,45 65,06 Naměřená data v tabulce 2 ukazují nejenom neefektivitu klasické CPU implementace, ale zároveň nárůst výkonu a vyrovnanost velmi podobných (neoptimálních) implementací pomocí CUDA a OpenCL. Implementace v OpenCV ukazují, že pomocí optimalizace lze dosáhnout úctyhodných výsledků i za pomoci CPU, ale přesto GPU poskytuje, zvláště při velkých objemech dat, výrazné zrychlení zpracování. Vizuální porovnání naměřených dat je ilustrováno grafem na obrázku 5. Obr. 5: Porovnání rychlosti prahování pomocí různých implementací (Zdroj: Karel Zídek)

44 6 POROVNÁNÍ IMPLEMENTACÍ 6.3 Implementace konvoluční masky Za účelem podrobnějšího srovnání platforem CUDA a OpenCL byla vytvořena implementace složitějšího algoritmu, který umožnil důslednější porovnání možností obou platforem. Jako vhodný algoritmus byl vybrán algoritmus obecné konvoluční masky s maticí 5 5. Ta umožňuje nad obrazovými daty provádět množství známých a používaných filtrů a pouhou změnou vstupních parametrů třídy docílit různých filtrů. Obr. 6: Příklad konvoluční matice pro efekt doostření (Zdroj: Karel Zídek) Jedním z nejběžnějších konvolučních filtrů je filtr doostření zobrazený na obrázku 6. Matice se vynásobí s hodnotami okolních pixelů. Všechny tyto hodnoty jsou sečteny, čímž vznikne hodnota nového pixelu. Matice tedy udává, jaký vliv mají okolní body na hodnotu nového bodu. U doostření dochází k potlačení okolních bodů a znásobení hodnoty počítaného pixelu. V případě efektu rozostření je naopak velmi významně brán v potaz vliv okolních pixelů jak ukazuje obrázek 7. Obr. 7: Příklad konvoluční matice pro efekt rozmazání (Zdroj: Karel Zídek)