Програмиране на GPU с C ++

Gpu Programming With C



В това ръководство ще изследваме силата на програмирането на GPU с C ++. Разработчиците могат да очакват невероятна производителност с C ++, а достъпът до феноменалната мощ на графичния процесор с език на ниско ниво може да доведе до някои от най-бързите изчисления, налични в момента.

Изисквания

Докато всяка машина, способна да изпълнява модерна версия на Linux, може да поддържа C ++ компилатор, ще ви е необходим графичен процесор, базиран на NVIDIA, който да следвате заедно с това упражнение. Ако нямате графичен процесор, можете да завъртите екземпляр, задвижван от графичен процесор, в Amazon Web Services или друг облачен доставчик по ваш избор.







Ако изберете физическа машина, моля, уверете се, че имате инсталирани собствени драйвери на NVIDIA. Можете да намерите инструкции за това тук: https://linuxhint.com/install-nvidia-drivers-linux/



В допълнение към драйвера ще ви е необходим набор от инструменти CUDA. В този пример ще използваме Ubuntu 16.04 LTS, но има налични файлове за изтегляне за повечето големи дистрибуции на следния URL адрес: https://developer.nvidia.com/cuda-downloads



За Ubuntu бихте избрали изтеглянето, базирано на .deb. Изтегленият файл няма да има разширение .deb по подразбиране, затова препоръчвам да го преименувате, за да има .deb в края. След това можете да инсталирате с:





sudo dpkg package-name.deb

Вероятно ще бъдете подканени да инсталирате GPG ключ и ако е така, следвайте предоставените инструкции за това.

След като направите това, актуализирайте хранилищата си:



sudo apt-get update
sudo apt-get installчудеса

След като приключите, препоръчвам да рестартирате, за да сте сигурни, че всичко е заредено правилно.

Ползите от разработката на GPU

Процесорите обработват много различни входове и изходи и съдържат голям набор от функции за не само справяне с широк асортимент от програмни нужди, но и за управление на различни хардуерни конфигурации. Те също така обработват памет, кеширане, системна шина, сегментиране и IO функционалност, което ги прави джак за всички сделки.

Графичните процесори са обратното - те съдържат много отделни процесори, които са фокусирани върху много прости математически функции. Поради това те обработват задачи много пъти по -бързо от процесорите. Специализирайки се в скаларни функции (функция, която приема един или повече входове, но връща само един изход), те постигат изключителна производителност с цената на изключителна специализация.

Примерен код

В примерния код добавяме вектори заедно. Добавих CPU и GPU версия на кода за сравнение на скоростта.
gpu-example.cpp съдържание по -долу:

#include 'cuda_runtime.h'
#включва
#включва
#включва
#включва
#включва

typedefчаса::хроно::high_resolution_clockЧасовник;

#дефинирайте ITER 65535

// CPU версия на функцията за добавяне на вектор
невалиденvector_add_cpu(int *да се,int *б,int *° С,intн) {
inti;

// Добавете векторните елементи a и b към вектора c
за (i= 0;i<н; ++i) {
° С[i] =да се[i] +б[i];
}
}

// GPU версия на функцията за добавяне на вектор
__global__невалиденvector_add_gpu(int *gpu_a,int *gpu_b,int *gpu_c,intн) {
inti=threadIdx.х;
// Не е необходим цикъл за цикъл, защото времето на изпълнение на CUDA
// ще вмъкна този ITER пъти
gpu_c[i] =gpu_a[i] +gpu_b[i];
}

intглавен() {

int *да се,*б,*° С;
int *gpu_a,*gpu_b,*gpu_c;

да се= (int *)malloc(ITER* размер на(int));
б= (int *)malloc(ITER* размер на(int));
° С= (int *)malloc(ITER* размер на(int));

// Нуждаем се от променливи, достъпни за графичния процесор,
// така че cudaMallocManaged предоставя тези
cudaMallocManaged(&gpu_a, ITER* размер на(int));
cudaMallocManaged(&gpu_b, ITER* размер на(int));
cudaMallocManaged(&gpu_c, ITER* размер на(int));

за (inti= 0;i<ITER; ++i) {
да се[i] =i;
б[i] =i;
° С[i] =i;
}

// Извикваме функцията на процесора и измерваме времето
Автоматиченcpu_start=Часовник::сега();
vector_add_cpu(a, b, c, ITER);
Автоматиченcpu_end=Часовник::сега();
часа::цена << 'vector_add_cpu:'
<<часа::хроно::duration_cast<часа::хроно::наносекунди>(cpu_end-cpu_start).броя()
<< 'наносекунди.н';

// Извикайте функцията GPU и я измервайте
// Тройните ъглови скоби са разширение за изпълнение на CUDA, което позволява
// параметри на извикване на ядрото на CUDA, които трябва да бъдат предадени.
// В този пример предаваме един блок с нишки с нишки ITER.
Автоматиченgpu_start=Часовник::сега();
vector_add_gpu<<<1, ITER>>> (gpu_a, gpu_b, gpu_c, ITER);
cudaDeviceSynchronize();
Автоматиченgpu_end=Часовник::сега();
часа::цена << 'vector_add_gpu:'
<<часа::хроно::duration_cast<часа::хроно::наносекунди>(gpu_end-gpu_start).броя()
<< 'наносекунди.н';

// Освобождаване на разпределението на паметта, базирано на GPU-функция
cudaБЕЗПЛАТНО(да се);
cudaБЕЗПЛАТНО(б);
cudaБЕЗПЛАТНО(° С);

// Освобождаване на разпределението на паметта, базирано на функцията на процесора
Безплатно(да се);
Безплатно(б);
Безплатно(° С);

връщане 0;
}

Makefile съдържание по -долу:

INC= -I/usr/местен/чудеса/включват
NVCC=/usr/местен/чудеса/съм/nvcc
NVCC_OPT= -std = c ++единадесет

всичко:
$(NVCC)$(NVCC_OPT)gpu-example.cpp-илиgpu-пример

чист:
-rm -fgpu-пример

За да стартирате примера, го компилирайте:

направете

След това стартирайте програмата:

./gpu-пример

Както можете да видите, версията на процесора (vector_add_cpu) работи значително по -бавно от версията на графичния процесор (vector_add_gpu).

Ако не, може да се наложи да коригирате дефиницията на ITER в gpu-example.cu на по-голямо число. Това се дължи на времето за настройка на графичния процесор, което е по-дълго от някои по-малки цикли, интензивни на процесора. Открих, че 65535 работи добре на моята машина, но пробегът ви може да варира. Въпреки това, след като изчистите този праг, графичният процесор е драстично по -бърз от процесора.

Заключение

Надявам се, че сте научили много от нашето въведение в програмирането на GPU с C ++. Горният пример не постига много, но демонстрираните концепции предоставят рамка, която можете да използвате, за да включите идеите си, за да разгърнете силата на вашия графичен процесор.