0

我正在为小输入数据(= 512 个元素)分析一个非常转储的排序算法。我正在调用一个从结构数组中读取合并的内核。

该结构如下所示:

struct __align__(8) Elements 
{
     float weight;
     int value;
};

nvprof 为 L1 未命中/命中和 gdl 指令提供以下指令计数:

                  Invocations    Avg       Min       Max  Event Name
        Kernel: sort(Elements*)
                      500         0         0         0  gld_inst_8bit
                      500         0         0         0  gld_inst_16bit
                      500      1024      1024      1024  gld_inst_32bit
                      500         0         0         0  gld_inst_64bit
                      500         0         0         0  gld_inst_128bit
                      500       120       120       120  l1_global_load_hit
                      500       120       120       120  l1_global_load_miss
                      500         0         0         0  uncached_global_load_tr.

如果我按以下方式更改结构的布局:

struct __align__(8) Elements 
{
     float weight;
     float value;
};

分析输出如下所示:

                  Invocations    Avg       Min       Max  Event Name
Device 0
        Kernel: sort(Elements*)
                      500         0         0         0  gld_inst_8bit
                      500         0         0         0  gld_inst_16bit
                      500         0         0         0  gld_inst_32bit
                      500       512       512       512  gld_inst_64bit
                      500         0         0         0  gld_inst_128bit
                      500         0         0         0  l1_global_load_hit
                      500       120       120       120  l1_global_load_miss
                      500         0         0         0  uncached_global_load_tr.

对执行时间没有任何影响,但我不明白为什么 GPU 在代码的第一个变体上执行 32 位加载指令而在第二个变体上执行 64 位指令。

内核使用 1 个块和 512 个线程调用(因此 l1_global_load_x 计数器可能不正确)。一切都发生在配备 CUDA 5.0 的 GeForce 480 上。

编辑: 排序内核(有点缩短):

__global__ void sort(Elements* nearest)
{
    ThreadIndex idx = index();

    __shared__ Elements temp[MAX_ELEMENTS];
    __shared__ int index_cache[MAX_ELEMENTS];

    temp[idx.x] = nearest[idx.x];

    WeightedElements elem = temp[idx.x];
    __syncthreads();

    int c = 0;

    // some index crunching 

    nearest[idx.x] = temp[c];
}
4

1 回答 1

1

其基本原因归结为编译器的代码生成。PTX 汇编器具有用于浮点和整数的不同虚拟寄存器状态空间,并且(我认为)不可能将 64 位加载到不同状态空间的两个寄存器中。因此编译器在混合整数/浮点结构中发出两个 32 位负载,但在浮点/浮点结构情况下可以将 64 位向量负载发出到两个寄存器中。

这可以通过考虑以下代码模型来说明:

struct __align__(8) ElementsB 
{
    float weight;
    float value;
};

struct __align__(8) ElementsA 
{
    float weight;
    int value;
};

template<typename T>
__global__ void kernel(const T* __restrict__ in, T* __restrict__ out, bool flag)
{
    int idx = threadIdx.x + blockIdx.x * blockDim.x;

    T ival = in[idx];
    if (flag) {
        out[idx] = ival;
    }
}


template __global__ void kernel<ElementsA>(const ElementsA *, ElementsA *, bool);
template __global__ void kernel<ElementsB>(const ElementsB *, ElementsB *, bool);

在这里,我们有您提到的两种结构,以及为这两种类型实例化的简单模板内核。如果我们查看编译器为 sm_20(CUDA 5.0 发行版编译器)发出的 PTX,差异是显而易见的。例如ElementsA

    ld.param.u32    %r4, [_Z6kernelI9ElementsAEvPKT_PS1_b_param_0];
    ld.param.u32    %r5, [_Z6kernelI9ElementsAEvPKT_PS1_b_param_1];
    ld.param.u8     %rc1, [_Z6kernelI9ElementsAEvPKT_PS1_b_param_2];
    cvta.to.global.u32      %r1, %r5;
    cvta.to.global.u32      %r6, %r4;
    .loc 2 16 1
    mov.u32         %r7, %ntid.x;
    mov.u32         %r8, %ctaid.x;
    mov.u32         %r9, %tid.x;
    mad.lo.s32      %r2, %r7, %r8, %r9;
    .loc 2 18 1
    shl.b32         %r10, %r2, 3;
    add.s32         %r11, %r6, %r10;
    ld.global.u32   %r3, [%r11+4];  // 32 bit integer load
    ld.global.f32   %f1, [%r11];  // 32 bit floating point load

(为强调而添加评论)

例如Element B

    ld.param.u32    %r3, [_Z6kernelI9ElementsBEvPKT_PS1_b_param_0];
    ld.param.u32    %r4, [_Z6kernelI9ElementsBEvPKT_PS1_b_param_1];
    ld.param.u8     %rc1, [_Z6kernelI9ElementsBEvPKT_PS1_b_param_2];
    cvta.to.global.u32      %r1, %r4;
    cvta.to.global.u32      %r5, %r3;
    .loc 2 16 1
    mov.u32         %r6, %ntid.x;
    mov.u32         %r7, %ctaid.x;
    mov.u32         %r8, %tid.x;
    mad.lo.s32      %r2, %r6, %r7, %r8;
    .loc 2 18 1
    shl.b32         %r9, %r2, 3;
    add.s32         %r10, %r5, %r9;
    ld.global.v2.f32        {%f9, %f10}, [%r10];  // 64 bit float2 load

两者之间没有性能损失的原因是底层硬件使用 128 字节获取来进行合并的扭曲级别负载,并且在这两种情况下,事务都会导致同一对 128 字节获取。

于 2013-08-27T16:31:42.603 回答