Ядра от поэлементных циклов

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

Подготовка myFun для Генерации кода

Первый оператор 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

Когда вы генерируете код 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);

Ограничения

  • Если границы цикла имеют тип данных без знака, генератор кода может добавить условные проверки, чтобы определить, допустимы ли границы цикла. Эти условные проверки могут ограничить оптимизацию, которая выполняется программным обеспечением и вводит ядра сокращения, которые могут влиять на производительность.

Смотрите также

| | | |

Похожие темы