Самый простой случай создания ядра CUDA® от функций MATLAB®, которые содержат scalarized, поэлементные математические операции. Когда поэлементные операции заключены в теле цикла for, параллельные потоки CUDA могут быть вызваны, чтобы вычислить каждую итерацию цикла параллельно. Поскольку потоки CUDA выполняются без определенного порядка и независимы друг от друга, важно что никакая итерация в вашем for
- цикл зависит от результатов других итераций.
В этом примере показано, как создать ядра CUDA из функций, которые содержат поэлементные математические операции. Предположим, что вы хотите придать каждому элементу квадратную форму матричного x
и шкала фактором 1/(i+j)
, где i,j
индексы строки и столбца. Можно реализовать этот пример как функцию MATLAB.
function [y] = myFun(x) y = zeros(size(x)); for i = 1:size(x,1) for j = 1:size(x,2) y(i,j)=(x(i,j)^2)/(i+j); end end end
Первый оператор zeros(size(A))
в myFun
функция должна инициализировать итоговый вектор y
к нулям. Для генерации кода CUDA предварительно выделите память для y
не подвергаясь издержкам инициализации памяти нулям. Замените эту линию на coder.nullcopy(zeros(size(y)))
.
Чтобы создать ядра CUDA из циклов, GPU Coder™ предоставляет другой прагме coder.gpu.kernel
. Определение этой прагмы ядра заменяет весь анализ параллельного цикла. Если вы не задаете параметров, GPU Coder определяет границы ядра на основе границ цикла и входного размера. Это обеспечивает способ для вас указать, что ядро запускает параметры, такие как размеры block и thread. Однако используйте его только, когда вы знаете, что цикл безопасно параллелизировать. Поскольку myFun
пример прост и не требует спецификации параметров запуска ядра, можно использовать coder.gpu.kernelfun
прагма, чтобы сгенерировать ядра CUDA.
С этими модификациями, исходным myFun
функция подходит для генерации кода.
function [y] = myFun(x) %#codegen y = coder.nullcopy(zeros(size(x))); coder.gpu.kernelfun(); for i = 1:size(x,1) for j = 1:size(x,2) y(i,j)=(x(i,j)^2)/(i+j); end end end
Когда вы генерируете код CUDA при помощи приложения GPU Coder или из командной строки, GPU Coder создает одно ядро, которое выполняет обработку на квадрат и операцию масштабирования. Следующее является отрывком myFun_kernel1
код ядра.
static __global__ __launch_bounds__(512, 1) void myFun_kernel1(const real_T *x, real_T *y) { ... threadId = ((((gridDim.x * gridDim.y * blockIdx.z + gridDim.x * blockIdx.y) + blockIdx.x) * (blockDim.x * blockDim.y * blockDim.z) + threadIdx.z * blockDim.x * blockDim.y) + threadIdx.y * blockDim.x) + threadIdx.x; i = (int32_T)(threadId / 512U); j = (int32_T)(threadId - (uint32_T)i * 512U); if ((!(j >= 512)) && (!(i >= 512))) { y[i + (j << 9)] = x[i + (j << 9)] * x[i + (j << 9)] / ((real_T)(i + j) + 2.0); } }
Следующее является отрывком основного myFun
функция. Прежде, чем вызвать myFun_kernel1
, существует один cudaMemcpy
вызовите, который передает матричный x
от хоста (x
) к устройству (gpu_x
). Ядро имеет 512 блоков, содержащих 512 потоков на блок, сопоставимый с размером входного вектора. Второй cudaMemcpy
вызовите копирует результат расчета назад к хосту.
cudaMemcpy((void *)gpu_x, (void *)x, 2097152ULL, cudaMemcpyHostToDevice); myFun_kernel1<<<dim3(512U, 1U, 1U), dim3(512U, 1U, 1U)>>>(gpu_x, gpu_y); cudaMemcpy((void *)y, (void *)gpu_y, 2097152ULL, cudaMemcpyDeviceToHost);
Если границы цикла имеют тип данных без знака, генератор кода может добавить условные проверки, чтобы определить, допустимы ли границы цикла. Эти условные проверки могут ограничить оптимизацию, которая выполняется программным обеспечением и вводит ядра сокращения, которые могут влиять на производительность.
coder.gpu.constantMemory
| coder.gpu.kernel
| coder.gpu.kernelfun
| gpucoder.matrixMatrixKernel
| gpucoder.stencilKernel