Самый простой случай создания ядра 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