Меню Закрыть

Занятие 5. Программирование на CUDA C. Часть 1.

image_pdfimage_print

На данном занятии читатель познакомится с основами практики программирования на CUDA C – будет подробно описано написание кода, его суть и исполнение. В качестве примеров будут разобраны самые популярные и полезные задачи, среди которых можно выделить:

арифметические операции (сложение, умножение) с числами и матрицами, добавление массива, генерация псевдослучайных чисел, увеличение значений в матрице, алгоритмы сортировки и т.д. В примерах будут разобраны такие моменты, как комбинация тредов и блоков, применение shared-памяти в решении задачи, использование SAXPY («Single-Precision A · X Plus Y»).

Сложение двух чисел

#include <stdio.h>
 
__global__ void add( int *a, int *b, int *c ) {
*c = *a + *b;
}
 
int main( void ) {
int a, b, c; // host копии a, b, c
int *dev_a, *dev_b, *dev_c; // device копии of a, b, c
int size = sizeof( int );
 
//выделяем память для device копий для a, b, c
 
cudaMalloc( (void**)&dev_a, size );
cudaMalloc( (void**)&dev_b, size );
cudaMalloc( (void**)&dev_c, size );
 
a = 5;
b = 10;
 
// копируем ввод на device
 
cudaMemcpy( dev_a, &a, size, cudaMemcpyHostToDevice );
cudaMemcpy( dev_b, &b, size, cudaMemcpyHostToDevice );
 
// запускаем add() kernel на GPU, передавая параметры
 
add<<< 1, 1 >>>( dev_a, dev_b, dev_c );
 
// copy device result back to host copy of c
 
cudaMemcpy( &c, dev_c, size, cudaMemcpyDeviceToHost );
cudaFree( dev_a );
cudaFree( dev_b );
cudaFree( dev_c );
 
return 0;
 
}

Также, как и в предыдущих примерах прописываем спецификатор __global__, внутри которого 3 аргумента — 2 числа и результат их сложения. В теле этой функции прописываем саму арифметическую операцию *c = *a + *b.

В главной функции переменные разделяются на две части: на хосте и на девайсе (названия переменных помечены соответственно), кроме этого указывается переменная, отвечающая за размер данных — int size. Далее выделяем память на GPU с помощью команды cudaMalloc, в аргументах прописаны указатели памяти на указатели типа void поадресно (dev_a, dev_b, dev_c) и выделяемый размер памяти для каждой переменной. Прописываем значения переменных a и b (например 5 и 10). Копируем значения на GPU и запускаем ядро GPU с помощью команды add<<< 1, 1 >>>( dev_a, dev_b, dev_c );. Копируем результат переменной c обратно на CPU и освобождаем память.

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

Сложение двух чисел с использованием N-блоков по одному треду

__global__ void add( int *a, int *b, int *c ) {
c[blockIdx.x] = a[blockIdx.x] + b[blockIdx.x];
}
 
#define N 512
 
int main( void ) {
int *a, *b, *c; // host копии a, b, c
int *dev_a, *dev_b, *dev_c; // device копии a, b, c
int size = N * sizeof( int );
 
//выделяем память для device копий a, b, c
 
cudaMalloc( (void**)&dev_a, size );
cudaMalloc( (void**)&dev_b, size );
cudaMalloc( (void**)&dev_c, size );
 
a = (int*)malloc( size );
b = (int*)malloc( size );
c = (int*)malloc( size );
 
random_ints( a, N );
random_ints( b, N );
 
// копируем ввод на device
 
cudaMemcpy( dev_a, a, size, cudaMemcpyHostToDevice );
cudaMemcpy( dev_b, b, size, cudaMemcpyHostToDevice );
 
// launch add() kernel with N parallel blocks
 
add<<< N, 1 >>>( dev_a, dev_b, dev_c );
 
// копируем результат работы device обратно на host - копию c
 
cudaMemcpy( c, dev_c, size, cudaMemcpyDeviceToHost );
free( a ); free( b ); free( c );
cudaFree( dev_a );
cudaFree( dev_b );
cudaFree( dev_c );
 
return 0;
 
}


Сложение двух чисел с одним блоком и
N-тредов

__global__ void add( int *a, int *b, int *c ) {
c[ threadIdx.x ] = a[ threadIdx.x ] + b[ threadIdx.x ];
// blockIdx.x blockIdx.x blockIdx.x
 
}
 
#define N 512
 
int main( void ) {
int *a, *b, *c; //host копии a, b, c
int *dev_a, *dev_b, *dev_c; //device копии of a, b, c
int size = N * sizeof( int );
 
//выделяем память для копий a, b, c
 
cudaMalloc( (void**)&dev_a, size );
cudaMalloc( (void**)&dev_b, size );
cudaMalloc( (void**)&dev_c, size );
 
a = (int*)malloc( size );
b = (int*)malloc( size );
c = (int*)malloc( size );
 
random_ints( a, N );
random_ints( b, N );
 
// копируем ввод device
 
cudaMemcpy( dev_a, a, size, cudaMemcpyHostToDevice );
cudaMemcpy( dev_b, b, size, cudaMemcpyHostToDevice );
 
// запускаем на выполнение add() kernel с N тредами в блоке
 
add<<< 1, N >>>( dev_a, dev_b, dev_c );
 
// N, 1
 
// копируем результат работы device обратно на host (копия c)
 
cudaMemcpy( c, dev_c, size, cudaMemcpyDeviceToHost );
free( a ); free( b ); free( c );
cudaFree( dev_a );
cudaFree( dev_b );
cudaFree( dev_c );
 
return 0;
 
}


Сложение двух чисел с использованием N-блоков и N-тредов

__global__ void add( int *a, int *b, int *c ) {
int index = threadIdx.x + blockIdx.x * blockDim.x;
c[index] = a[index] + b[index];
 
}
 
#define N (2048*2048)
 
#define THREADS_PER_BLOCK 512
 
int main( void ) {
int *a, *b, *c; // host копии a, b, c
int *dev_a, *dev_b, *dev_c; // device копии of a, b, c
int size = N * sizeof( int );
 
//выделяем память на device для of a, b, c
 
cudaMalloc( (void**)&dev_a, size );
cudaMalloc( (void**)&dev_b, size );
cudaMalloc( (void**)&dev_c, size );
 
a = (int*)malloc( size );
b = (int*)malloc( size );
c = (int*)malloc( size );
 
random_ints( a, N );
random_ints( b, N );
 
//копируем ввод на device
 
cudaMemcpy( dev_a, a, size, cudaMemcpyHostToDevice );
cudaMemcpy( dev_b, b, size, cudaMemcpyHostToDevice );
 
//запускаем на выполнение add() kernel с блоками и тредами
 
add<<< N/THREADS_PER_BLOCK, THREADS_PER_BLOCK >>>( dev_a, dev_b, dev_c );
 
// копируем результат работы device на host ( копия c )
 
cudaMemcpy( c, dev_c, size, cudaMemcpyDeviceToHost );
free( a ); free( b ); free( c );
cudaFree( dev_a );
cudaFree( dev_b );
cudaFree( dev_c );
 
return 0;
 
}

Связанные записи