可以使用__syncthreads()合并CUDA内核吗?

时间:2016-08-03 23:45:08

标签: c++ cuda

说我有这个玩具代码:

#define N (1024*1024)
#define M (1000000)

__global__ void cudakernel1(float *buf)
{
   int i = threadIdx.x + blockIdx.x * blockDim.x;
   buf[i] = 1.0f * i / N;
   for(int j = 0; j < M; j++)
      buf[i] *= buf[i];
}

__global__ void cudakernel2(float *buf)
{
   int i = threadIdx.x + blockIdx.x * blockDim.x;
   for(int j = 0; j < M; j++)
      buf[i] += buf[i];
}

int main()
{
   float data[N];
   float *d_data;
   cudaMalloc(&d_data, N * sizeof(float));
   cudakernel1<<<N/256, 256>>>(d_data);
   cudakernel2<<<N/256, 256>>>(d_data);
   cudaMemcpy(data, d_data, N * sizeof(float), cudaMemcpyDeviceToHost);
   cudaFree(d_data); 
}

我可以像这样合并两个内核:

#define N (1024*1024)
#define M (1000000)

__global__ void cudakernel1_plus_2(float *buf)
{
   int i = threadIdx.x + blockIdx.x * blockDim.x;
   buf[i] = 1.0f * i / N;
   for(int j = 0; j < M; j++)
      buf[i] *= buf[i];

   __syncthreads();

   for(int j = 0; j < M; j++)
      buf[i] += buf[i];
}

int main()
{
   float data[N];
   float *d_data;
   cudaMalloc(&d_data, N * sizeof(float));
   cudakernel1_plus_2<<<N/256, 256>>>(d_data);
   cudaMemcpy(data, d_data, N * sizeof(float), cudaMemcpyDeviceToHost);
   cudaFree(d_data); 
}

一般情况下,两个连续的内核采用相同的块和线程参数是否可以与中间__syncthreads()合并?

(我的实际案例是6个连续的非平凡内核,它们具有大量的设置和拆卸开销)。

1 个答案:

答案 0 :(得分:3)

最简单,最普遍的答案是否定的。我只需要找到一个范例,范例就是为了支持它而破坏的。让我们自己提醒:

  1. __syncthreads()是块级执行障碍,但不是设备范围的执行障碍。唯一定义的设备范围执行障碍是内核启动(假设我们正在讨论将内核发布到同一个流中,以便顺序执行)。

  2. 特定内核启动的线程块可以按任何顺序执行

  3. 我们说我们有两个功能:

    1. 反转向量的元素
    2. 求和向量元素
    3. 让我们假设向量反转不是就地操作(输出与输入不同),并且每个线程块处理一个块大小的向量块,读取元素并存储到输出向量中的适当位置。

      为了保持简单,我们假设我们只有(需要)两个线程块。对于第一步,块0将向量的左侧复制到右侧(颠倒顺序)并从右到左块1复制:

      1 2 3 4 5 6 7 8
      |blk 0 |blk 1  |
           \ | /
             X
            /| \
           v |  v
      8 7 6 5 4 3 2 1
      

      对于第二步,在经典的并行缩减方式中,块零对输出向量的左手元素求和,块1对右手元素求和:

      8 7 6 5 4 3 2 1
        \  /   \  /
        blk0    blk1
         26      10
      

      只要第一个函数在kernel1中发布,第二个函数在kernel2中发布,在kernel1之后的同一个流中,这一切都可行。对于每个内核,块0在块1之前执行无关紧要,反之亦然。

      如果我们组合操作以便我们有一个内核,并且块0复制/将向量的前半部分反转到输出向量的后半部分,则执行__syncthreads(),然后对第一个求和输出矢量的一半,事情很可能会破裂。如果块0在块1之前执行,则第一步将很好(向量的复制/反转),但第二步将在尚未填充的输出数组的一半上运行,因为第1块尚未开始执行。计算出的总和是错误的。

      在没有尝试提供正式证据的情况下,我们可以看到,在上述情况下,一个区块的数据移动了#34;域&#34;对于另一个块&#34;域&#34;,我们冒着破坏事物的风险,因为先前的设备范围同步(内核启动)是正确性所必需的。但是,如果我们可以限制&#34;域&#34;阻止后续操作消耗的任何数据以前的操作 ,然后__syncthreads()可能足以允许此策略正确性。 (之前的愚蠢示例很容易被重新设计以允许这样做,只需让块0负责输出向量的前半部分,从而复制后半部分输入向量,反之亦然。)

      最后,如果我们将数据范围限制为单个线程,那么我们甚至可以在不使用__syncthreads()的情况下进行此类组合。最后两个案例可能具有&#34;令人难以置信的平行&#34;问题,表现出很高的独立性