Программирование на 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. Затем вы можете установить с помощью:

судо dpkg имя-пакета.deb

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

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



судо apt-get update
судо apt-get installчудеса-а также

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

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

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

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

Пример кода

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

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

typedefчасы::хроно::high_resolution_clockЧасы;

#define ITER 65535

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

// Добавляем элементы вектора a и b к вектору c
для (язнак равно 0;я<п; ++я) {
c[я] знак равнок[я] +б[я];
}
}

// Версия функции добавления вектора для графического процессора
__Глобальный__пустотаvector_add_gpu(int *gpu_a,int *gpu_b,int *gpu_c,intп) {
intязнак равноthreadIdx.Икс;
// Цикл for не нужен, потому что среда выполнения CUDA
// потянем этот ИТЭР раз
gpu_c[я] знак равноgpu_a[я] +gpu_b[я];
}

intглавный() {

int *к,*б,*c;
int *gpu_a,*gpu_b,*gpu_c;

кзнак равно (int *)маллок(ИТЭР* размер(int));
бзнак равно (int *)маллок(ИТЭР* размер(int));
cзнак равно (int *)маллок(ИТЭР* размер(int));

// Нам нужны переменные, доступные для GPU,
// поэтому cudaMallocManaged предоставляет эти
cudaMallocManaged(&gpu_a, ИТЭР* размер(int));
cudaMallocManaged(&gpu_b, ИТЭР* размер(int));
cudaMallocManaged(&gpu_c, ИТЭР* размер(int));

для (intязнак равно 0;я<ИТЭР; ++я) {
к[я] знак равноя;
б[я] знак равноя;
c[я] знак равноя;
}

// Вызов функции ЦП и время ее выполнения
автоcpu_startзнак равноЧасы::Теперь();
vector_add_cpu(а, б, в, ИТЭР);
автоcpu_endзнак равноЧасы::Теперь();
часы::Стоимость << 'vector_add_cpu:'
<<часы::хроно::duration_cast<часы::хроно::наносекунды>(cpu_end-cpu_start).считать()
<< наносекунды. п';

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

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

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

возвращение 0;
}

Makefile содержание ниже:

INC= -I/usr/местный/чудеса/включают
NVCCзнак равно/usr/местный/чудеса/являюсь/nvcc
NVCC_OPT= -std = c ++одиннадцать

все:
$(NVCC)$(NVCC_OPT)gpu-example.cpp-илиgpu-example

чистый:
-rm -fgpu-example

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

делать

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

./gpu-example

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

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

Заключение

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