CUDA指针算法导致未恢复的内存访问?

CUDA指针算法导致未恢复的内存访问?,cuda,nvvp,Cuda,Nvvp,我正在使用一个CUDA内核,它必须对指向指针的指针进行操作。内核基本上执行大量非常小的缩减,最好以串行方式完成,因为缩减的大小为Nptrs=3-4。 以下是内核的两种实现: __global__ void kernel_RaiseIndexSLOW(double*__restrict__*__restrict__ A0, const double*__restrict__*__restrict__ B0, const double*__restrict__*__r

我正在使用一个CUDA内核,它必须对指向指针的指针进行操作。内核基本上执行大量非常小的缩减,最好以串行方式完成,因为缩减的大小为Nptrs=3-4。 以下是内核的两种实现:

__global__
void kernel_RaiseIndexSLOW(double*__restrict__*__restrict__ A0,
        const double*__restrict__*__restrict__ B0,
        const double*__restrict__*__restrict__ C0,
        const int Nptrs, const int Nx){
      const int i = blockIdx.y;
      const int j = blockIdx.z;
      const int idx = blockIdx.x*blockDim.x + threadIdx.x;
      if(i<Nptrs) {
         if(j<Nptrs) {
           for (int x = idx; x < Nx; x += blockDim.x*gridDim.x){
              A0gpu[i+3*j][x] = B0gpu[i][x]*C0gpu[3*j][x]
                       +B0gpu[i+3][x]*C0gpu[1+3*j][x]
                       +B0gpu[i+6][x]*C0gpu[2+3*j][x];               
           }
         }
       }
 }

__global__
void kernel_RaiseIndexsepderef(double*__restrict__*__restrict__  A0gpu, 
               const double*__restrict__*__restrict__ B0gpu,
               const double*__restrict__*__restrict__ C0gpu,
               const int Nptrs, const int Nx){
const int i = blockIdx.y;
const int j = blockIdx.z;
const int idx = blockIdx.x*blockDim.x + threadIdx.x;
if(i<Nptrs) {
  if(j<Nptrs){
    double*__restrict__ A0ptr = A0gpu[i+3*j];
    const double*__restrict__ B0ptr0 = B0gpu[i];
    const double*__restrict__ C0ptr0 = C0gpu[3*j];
    const double*__restrict__ B0ptr1 = B0ptr0+3;
    const double*__restrict__ B0ptr2 = B0ptr0+6;
    const double*__restrict__ C0ptr1 = C0ptr0+1;
    const double*__restrict__ C0ptr2 = C0ptr0+2;

    for (int x = idx; x < Nx; x +=blockDim.x *gridDim.x){
      double d2 = C0ptr0[x];
      double d4 = C0ptr1[x]; //FLAGGED
      double d6 = C0ptr2[x]; //FLAGGED
      double d1 = B0ptr0[x];
      double d3 = B0ptr1[x]; //FLAGGED
      double d5 = B0ptr2[x]; //FLAGGED
      A0ptr[x] = d1*d2 + d3*d4 + d5*d6;

    }
   }                                                                        
  }
 }
\u全局__
void kernel\u raiseIndexLow(双*\uuuuu restrict\uuuu*\uuu restrict\uuuuuu A0,
常数双*\uuuuu限制\uuu*\ uuuu限制\uuub0,
常数双*\uuuuuuuuuuuuuuuuuc0,
常数int Nptrs,常数int Nx){
常数int i=块idx.y;
const int j=blockIdx.z;
const int idx=blockIdx.x*blockDim.x+threadIdx.x;

如果(i要理解这种行为,需要了解到目前为止所有CUDA GPU都执行指令。在发出从内存加载操作数的指令后,其他独立指令仍将继续执行。但是,一旦遇到依赖于来自内存的操作数的指令,所有进一步的操作都将执行此指令指令流暂停,直到操作数可用

在“sepderef”示例中,在对操作数求和之前从内存加载所有操作数,这意味着每个循环迭代只会产生一次全局内存延迟(每个循环迭代有六个加载,但它们都可以重叠。只有循环的第一次加法将暂停,直到其操作数可用。暂停后,所有其他加法操作数将随时可用或很快可用)

在“慢速”示例中,从内存加载和添加是混合的,因此每次循环操作都会产生多次全局内存延迟


您可能想知道为什么编译器在计算前不自动重新排序加载指令。CUDA编译器过去常常非常积极地进行此操作,在操作数等待使用的位置使用额外的寄存器。然而,CUDA 8.0在这方面似乎远没有那么积极,更多地遵循sou中的指令顺序rce代码。这使程序员有更好的机会以最佳的方式构造代码。同时,即使在以前的编译器版本正确的情况下,也会给程序员带来更大的负担来显式地调度指令。

要了解这种行为,需要知道目前为止所有的CUDA GPU都执行te指令。在发出从内存加载操作数的指令后,其他独立指令仍将继续执行。但是,一旦遇到依赖于来自内存的操作数的指令,此指令流上的所有后续操作都将暂停,直到操作数可用为止

在“sepderef”示例中,在对操作数求和之前从内存加载所有操作数,这意味着每个循环迭代只会产生一次全局内存延迟(每个循环迭代有六个加载,但它们都可以重叠。只有循环的第一次加法将暂停,直到其操作数可用。暂停后,所有其他加法操作数将随时可用或很快可用)

在“慢速”示例中,从内存加载和添加是混合的,因此每次循环操作都会产生多次全局内存延迟


您可能想知道为什么编译器在计算前不自动重新排序加载指令。CUDA编译器过去常常非常积极地进行此操作,在操作数等待使用的位置使用额外的寄存器。然而,CUDA 8.0在这方面似乎远没有那么积极,更多地遵循sou中的指令顺序rce代码。这为程序员提供了更好的机会,以最佳的方式构造代码。同时,它也给程序员带来了更大的负担,即使在以前的编译器版本正确的情况下,也要显式地调度指令。

您应该确保在内核的一个级别上使用
const
参数-不应覆盖的级别。另外“必须对指向指针的指针进行操作”-虽然这不是你的问题,但我很怀疑;很可能你可以绕过它。你应该确保在内核参数的一个级别上使用
const
,这个级别不应该被覆盖。此外,“必须对指向指针的指针进行操作”-虽然这不是你的问题,但我很怀疑;很可能你可以绕过它。这非常有用,谢谢。但是,为什么探查器会将'sepderef'内核中的//标记行标记为未合并?还要注意,循环仅用于捕获问题大小大于网格大小的情况。在在我的实际测试中,我们只进行了一次循环迭代。这些行可能会被标记,因为访问没有对齐,所以它们依赖于缓存来实现全部吞吐量。我不会太担心它们。我更愿意考虑einpoklum的评论,是否真的不可避免地使用指向指针的指针,这意味着将延迟增加一倍或每次内存访问(或至少每次循环迭代)。好的,除了被标记外,第二个内核中的全局内存负载效率从~100%下降到约56%,但可能确实存在对齐问题。很难避免指向指针的指针,因为这些指针是用来作为自动生成的内核,以加速在cl上运行的非常复杂的CPU代码ass不会连续存储单个数组。不过,也许有一种方法。这非常有用,谢谢。但是,为什么探查器会将“sepderef”内核中的//标记行标记为未合并?还要注意,循环仅用于捕获问题大小大于网格大小的情况。在我的实际测试中,我们将继续只需执行一次循环迭代。这些行可能会被标记,因为访问没有对齐,所以它们依赖于缓存来获得全部吞吐量。