CUDA中#pragma unroll N最佳值的确定

CUDA中#pragma unroll N最佳值的确定,cuda,pragma,loop-unrolling,Cuda,Pragma,Loop Unrolling,我了解#pragma unroll的工作原理,但如果我有以下示例: __global__ void test_kernel( const float* B, const float* C, float* A_out) { int j = threadIdx.x + blockIdx.x * blockDim.x; if (j < array_size) { #pragma unroll for (int i = 0; i < LIMIT; i++) {

我了解
#pragma unroll
的工作原理,但如果我有以下示例:

__global__ void
test_kernel( const float* B, const float* C, float* A_out)
{
  int j = threadIdx.x + blockIdx.x * blockDim.x;
  if (j < array_size) {
     #pragma unroll
     for (int i = 0; i < LIMIT; i++) {
       A_out[i] = B[i] + C[i];
     }
  }
}
全局无效
测试内核(常量浮点*B,常量浮点*C,浮点*A)
{
int j=threadIdx.x+blockIdx.x*blockDim.x;
if(j<数组大小){
#布拉格展开
对于(int i=0;i

我想在上面的内核中确定
LIMIT
的最佳值,它将以
x
线程数和
y
块数启动。
LIMIT
可以是从
2
1的任何地方。您的示例内核是完全串行的,在任何情况下都不是循环展开的一个有用的实际用例,但让我们仅限于编译器将执行多少循环展开的问题

以下是内核的可编译版本,带有一些模板装饰:

template<int LIMIT>
__global__ void
test_kernel( const float* B, const float* C, float* A_out, int array_size)
{
  int j = threadIdx.x + blockIdx.x * blockDim.x;
  if (j < array_size) {
     #pragma unroll
     for (int i = 0; i < LIMIT; i++) {
       A_out[i] = B[i] + C[i];
     }
  }
}

template __global__ void test_kernel<4>(const float*, const float*, float*, int);
template __global__ void test_kernel<64>(const float*, const float*, float*, int);
template __global__ void test_kernel<256>(const float*, const float*, float*, int);
template __global__ void test_kernel<1024>(const float*, const float*, float*, int);
template __global__ void test_kernel<4096>(const float*, const float*, float*, int);
template __global__ void test_kernel<8192>(const float*, const float*, float*, int);
模板
__全局无效
测试内核(常量浮点*B、常量浮点*C、浮点*A、整数数组大小)
{
int j=threadIdx.x+blockIdx.x*blockDim.x;
if(j<数组大小){
#布拉格展开
对于(int i=0;i
您可以将其编译为PTX,并亲自查看(至少使用CUDA 7发行版编译器和默认的compute capability 2.0目标体系结构),最高
LIMIT=4096
的内核完全展开。
LIMIT=8192
案例未展开。如果您像我一样有更多的耐心,您可能可以尝试使用模板来找到这段代码的确切编译器限制,尽管我怀疑这是否特别有指导意义


您还可以通过编译器自己看到,所有高度展开的版本都使用相同数量的寄存器(因为您的内核非常简单)。

CUDA利用了线程级并行性,您可以通过将工作拆分为多个线程和指令级并行性来公开线程级并行性,CUDA通过在编译代码中搜索独立指令来查找

@talonmies的结果显示,您的循环可能会在4096到8192次迭代之间展开,这让我感到惊讶,因为在现代CPU上,循环展开的回报会急剧下降,其中大多数迭代开销都通过分支预测和推测执行等技术进行了优化

在CPU上,我怀疑展开超过(比如)10-20次迭代会有多大好处,展开的循环会占用指令缓存中更多的空间,因此展开也会有成本。CUDA编译器在决定展开多少时将考虑成本/收益权衡。所以问题是,展开4096次以上的迭代有什么好处?我认为这可能是因为它给了GPU更多的代码,它可以在其中搜索独立的指令,然后使用指令级并行性并发运行

循环的主体是
A_out[i]=B[i]+C[i]。由于循环中的逻辑不访问外部变量,也不访问循环早期迭代的结果,因此每个迭代都独立于所有其他迭代。因此,
i
不必按顺序增加。即使循环以完全随机的顺序在
0
LIMIT-1
之间迭代
i
的每个值,最终结果也是相同的。该特性使循环成为并行优化的良好候选对象

但有一个陷阱,这就是我在评论中提到的。只有当
A
缓冲区与
B
C
缓冲区分开存储时,循环的迭代才是独立的。如果
A
缓冲区与内存中的
B
和/或
C
缓冲区部分或完全重叠,则会在不同迭代之间创建连接。一次迭代现在可以通过写入
A
来更改另一次迭代的
B
C
输入值。因此,根据两个迭代中的哪个先运行,您会得到不同的结果

指向内存中相同位置的多个指针称为指针别名。因此,一般来说,指针别名会导致代码段之间的“隐藏”连接看起来是分离的,因为一段代码通过一个指针进行的写入会改变另一段代码从另一个指针读取的值。默认情况下,CPU编译器生成的代码考虑了可能的指针别名,生成的代码不管结果如何都能产生正确的结果。问题是CUDA做了什么,因为回到talonmies的测试结果,我能看到如此大量展开的唯一原因是它打开了指令级并行的代码。但这意味着CUDA在这种特殊情况下不考虑指针别名

Re。关于运行多个线程的问题,当增加线程数时,常规串行程序不会自动成为并行程序。您必须确定可以并行运行的工作部分,然后在CUDA内核中表示出来。这就是所谓的线程级并行性,它是提高代码性能的主要来源。此外,CUDA将在每个内核中搜索独立的指令,并可能运行这些指令