Могу ли я убедиться, что NVCC удалось разместить массив в регистрах? - PullRequest
0 голосов
/ 03 июня 2018

Ядро CUDA с некоторым локальным массивом фиксированного размера может быть скомпилировано так, чтобы массив находился в «локальной памяти» потока, или - если NVCC может определить позицию доступа к каждому массиву во время компиляции, и их достаточнодоступные регистры - массив может быть разбит на элементы, находящиеся в регистрах.

Можно ли проверить или убедиться, либо с помощью кода, либо как часть процесса сборки, что определенный массив или все локальные массивы в ядре вписаны в регистры?Поддерживается ли это каким-либо инструментом?

1 Ответ

0 голосов
/ 03 июня 2018

Во время выполнения

Вы можете использовать CU_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES в качестве подсказки о том, был ли ваш массив зарегистрирован повторно;используя функцию API драйвера CUDA cuFuncGetAttribute.Но для некоторых случаев использования время выполнения может быть слишком поздним.

Во время компиляции

Вы хотите взглянуть на сгенерированный файл ptx (используя параметр --keepв nvcc).

Местное отклонение данных идентифицируется как .local в ptx.Вот небольшой пример с ядром.

#define ww 65

__global__ void kernel(int W, int H, const int *a, int *b)
{
    int buffer[ww];

    for (int i = threadIdx.x; i < H; i += blockDim.x)
    {
        #pragma unroll
        for (int w = 0; w < ww; ++w)
            buffer[w] = a[i + w * W];

        for (int j = 5; j < H - 5; ++j)
        {
            buffer[j % ww] = a[i + (j + 6) * W];

            int s = 0;
            #pragma unroll 
            for (int w = 0; w < ww; ++w)
                s += buffer[w];

            b[i + (j + 6) * W] = s;
        }
    }
}

При компиляции существует объявление локальной переменной:

.visible .entry _Z6kerneliiPKiPi(
    .param .u32 _Z6kerneliiPKiPi_param_0,
    .param .u32 _Z6kerneliiPKiPi_param_1,
    .param .u64 _Z6kerneliiPKiPi_param_2,
    .param .u64 _Z6kerneliiPKiPi_param_3
)
{
    .local .align 4 .b8     __local_depot0[260];
    .reg .b64   %SP;
    .reg .b64   %SPL;
    .reg .pred  %p<5>;
    .reg .b32   %r<219>;
    .reg .b64   %rd<81>;

Однако, при прокате буфера, bufferвсегда доступны с известными индексами и регистры могут быть получены - без локального хранилища:

#define ww 65

__global__ void kernel(int W, int H, const int *a, int *b)
{
    int buffer[ww];

    for (int i = threadIdx.x; i < H; i += blockDim.x)
    {
        #pragma unroll
        for (int w = 0; w < ww; ++w)
            buffer[w] = a[i + w * W];

        for (int j = 5; j < H - 5; ++j)
        {
            #pragma unroll 
            for (int w = 0; w < ww-1; ++w)
                buffer[w] = buffer[w + 1];
            buffer[ww - 1] = a[i + (j + 6) * W];

            int s = 0;
            #pragma unroll 
            for (int w = 0; w < ww; ++w)
                s += buffer[w];

            b[i + (j + 6) * W] = s;
        }
    }
}

Возвращает следующий код:

.visible .entry _Z6kerneliiPKiPi(
    .param .u32 _Z6kerneliiPKiPi_param_0,
    .param .u32 _Z6kerneliiPKiPi_param_1,
    .param .u64 _Z6kerneliiPKiPi_param_2,
    .param .u64 _Z6kerneliiPKiPi_param_3
)
{
    .reg .pred  %p<5>;
    .reg .b32   %r<393>;
    .reg .b64   %rd<240>;

Обратите внимание, что в зависимости от количества доступных регистров, числотребуемых регистров может не подходить.Это виртуальные регистры (которые как-то изменились в последних версиях CUDA).Это означает, что отсутствие .local .align 4 .b8 __local_depot является обязательным условием, но не достаточным.

Вам нужно посмотреть на SASS.Используя nvdisasm в сгенерированном .cubin, вы хотите найти STL инструкцию, которая обозначает STore Local, как кратко описано здесь .Вот части двух разобранных кубинов, скомпилированных с двумя различными значениями --maxrregcount переключателя компилятора - сначала для 32 (см. Множество вхождений STL):

//--------------------- .text._Z6kerneliiPKiPi    --------------------------
    .section    .text._Z6kerneliiPKiPi,"ax",@progbits
    .sectioninfo    @"SHI_REGISTERS=32"
    .align  32
        .global         _Z6kerneliiPKiPi
        .type           _Z6kerneliiPKiPi,@function
        .size           _Z6kerneliiPKiPi,(.L_25 - _Z6kerneliiPKiPi)
        .other          _Z6kerneliiPKiPi,@"STO_CUDA_ENTRY STV_DEFAULT"
_Z6kerneliiPKiPi:
.text._Z6kerneliiPKiPi:
        /*0008*/                   MOV R1, c[0x0][0x20];
        /*0010*/         {         IADD32I R1, R1, -0x180;
        /*0018*/                   S2R R0, SR_TID.X;        }
        /*0028*/                   ISETP.GE.AND P0, PT, R0, c[0x0][0x144], PT;
        /*0030*/                   NOP;
        /*0038*/                   NOP;
        /*0048*/               @P0 EXIT;
.L_3:
        /*0050*/                   IADD R2, R0, c[0x0][0x140];
        /*0058*/                   MOV R30, c[0x0][0x140];
        /*0068*/                   ISCADD R5.CC, R2.reuse, c[0x0][0x148], 0x2;
        /*0070*/         {         SHR R3, R2, 0x1e;
        /*0078*/                   STL [R1+0x14], R5;        }
        /*0088*/                   ISCADD R2, R30.reuse, R0.reuse, 0x1;
        /*0090*/                   ISCADD R4, R30.reuse, R0.reuse, 0x2;
        /*0098*/                   ISCADD R20, R30, R0, 0x3;
        /*00a8*/                   IADD.X R5, R3, c[0x0][0x14c];
        /*00b0*/         {         SHR R3, R2.reuse, 0x1e;
        /*00b8*/                   STL [R1+0x10], R5;        }
        /*00c8*/                   ISCADD R2.CC, R2, c[0x0][0x148], 0x2;
        /*00d0*/                   STL [R1+0x8], R2;
        /*00d8*/                   SHR R5, R4, 0x1e;
        /*00e8*/                   IADD.X R2, R3, c[0x0][0x14c];
        /*00f0*/         {         ISCADD R4.CC, R4, c[0x0][0x148], 0x2;
        /*00f8*/                   STL [R1+0x4], R2;        }

Затем для 255 - без вхожденияSTL:

//--------------------- .text._Z6kerneliiPKiPi    --------------------------
    .section    .text._Z6kerneliiPKiPi,"ax",@progbits
    .sectioninfo    @"SHI_REGISTERS=124"
    .align  32
        .global         _Z6kerneliiPKiPi
        .type           _Z6kerneliiPKiPi,@function
        .size           _Z6kerneliiPKiPi,(.L_25 - _Z6kerneliiPKiPi)
        .other          _Z6kerneliiPKiPi,@"STO_CUDA_ENTRY STV_DEFAULT"
_Z6kerneliiPKiPi:
.text._Z6kerneliiPKiPi:
        /*0008*/                   MOV R1, c[0x0][0x20];
        /*0010*/                   S2R R0, SR_TID.X;
        /*0018*/                   ISETP.GE.AND P0, PT, R0, c[0x0][0x144], PT;
        /*0028*/                   NOP;
        /*0030*/                   NOP;
        /*0038*/               @P0 EXIT;
        /*0048*/                   MOV R46, c[0x0][0x144];
        /*0050*/                   IADD R47, RZ, -c[0x0][0x140];
        /*0058*/                   IADD32I R46, R46, -0x5;
        /*0068*/                   SHL R47, R47, 0x2;
.L_3:
        /*0070*/                   ISETP.LT.AND P0, PT, R46, 0x6, PT;
        /*0078*/               @P0 BRA `(.L_1);
        /*0088*/                   MOV R2, c[0x0][0x140];
        /*0090*/                   ISCADD R2, R2, R0, 0x6;
        /*0098*/                   SHR R27, R2.reuse, 0x1e;
        /*00a8*/                   ISCADD R26.CC, R2, c[0x0][0x148], 0x2;
        /*00b0*/                   SHR R48, R47, 0x1f;
        /*00b8*/                   IADD.X R27, R27, c[0x0][0x14c];
        /*00c8*/         {         IADD R44.CC, R47.reuse, R26;
        /*00d0*/                   LDG.E R49, [R26];        }
        /*00d8*/                   IADD.X R45, R48.reuse, R27;
        /*00e8*/         {         IADD R42.CC, R47.reuse, R44  SLOT 0;
        /*00f0*/                   LDG.E R44, [R44]  SLOT 1;        }
        /*00f8*/                   IADD.X R43, R48.reuse, R45;
        /*0108*/         {         IADD R38.CC, R47, R42  SLOT 0;
        /*0110*/                   LDG.E R42, [R42]  SLOT 1;        }

Очень похоже на вас, я бы хотел, чтобы все это было лучше задокументировано.

...