我写了一个小内核,使用并行缩减来求和2 ^ k个元素。 这里没什么新东西....我的矢量存储在全局存储器中,我将矢量的每个部分分配给不同的块并将每个块减少到一个位置。其余的我在CPU中做。
__global__ void sum(real *v, long int s){
long int ix = threadIdx.x;
long int shift = blockIdx.x*blockDim.x;
long int h = blockDim.x/2;
while (h >= 1){
if (ix < h){
v[ix + shift] = v[2*ix + shift] + v[2*ix + 1 + shift];
}
__syncthreads();
h = h / 2;
}
}
代码有效。然而,仔细检查后,我意识到它可能不起作用。所以我很困惑....可能是thread_id = 1,它对元素2和3求和,在thread_id = 0能够读取元素0和1之前将其和值写入位置1,从而使结果无效。 / p>
我会认为,为了安全起见,代码必须是
__global__ void sumsafe(real *v, long int s){
long int ix = threadIdx.x;
long int shift = blockIdx.x*blockDim.x;
real x = 0;
long int h = blockDim.x/2;
while (h >= 1){
if (ix < h){
x = v[2*ix + shift] + v[2*ix + 1 + shift];
}
__syncthreads();
if (ix < h){
v[ix + shift] = x;
}
__syncthreads();
h = h / 2;
}
}
所以我保证所有线程在开始更改它们之前读取它们的值。但正如我所说......两个代码都有效...他们的时间实际上也差不多。
为什么会这样?
我知道GPU并不能保证一个线程写入全局内存的内容对其他线程不可见。但它并不能保证这种情况永远不会发生。
任何想法!?我正在研发GTX 1080。
答案 0 :(得分:4)
你确实很“幸运”,因为CUDA不能保证warp的执行顺序。以下描述(这是猜想)不应被解释为声明您所展示的是一个好主意。没有人应该像这样减少。
但是对于一个小的测试用例(没有其他代码,并且在单个数据块上运行),我希望这可以工作。
从全局内存中读取通常是高延迟的。当执行遇到这行代码时:
v[ix + shift] = v[2*ix + shift] + v[2*ix + 1 + shift];
将转换为SASS指令,如下所示:
LD R0, v[2*ix + shift] (let's call this LD0)
LD R1, v[2*ix + 1 + shift]; (let's call this LD1)
ADD R3, R0, R1
ST v[ix + shift], R3
现在,前两个LD操作不会导致停顿。但是,如果R1和R0无效,则ADD操作将导致停顿(无法发出)。
停顿的结果将是SM中的warp调度引擎将寻找其他可用的工作。其他可用的工作可能构成上述其他经线的代码。
由于ADD指令不能发出,直到读取完成,并且由于warp调度程序对停顿的响应,读取(跨warp)都被有效地背靠背发出,因此读取在ADD指令完成发布时,操作将倾向于所有完成,这意味着所有读取都在所有ADD操作发出时完成(并且ST不能发布直到其相应的ADD完成)。 ADD也具有流水线延迟,因此ADD操作也可能按顺序发出(但这里的流水线延迟可能会增加危险的可能性),并且在相应的ADD操作完成之前不能发出给定的ST操作。净效应可能是:
LD0 W0
LD1 W0
LD0 W1
LD1 W1
... (all LD0 and LD1 get issued across all warps W0..WN)
<read latency stall -- eventually the first 2 LD0 and LD1 complete>
ADD W0
<read pipeline latency - 1 cycle>
ADD W1
<read pipeline latency - 1 cycle>
ADD W2
...
<add pipeline latency>
ST W0
<add pipeline latency>
ST W1
...
延迟的结果是在任何ADD操作开始之前,所有读取都以高可能性发布到全局内存。由于管道效应,所有读取操作也可能在任何ST操作开始之前完成(可能?),导致这种有限测试用例的可能性,即不会发生实际的危险错误。
我希望即使数据在L2缓存中,来自L2缓存的读取延迟仍然足以允许上述工作。我怀疑如果数据在L1缓存中,来自L1缓存的读取延迟(并假设最大补充的warp)可能不足以导致上述描述保持,但我没有仔细检查算法。由于ADD流水线延迟是固定的,但是从LD到ST操作的危险由ADD操作的数量与ADD流水线延迟相比确定,实际的危险概率随着在线程块中加载更多扭曲而增加。
请注意,以上所有描述都试图解压缩while
循环的单个迭代的行为。 __syncthreads()
的{{3}}应保证迭代i+1
的读取不会被(见证失败)迭代i
的写入所破坏。