繁体   English   中英

CUDA每线程数组有不同的类型

[英]CUDA per-thread arrays with different types

我的CUDA内核的每个实例(即每个线程 )需要三个具有不同类型的私有数组。

例如

__global__ void mykernel() {
    type1 a[aLen];
    type2 b[bLen];
    type3 c[cLen];

    ...
}

这些类型的大小在编译时之前是未知的,并且长度aLenbLencLen是动态的。

当然,我必须为整个块分配一个共享内存的实例。

void caller() {
    int threadsPerCUDABlock = ...
    int CUDABlocks = ...

    int threadMemSize = 
        aLen*sizeof(type1) + bLen*sizeof(type2) + cLen*sizeof(type3);

    int blockMemSize = threadsPerCUDABlock * threadMemSize;

    mykernel <<< CUDABlocks, threadsPerCUDABlock, blockMemSize >>>();
}

然后,每个线程的任务是确定共享内存的哪个分区是其私有内存空间,以及如何将其划分为3种类型的子数组。 在这个例子中,我组织共享内存数组以具有结构:

[ thread0_a, thread0_b, thread0_c,  thread1_a, ...]

我不确定如何最好地在内核中解压缩这个结构。 我已经尝试传递每个线程的私有空间的字节数,并且最初假设内存空间是像char这样的1字节类型:

mykernel <<< CUDABlocks, threadsPerCUDABlock, blockMemSize >>>(threadMemSize);
__global__ void mykernel(int threadMemSize) {

    extern __shared__ char sharedMem[];

    char* threadMem = &sharedMem[threadMemSize*threadIdx.x]
    type1 *a = (type1*) threadMem;
    type2 *b = (type2*) &a[aLen];
    type3 *c = (type3*) &b[bLen];

    ...
}

这没有用(虽然没有任何错误,很难调试),但我不相信它甚至应该在原则上工作。 我不能保证例如type1type2type3类型的大小严格减少。

那么一般来说这样做的正确范例是什么? 也就是说,解压缩不同类型和大小的多个每线程数组的既定方法?

预赛

通常,出于性能原因,人们对GPU计算感兴趣 - 使代码运行得更快。 因此,在尝试做出决定时,我们会将性能作为指导。

我认为你在问题中提供的草图存在的一个问题是CUDA中自然对齐要求之一 拾取任意指针并将其类型转换为其他类型可能会与此相反。 如果您的代码中存在此类问题,则cuda-memcheck工具应该能够发现它。

在C ++中放置线程专用数组的典型位置是本地内存,而我认为CUDA也不例外。 但是,CUDA C ++至少不支持可变长度数组 在您的问题中,您勾勒出使用共享内存作为代理。 您认为(我假设)的一个含义是尽管在编译时不知道这些数组的大小,但必须有一个大小的上限,因为共享内存可能会为每个线程块施加低至48KB的限制。 因此,如果线程块中有1024个线程,则每个线程的最大组合数组大小将限制为48个字节。 每个块有512个线程,可以想象每个线程有96个字节。 如果您使用共享内存,这些将归因于共享内存限制。

因此,另一种方法(如果您可以遵守这些低限制)将简单地上限所需的本地内存,并静态地定义每个线程的该大小(或3)的本地内存数组。 单个阵列必须在各种阵列之间进行分区,并注意已经提到的对齐。 但是考虑到你的方法建议的小尺寸(例如总共~96个字节),使用上限固定大小的本地数组(不是共享内存)可能是有利的。

CUDA中的本地内存最终由与全局内存相同的物理资源(GPU DRAM内存)支持。 然而,如果每个线程正在访问它们自己的本地存储器中的特定元素,那么如果该访问需要由DRAM提供服务,那么跨线程的效果将等同于合并访问。 这意味着以某种方式交换每线程本地存储。 如果我们提出自己的可变长度数组实现,那么出于性能原因,这种交错特性也是我们需要注意的。 它同样适用于全局内存代理(以启用合并)或共享内存代理(以避免银行冲突)。

除了出于性能原因而需要交错访问之外, 喜欢共享存储器实现的可能性能原因是共享存储器的广泛使用会对占用率产生负面影响,从而对性能产生负面影响。 许多其他地方都涉及到这个主题,所以我不会在这里进一步深入研究。

实现

本地记忆

如上所述,我认为关于您使用共享内存的建议的一个隐含假设是,必须有一些(相当小的)上限到所需数组的实际大小。 如果是这种情况,使用3个分配上限大小的数组可能是有利的:

const int Max_aLen = 9;
const int Max_bLen = 5;
const int Max_cLen = 9;
__global__ void mykernel() {
    type1 a[Max_aLen];
    type2 b[Max_bLen];
    type3 c[Max_cLen];

    ...
}

在我看来,每个线程使用高达8k字节的本地内存不应该是一个主要问题,但它可能取决于你的GPU和内存大小, 下面提到/链接分析应该表明任何问题。 当然,每个线程的低级别/限制例如~96个字节应该不是问题。

全球记忆

我相信最简单和最灵活的方法是通过全局内存为这种可变长度数组提供存储,并将指针传递给内核。 这允许我们通过例如cudaMalloc为每个阵列分配存储,并且我们可以单独处理单独的阵列,并且我们需要相对较少地关注对齐要求。 由于我们假装这些全局数组将被用作线程专用的,我们将希望安排我们的索引来为每个线程创建交叉存储/访问,这将有助于合并。 对于您的3阵列示例,它可能看起来像这样:

#include <stdio.h>

typedef unsigned type1;
typedef char     type2;
typedef double   type3;

__global__ void mykernel(type1 *a, type2 *b, type3 *c) {

  size_t stride = (size_t)gridDim.x * blockDim.x;
  size_t idx = (size_t)blockIdx.x*blockDim.x+threadIdx.x;
  a[7*stride+idx] = 4;    // "local"  access to a
  b[0*stride+idx] = '0';  // "local"  access to b
  c[3*stride+idx] = 1.0;  // "local"  access to c
}

int main(){
  // 1D example
  type1 *d_a;
  type2 *d_b;
  type3 *d_c;
  // some arbitrary choices to be made at run-time
  size_t alen = 27;
  size_t blen = 55;
  size_t clen = 99;
  int nTPB = 256;
  int nBLK = 768;
  size_t grid = (size_t)nBLK*nTPB;
  // allocate
  cudaMalloc(&d_a, alen*grid*sizeof(type1));
  cudaMalloc(&d_b, blen*grid*sizeof(type2));
  cudaMalloc(&d_c, clen*grid*sizeof(type3));
  // launch
  mykernel<<<nBLK, nTPB>>>(d_a, d_b, d_c);
  cudaDeviceSynchronize();
}

对这种方法的一种可能的批评是它可能会消耗比本地内存方法更多的设备内存(它可能也会消耗更少,取决于相对于GPU类型的网格大小 )。 然而,这可以通过诸如网格跨步循环的方法来限制网格大小来管理。

共享内存

由于我们只有一个指向共享内存的指针用于动态分配的共享内存,如果我们对共享内存执行某些操作,我们将不得不小心注意对齐。 下面是分配和定位正确对齐的指针所需的计算类型的示例:

#include <stdio.h>

typedef unsigned type1;
typedef char     type2;
typedef double   type3;

__global__ void mykernel(int b_round_up, int c_round_up) {

  extern __shared__ char sdata[];
  type1 *a = (type1 *)sdata;
  type2 *b = (type2 *)(sdata + b_round_up);
  type3 *c = (type3 *)(sdata + c_round_up);
  size_t stride = blockDim.x;
  size_t idx = threadIdx.x;
  a[7*stride+idx] = 4;    // "local"  access to a
  b[0*stride+idx] = '0';  // "local"  access to b
  c[3*stride+idx] = 1.0;  // "local"  access to c
}

int main(){
  // 1D example
  // some arbitrary choices to be made at run-time
  int alen = 9;
  int blen = 5;
  int clen = 9;
  int nTPB = 256;
  int nBLK = 1;
  // calculate aligned shared mem offsets
  int b_round_up = (((nTPB*alen*sizeof(type1) + sizeof(type2)-1)/sizeof(type2))*sizeof(type2)); // round up
  int c_round_up = (((b_round_up + nTPB*blen*sizeof(type2) + sizeof(type3)-1)/sizeof(type3))*sizeof(type3)); // round up
  // allocate + launch
  mykernel<<<nBLK, nTPB, c_round_up + nTPB*clen*sizeof(type3)>>>(b_round_up,c_round_up);
  cudaDeviceSynchronize();
}

我并不是说我的任何代码都没有缺陷,但你可以从相对代码复杂性的角度看,本地或全局选项是首选。 我无法想象共享内存实现首选的原因或情况。

我的CUDA内核的每个实例(即每个线程)

线程不是内核的实例。 线程是块的一部分,块形成网格,网格运行内核函数。

我的CUDA内核的每个[thread]都需要三个私有数组

是吗? 我对此表示怀疑。 我猜你的计算问题可以重新制定,以便许多线程合作并在单个这样的三元组阵列上工作(或者可能是几个这样的三元组)。

然后,每个线程的任务是确定共享内存的哪个分区是其私有内存空间

不必要。 即使您坚持使用3个私有数组,也可以将它们放在“本地内存”中(这实际上只是线程专用的全局内存)。 如果每个线程只使用少量本地内存,它可能都适合L2缓存,虽然最佳速度比共享内存慢 - 但由于各种原因(例如共享内存库冲突),有时会有意义。

或者,如果小阵列的整体尺寸非常小,您可以考虑将它们粘贴到寄存器中。 这意味着你不能使用它们的索引访问(这是一个非常紧缩的条件),但寄存器超快,并且有很多 - 例如,超过共享内存大小。

无论你选择何种内存空间 - 总是测量 ,并使用分析器来确定这是否是你的瓶颈; 是否会影响占用率,或有效使用GPU核心的功能单元等。 如果您对所得到的内容不满意,请尝试其他选项之一。

我组织共享内存数组有结构:

 [ thread0_a, thread0_b, thread0_c, thread1_a, ...] 

是的......这可能不是一个好选择。 你看,共享内存安排在银行; 如果你的warp的通道(warp中的线程)试图从同一个 bank访问数据,这些访问将被序列化。 例如,假设每个数组的大小是128个字节的倍数。 如果warp中的所有线程都通过访问a[0] (经常发生)开始工作 - 他们都将尝试访问同一个银行,并且你将获得32倍的减速。

如果warp中的通道倾向于访问数组中的相同索引,则最好交错数组,即使用以下排列(使用您的说明方式):

[ thread_0_a[0], thread_1_a[0], thread_2_a[0], ... thread_n_a[0], thread_0_a[1], ... ]

这还有一个额外的好处,你需要知道的是线程的私有数组的最大长度,以及线程的数量,以确定每个线程的数组的确切位置。 另一方面,这意味着你可以“打包”少数组,而不是你可能。 但那还不错! 每个块使用更少的warp,你应该还可以。

我应该提到@RobertCrovella的答案也有类似的观点。

警告 :注意我没有说,如果在回答这部分的开始。 情况可能是线程的访问模式不同。 如果是,隔行扫描可能对您没有帮助。 再次分析和测量检查可能是一个好主意。


由于我的答案提出了更为深远的变化(因为我没有时间),所以我不会详细介绍。 如果我某处不清楚,请随意发表评论。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM