Простейшим примером создания ядра CUDA ® являются функции MATLAB ®, содержащие скаляризованные математические операции. Когда операции по элементам заключены в тело for-loop, можно вызвать параллельные потоки CUDA для параллельного вычисления каждой итерации цикла. Поскольку потоки CUDA не выполняются в определенном порядке и не зависят друг от друга, важно, чтобы ни одна итерация не выполнялась в for-loop зависит от результатов других итераций.
В этом примере показано, как создавать ядра 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. Указание этой pragma ядра переопределяет весь анализ с параллельным циклом. Если параметры не указаны, кодер графического процессора определяет границы ядра на основе границ цикла и размера входных данных. Он позволяет задать параметры запуска ядра, такие как размеры потоков и блоков. Однако используйте его только в том случае, если известно, что цикл безопасен для параллелизма. Потому что myFun пример прост и не требует спецификации параметров запуска ядра, вы можете использовать coder.gpu.kernelfun pragma для создания ядер 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