说我有这个玩具代码:
#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个连续的非平凡内核,它们具有大量的设置和拆卸开销)。
答案 0 :(得分:3)
最简单,最普遍的答案是否定的。我只需要找到一个范例,范例就是为了支持它而破坏的。让我们自己提醒:
__syncthreads()
是块级执行障碍,但不是设备范围的执行障碍。唯一定义的设备范围执行障碍是内核启动(假设我们正在讨论将内核发布到同一个流中,以便顺序执行)。
特定内核启动的线程块可以按任何顺序执行 。
我们说我们有两个功能:
让我们假设向量反转不是就地操作(输出与输入不同),并且每个线程块处理一个块大小的向量块,读取元素并存储到输出向量中的适当位置。
为了保持简单,我们假设我们只有(需要)两个线程块。对于第一步,块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;问题,表现出很高的独立性。