Общ преглед
В това ръководство ще изследваме силата на GPU програмирането с C++. Разработчиците могат да очакват невероятна производителност с C ++, а достъпът до феноменалната мощ на графичния процесор с език на ниско ниво може да доведе до някои от най-бързите изчисления, налични в момента.
Изисквания
Въпреки че всяка машина, способна да изпълнява модерна версия на Linux, може да поддържа компилатор на C ++, ще ви е необходим графичен процесор, базиран на NVIDIA, който да следва заедно с това упражнение. Ако нямате графичен процесор, можете да завъртите екземпляр, задвижван от графичен процесор, в Amazon Web Services или друг доставчик на облак по ваш избор.
Ако изберете физическа машина, моля, уверете се, че имате инсталирани собствени драйвери на NVIDIA. Можете да намерите инструкции за това тук: https: // linuxhint.com / install-nvidia-drivers-linux /
В допълнение към драйвера ще ви е необходим инструментариумът CUDA. В този пример ще използваме Ubuntu 16.04 LTS, но има налични изтегляния за повечето основни дистрибуции на следния URL: https: // разработчик.nvidia.com / cuda-downloads
За Ubuntu бихте избрали .изтегляне на базата на deb. Изтегленият файл няма да има .deb разширение по подразбиране, затова препоръчвам да го преименувате, за да има .деб в края. След това можете да инсталирате с:
sudo dpkg -i име на пакета.дебВероятно ще бъдете подканени да инсталирате GPG ключ и ако е така, следвайте предоставените инструкции за това.
След като направите това, актуализирайте своите хранилища:
sudo apt-get updatesudo apt-get install cuda -y
След като приключите, препоръчвам да рестартирате, за да сте сигурни, че всичко е заредено правилно.
Ползите от развитието на GPU
Процесорите се справят с много различни входове и изходи и съдържат голям набор от функции за не само справяне с широк асортимент от програмни нужди, но и за управление на различни хардуерни конфигурации. Те също така се справят с паметта, кеширането, системната шина, сегментирането и IO функционалността, което ги прави вале за всички сделки.
Графичните процесори са обратното - те съдържат много отделни процесори, които са фокусирани върху много прости математически функции. Поради това те обработват задачите многократно по-бързо от процесорите. Като се специализират в скаларни функции (функция, която приема един или повече входове, но връща само един изход), те постигат изключителна производителност за сметка на екстремната специализация.
Примерен код
В примерния код добавяме вектори заедно. Добавих версия на CPU и GPU на кода за сравнение на скоростта.
gpu-пример.cpp съдържание по-долу:
#include
#include
#include
#include
#include
typedef std :: chrono :: high_resolution_clock Clock;
#define ITER 65535
// CPU версия на функцията за добавяне на вектор
void vector_add_cpu (int * a, int * b, int * c, int n)
int i;
// Добавяне на векторните елементи a и b към вектора c
за (i = 0; i < n; ++i)
c [i] = a [i] + b [i];
// GPU версия на функцията за добавяне на вектор
__global__ void vector_add_gpu (int * gpu_a, int * gpu_b, int * gpu_c, int n)
int i = threadIdx.х;
// Не е необходим цикъл, защото изпълнението на CUDA
// ще преведе това ITER пъти
gpu_c [i] = gpu_a [i] + gpu_b [i];
int main ()
int * a, * b, * c;
int * gpu_a, * gpu_b, * gpu_c;
a = (int *) malloc (ITER * sizeof (int));
b = (int *) malloc (ITER * sizeof (int));
c = (int *) malloc (ITER * sizeof (int));
// Нуждаем се от променливи, достъпни за графичния процесор,
// така че cudaMallocManaged ги предоставя
cudaMallocManaged (& gpu_a, ITER * sizeof (int));
cudaMallocManaged (& gpu_b, ITER * sizeof (int));
cudaMallocManaged (& gpu_c, ITER * sizeof (int));
за (int i = 0; i < ITER; ++i)
a [i] = i;
b [i] = i;
c [i] = i;
// Извикайте функцията на процесора и я определете по време
auto cpu_start = Clock :: now ();
vector_add_cpu (a, b, c, ITER);
auto cpu_end = Clock :: now ();
std :: cout << "vector_add_cpu: "
<< std::chrono::duration_cast
<< " nanoseconds.\n";
// Обадете се на функцията GPU и я измервайте по време
// Тройните ъглови скоби са разширение за изпълнение на CUDA, което позволява
// параметри на извикване на ядрото CUDA, което трябва да бъде предадено.
// В този пример предаваме един блок от нишки с ITER нишки.
auto gpu_start = Clock :: now ();
vector_add_gpu <<<1, ITER>>> (gpu_a, gpu_b, gpu_c, ITER);
cudaDeviceSynchronize ();
auto gpu_end = Clock :: now ();
std :: cout << "vector_add_gpu: "
<< std::chrono::duration_cast
<< " nanoseconds.\n";
// Освобождаване на разпределението на паметта въз основа на GPU-функция
cudaFree (а);
cudaFree (b);
cudaFree (c);
// Освободете разпределението на паметта въз основа на CPU-функцията
безплатно (а);
безплатно (б);
безплатно (c);
връщане 0;
Makefile съдържание по-долу:
INC = -I / usr / local / cuda / включваNVCC = / usr / local / cuda / bin / nvcc
NVCC_OPT = -std = c ++ 11
всичко:
$ (NVCC) $ (NVCC_OPT) gpu-пример.cpp -o gpu-пример
чисто:
-rm -f gpu-пример
За да стартирате примера, компилирайте го:
направиСлед това стартирайте програмата:
./ gpu-примерКакто можете да видите, версията на процесора (vector_add_cpu) работи значително по-бавно от версията на графичния процесор (vector_add_gpu).
Ако не, може да се наложи да коригирате дефиницията на ITER в gpu-пример.cu на по-голямо число. Това се дължи на това, че времето за настройка на графичния процесор е по-дълго от някои по-малки цикли, интензивни за процесора. Установих, че 65535 работи добре на моята машина, но пробегът ви може да варира. След като изчистите този праг, GPU е драстично по-бърз от CPU.
Заключение
Надявам се да сте научили много от въвеждането ни в GPU програмирането с C++. Примерът по-горе не постига много, но демонстрираните концепции осигуряват рамка, която можете да използвате, за да включите идеите си, за да разгърнете мощта на вашия графичен процесор.