У меня проблема с разворачиванием цикла в CUDA.

В нормальном серийном коде:

//serial basic:
for(int i = 0; i < n; i++){
    c[i] = a[i] + b[i];}

//serial loop unroll:
for(int i = 0; i < n/4; i++){
    c[i] = a[i] + b[i];
    c[i+1] = a[i+1] + b[i+1];
    c[i+2] = a[i+2] + b[i+2];
    c[i+3] = a[i+3] + b[i+3];} 

Итак, я думаю, что развертывание цикла CUDA выглядит так:

int i = 2*(threadIdx.x + blockIdx.x * gridDim.x);
a[i+0] = b[i+0] + c[i+0];
a[i+1] = b[i+1] + c[i+1];

Но в справочнике CUDA пример развертывания, который я не могу понять

Это нормальное ядро ​​GlobalWrite:

__global__ void GlobalWrites( T *out, T value, size_t N )
{
for(size_t i = blockIdx.x*blockDim.x+threadIdx.x;
    i < N;
    i += blockDim.x*gridDim.x ) {
    out[i] = value;
    } 
 }

Разворачивание ядра:

template<class T, const int n> __global__ void Global_write(T* out, T value, size_t N){
size_t i;
for(i = n*blockDim.x*blockIdx.x + threadIdx.x;
    i < N - n*blockDim.x*blockIdx.x;
    i += n*gridDim.x*blockDim.x;)
    for(int j = 0; j < n; i++){
        size_t index = i + j * blockDim.x;
        outp[index] = value;
    }
for ( int j = 0; j < n; j++ ) {
    size_t index = i+j*blockDim.x;
    if ( index<N ) out[index] = value;
}}

Я знаю, что это ядро ​​использует меньше блоков, но может кто-нибудь объяснит, почему оно работает лучше (n = 4,10% ускорение).

0
Zziggurats 22 Июн 2014 в 10:12

2 ответа

Лучший ответ

Если это не было очевидным, поскольку n является параметром шаблона, он остается постоянным во время компиляции. Это означает, что компилятор может оптимизировать цикл постоянного счетчика отключений путем развертывания. Поэтому поучительно удалить магию шаблона и развернуть цикл вручную для случая n = 4, который вы упомянули:

template<class T> 
__global__ void Global_write(T* out, T value, size_t N)
{
    size_t i;
    for(i = 4*blockDim.x*blockIdx.x + threadIdx.x;
        i < N - 4*blockDim.x*blockIdx.x;
        i += 4*gridDim.x*blockDim.x;) {
            out[i + 0 * blockDim.x] = value;
            out[i + 1 * blockDim.x] = value;
            out[i + 2 * blockDim.x] = value;
            out[i + 3 * blockDim.x] = value;
    }
    if ( i+0*blockDim.x < N ) out[i+0*blockDim.x] = value;
    if ( i+1*blockDim.x < N ) out[i+1*blockDim.x] = value;
    if ( i+2*blockDim.x < N ) out[i+2*blockDim.x] = value;
    if ( i+3*blockDim.x < N ) out[i+3*blockDim.x] = value;
}

Развернутый внутренний цикл приводит к четырем полностью независимым операциям записи, которые объединяются. Именно этот параллелизм на уровне инструкций дает коду более высокую пропускную способность и улучшенную производительность. Я настоятельно рекомендую развертывание параллельных циклов Василия Волкова с конференции GTC несколько лет назад, если вы еще не видели. В его презентации излагаются теоретические основы того, почему этот тип развертывания цикла является оптимизацией в CUDA.

6
talonmies 23 Июн 2014 в 10:44

В шаблонном ядре const int n известно во время компиляции, что позволяет компилятору фактически развернуть цикл for(int j = 0; j < n; i++), удаляя условные проверки этого цикла. Если размер цикла неизвестен во время компиляции, компилятор не может развернуть цикл. Просто как тот.

3
Avi Ginsburg 22 Июн 2014 в 07:07