Программирование на GPU с помощью 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 -i package-name.deb

Вероятно, вам будет предложено установить ключ GPG, и если это так, следуйте инструкциям, чтобы сделать это.

Как только вы это сделаете, обновите свои репозитории:

sudo apt-get update
sudo apt-get install cuda -y

После этого я рекомендую перезагрузиться, чтобы убедиться, что все правильно загружено.

Преимущества разработки GPU

ЦП обрабатывают множество различных входов и выходов и содержат большой набор функций не только для работы с широким ассортиментом программных потребностей, но и для управления различными конфигурациями оборудования. Они также обрабатывают память, кэширование, системную шину, сегментирование и функции ввода-вывода, что делает их мастером на все руки.

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

Пример кода

В примере кода мы складываем векторы вместе. Я добавил версию кода для CPU и GPU для сравнения скорости.
gpu-example.cpp , содержимое ниже:

#include «cuda_runtime.h»
#include
#include
#include
#include
#include

typedef std :: chrono :: high_resolution_clock Часы;

#define ITER 65535

//Версия процессора функции добавления вектора
void vector_add_cpu (int * a, int * b , int * c, int n) {
int i;

//Добавляем векторные элементы a и b к вектору c
for (i = 0 ; i c [i] = a [i] + b [i];
}
}

//Версия функции добавления вектора для графического процессора
__ global__ void vector_add_gpu (int * gpu_a, int * gpu_b, int * gpu_c, int n) {
int i = threadIdx.x;
//Цикл for не нужен, потому что среда выполнения 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));
//Нам нужны переменные, доступные для GPU,
//поэтому cudaMallocManaged предоставляет эти
cudaMallocManaged (& gpu_a, ITER * sizeof (int));
cudaMallocManaged (& gpu_b, ITER * sizeof (int));
cudaMallocManaged (& gpu_c, ITER * sizeof (int));

for (int i = 0; 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 (cpu_end — cpu_start) .count ()

//Вызов функции GPU и время ее выполнения
// тройные угловые скобки — это расширение среды выполнения CUDA, которое позволяет передавать
//параметры вызова ядра CUDA.
//В этом примере мы передаем один блок потока с потоками ITER.
auto gpu_start = Clock :: now ();
vector_add_gpu >> (gpu_a, gpu_b, gpu_c, ITER);
cudaDeviceSynchronize ();
auto gpu_end = Clock :: now ();
std :: cout (gpu_end — gpu_start) .co unt ()

//Освобождает выделение памяти на основе функций графического процессора
cudaFree (a);
cudaFree (b);
cudaFree (c);

//Освободить выделение памяти на основе функции ЦП
free (a);
бесплатно (b);
бесплатно (c);

return 0;
}

Makefile ниже:

INC = -I/usr/local/cuda/include
NVCC =/usr/local/ cuda/bin/nvcc
NVCC_OPT = -std = c ++ 11

all:
$ (NVCC) $ (NVCC_OPT) пример графического процессора. cpp -o gpu-example

clean:
-rm -f gpu-example

Чтобы запустить пример, скомпилируйте его :

make

Затем запустите программу:

./ gpu-example

Как видите, версия CPU (vector_add_cpu) работает значительно медленнее, чем версия GPU (vector_add_gpu).

Если нет, вам может потребоваться изменить определение ИТЭР в gpu-example.cu на большее число. Это связано с тем, что время настройки графического процессора больше, чем в некоторых меньших циклах, интенсивно использующих ЦП. Я обнаружил, что 65535 хорошо работает на моей машине, но ваш пробег может отличаться. Однако, как только вы преодолеете этот порог, GPU станет значительно быстрее, чем CPU.

Заключение

Я надеюсь, что вы многому научились из нашего введения в программирование GPU. с C ++. Приведенный выше пример не дает многого, но продемонстрированные концепции обеспечивают основу, которую вы можете использовать, чтобы воплотить свои идеи, чтобы раскрыть всю мощь вашего графического процессора.

Оцените статью
nanomode.ru
Добавить комментарий