Cuda合并了内存加载行为

时间:2013-03-25 20:56:22

标签: memory cuda load

我正在使用一个结构数组,我希望每个块在共享内存中加载一个数组的单元格。例如:块0将在共享存储器中加载数组[0],块1将加载数组[1]。

为了做到这一点,我在float *中强制转换结构数组,以便尝试合并内存访问。

我有两个版本的代码

版本1

__global__ 
void load_structure(float * label){

  __shared__ float shared_label[48*16];
  __shared__ struct LABEL_2D* self_label;


  shared_label[threadIdx.x*16+threadIdx.y] = 
          label[blockIdx.x*sizeof(struct LABEL_2D)/sizeof(float) +threadIdx.x*16+threadIdx.y];
  shared_label[(threadIdx.x+16)*16+threadIdx.y] = 
          label[blockIdx.x*sizeof(struct LABEL_2D)/sizeof(float) + (threadIdx.x+16)*16+threadIdx.y];
  if((threadIdx.x+32)*16+threadIdx.y < sizeof(struct LABEL_2D)/sizeof(float))  {
    shared_label[(threadIdx.x+32)*16+threadIdx.y] = 
          label[blockIdx.x*sizeof(struct LABEL_2D)/sizeof(float) +(threadIdx.x+32)*16+threadIdx.y];
   }

  if(threadIdx.x == 0){
    self_label = (struct LABEL_2D *) shared_label;
  }
  __syncthreads();
  return;
}

...

dim3 dimBlock(16,16);
load_structure<<<2000,dimBlock>>>((float*)d_Label;

计算时间:0.740032 ms

版本2

__global__ 
void load_structure(float * label){

  __shared__ float shared_label[32*32];
  __shared__ struct LABEL_2D* self_label;

  if(threadIdx.x*32+threadIdx.y < *sizeof(struct LABEL_2D)/sizeof(float))
    shared_label[threadIdx.x*32+threadIdx.y] = 
              label[blockIdx.x*sizeof(struct LABEL_2D)/sizeof(float)+threadIdx.x*32+threadIdx.y+];


  if(threadIdx.x == 0){
      self_label = (struct LABEL_2D *) shared_label;
    }
  __syncthreads();
  return;
}

dim3 dimBlock(32,32);
load_structure<<<2000,dimBlock>>>((float*)d_Label);

计算时间:2.559264 ms

在这两个版本中,我都使用了nvidia分析器,全局负载效率为8%。

我有两个问题: 1 - 我不明白为什么时间有所不同。 2 - 我的电话合并了吗?

我正在使用具有2.1计算能力的视频卡(32线程/包装)

2 个答案:

答案 0 :(得分:2)

您的全局负载未合并。 8%相当低,你可能做的最差的是3%。

我认为这样做的主要原因是您基于threadIdx.x和threadIdx.y进行索引的方式。让我们考虑来自第二个内核的这行代码(第一个内核有类似的问题):

shared_label[threadIdx.x*32+threadIdx.y] =  label[blockIdx.x*sizeof(struct LABEL_2D)/sizeof(float)+threadIdx.x*32+threadIdx.y];

特别要考虑这个索引:

threadIdx.x*32+threadIdx.y

CUDA经线按X,Y,Z的顺序分组。这意味着warp中快速变化的索引将首先出现在X索引上,然后出现在Y上,然后出现在Z上。例如,如果我有一个16x16的线程块,那么第一个warp将具有threadIdx.x跨越的线程从0到15并且threadIdx.y仅跨越0到1.在这种情况下,相邻的线程将主要具有相邻的threadIdx.x索引。

您的代码的结果是您因索引而破坏了合并。如果您可以重新构建加载和存储以使用此类索引:

threadIdx.y*32+threadIdx.x

您将突然看到全球负载效率的显着提升。 (您的共享内存使用情况也可能更好。)

我意识到你有两个问题,当我想到第一个时,我很困惑。你告诉我们“计算时间”是大约。第二次实现的时间长4倍,但可能你指的是compute_interpolation内核,你根本没有显示任何细节,除非在第二种情况下你启动了4倍的线程。也许这里没有神秘感。您尚未显示任何代码。并使用内核在共享内存中加载一堆东西然后退出也没有任何意义。共享内存内容不会从一个内核调用持续到下一个内核。

答案 1 :(得分:0)

我解决了我的问题,访问内存模式在以前的版本中不正确。 在阅读了cuda最佳实践指南的第6.2.1段之后,我发现如果它们一致,访问速度会更快。

为了对我的访问模式进行分析,我在结构中添加了一个“假”变量,以便将结构大小除以128(现金大小行)。

通过这个策略,我获得了良好的性能:为了将2000结构加载到2000块,它只用了0.16ms。

以下是代码的版本:

struct TEST_ALIGNED{
  float data[745];
  float aligned[23];
}; 


__global__
void load_structure_v4(float * structure){

  // Shared structure within a block
  __shared__ float s_structure[768];
  __shared__ struct TEST_ALIGNED * shared_structure;

  s_structure[threadIdx.x] = 
    structure[blockIdx.x*sizeof(struct TEST_ALIGNED)/sizeof(float) + threadIdx.x];
  s_structure[threadIdx.x + 256] = 
    structure[blockIdx.x*sizeof(struct TEST_ALIGNED)/sizeof(float) + threadIdx.x + 256];
  if(threadIdx.x < 745)
        s_structure[threadIdx.x + 512] = 
            structure[blockIdx.x*sizeof(struct TEST_ALIGNED)/sizeof(float) +    threadIdx.x + 512];
  if(threadIdx.x == 0)
       shared_structure = (struct TEST_ALIGNED*) s_structure;

  __syncthreads();

    return;
}

dim3 dimBlock(256);
load_structure_v4<<<2000,dimBlock>>>((float*)d_test_aligned);

我仍在寻找优化,如果找到一些优惠,我会在此发布。