【发布时间】:2013-01-09 18:06:51
【问题描述】:
以下全局屏障适用于 Kepler K10 而不是 Fermi GTX580:
__global__ void cudaKernel (float* ref1, float* ref2, int* lock, int time, int dim) {
int gid = blockIdx.x * blockDim.x + threadIdx.x;
int lid = threadIdx.x;
int numT = blockDim.x * gridDim.x;
int numP = int (dim / numT);
int numB = gridDim.x;
for (int t = 0; t < time; ++t) {
// compute @ time t
for (int i = 0; i < numP; ++i) {
int idx = gid + i * numT;
if (idx > 0 && idx < dim - 1)
ref2 [idx] = 0.333f * ((ref1 [idx - 1] + ref1 [idx]) + ref1 [idx + 1]);
}
// global sync
if (lid == 0){
atomicSub (lock, 1);
while (atomicCAS(lock, 0, 0) != 0);
}
__syncthreads();
// copy-back @ time t
for (int i = 0; i < numP; ++i) {
int idx = gid + i * numT;
if (idx > 0 && idx < dim - 1)
ref1 [idx] = ref2 [idx];
}
// global sync
if (lid == 0){
atomicAdd (lock, 1);
while (atomicCAS(lock, numB, numB) != numB);
}
__syncthreads();
}
}
因此,通过查看发送回 CPU 的输出,我注意到一个线程(第一个或最后一个线程)逃脱了屏障并比其他线程更早地恢复执行。我正在使用 CUDA 5.0。块数也总是小于 SM 数(在我的一组运行中)。
知道为什么相同的代码不能在两种架构上运行吗? Kepler 中有哪些新功能有助于实现全球同步?
【问题讨论】:
-
如果您在断言这行不通的情况下包含更多内容,那就太好了。提供一个特定的完整示例以及您测试的两个设备上的实际和预期输出会很有帮助。根据我所看到的,由于 atomicCAS 指令生成的访问将在线程块之间序列化,我完全希望线程块能够连续退出“障碍”。因此,无论如何,我希望一个线程块比其他线程块更早地恢复执行。因此,我对您定义的差异和“通过”或“失败”感兴趣。
-
Kepler GK110 全局原子操作为significantly faster than Fermi。在从单独的 SM 发出的原子的情况下,它们可能实际上是背靠背的,并且以 Kepler GK110 (K20) 的核心时钟速率完成。我的观点是,这将比 Fermi 序列化/完成快得多。但是,根据您的代码,这并不影响退出屏障仍然是线程块之间的串行操作这一事实。而且K10不是GK110。
-
所以我编写的代码为每个 CUDA 线程提供了一个索引(例如,索引 i 映射到一维数组 A[i])并要求每个线程获取 3 个相邻的数据元素(A[i-1], A[i], A[i+1]) 从全局内存中计算平均值。稍后,在每个线程在“全局”屏障处停止后,继续更新原始数据数组。这整个事情重复了几次。所以每个线程都执行以下循环: for (t=0 to T) {compute;同步;复制;同步}
-
@Naseria 由于内核正在执行全局内存访问,除了全局屏障之外,您还应该使用全局内存围栏。您可能还需要将指针限定为 volatile 或在禁用 L1 缓存的情况下进行编译。请按照 Robert 的建议提供一个完整的示例。
-
我用 CUDA 内核更新了原帖。这是一个 1-D 3 点 jacobi 样式代码。输入数组是“ref1”,“ref2”是用于复制回的临时数组,“lock”是全局互斥体,“time”是时间步数,“dim”是数据元素的总数。我已经测试过内存栅栏“__threadfence()”,但没有帮助。但是,“不稳定”的技巧很有帮助。问题似乎在于如何将一些数据元素缓存在 L1 中,并且通过使用“volatile”关键字,我确保这些元素“ref1 和 ref2 数组”不会被缓存在 L1 中。
标签: cuda