内存一致性模型CUDA 4.0和全局内存?

时间:2012-04-21 18:25:35

标签: memory parallel-processing cuda cpu-architecture

更新:编译器优化了下面的while()条件,因此两个线程都跳过条件并输入C.S.甚至带有-O0标志。有谁知道为什么编译器这样做?顺便说一句,声明全局变量volatile导致程序因某些奇怪的原因而挂起......

我读了CUDA programming guide但是我对CUDA如何处理全局内存内存一致性仍然有点不清楚。 (这与内存层次结构不同)基本上,我正在运行试图破解顺序一致性的测试。我使用的算法是 Peterson的内核函数内两个线程之间互斥的算法:

flag[threadIdx.x] = 1; // both these are global
turn = 1-threadIdx.x;

while(flag[1-threadIdx.x] == 1 && turn == (1- threadIdx.x));
shared_gloabl_variable_x ++;

flag[threadIdx.x] = 0;

这非常简单。每个线程通过将其标志设置为1并通过将转向设置为另一个线程来获得关键部分来请求关键部分。在评估while()时,如果另一个线程未设置其标志,则请求线程可以安全地进入临界区。 现在,这种方法的一个微妙问题是,如果编译器对写入进行重新排序,以便在写入turn之前执行对flag的写入。如果这两个线程都发生这种情况将同时在CS中结束。这很容易用普通的Pthreads来证明,因为大多数处理器都没有实现顺序一致性。 但GPU呢呢?

这两个线程都将处于相同的warp中。并且他们将以锁步模式执行他们的语句。但是当它们到达turn变量时,它们会写入同一个变量,因此warp内执行变得序列化(无论顺序是什么)。现在,此时,获胜的线程是否继续执行while条件,还是等待另一个线程完成其写入,以便两者可以同时评估while()?这些路径将再次分散在while(),因为只有其中一条会赢,而另一条则会等待。

运行代码后,我会让它始终破坏SC。我读的值总是1,这意味着两个线程每次都以某种方式进入C.S.这怎么可能(GPU按顺序执行指令)? (注意:我用-O0编译了它,因此没有编译器优化,因此没有使用volatile)。

2 个答案:

答案 0 :(得分:3)

编辑:由于您只有两个线程且1-threadIdx.x有效,因此您必须使用线程ID 0和1.线程0和1将始终是同一个warp的一部分目前所有的NVIDIA GPU。 Warps执行指令SIMD方式,具有用于发散条件的线程执行掩码。你的while循环是一个不同的条件。

  • turnflags 不是 volatile时,编译器可能会重新排序说明,并且您会看到两个线程进入CS的行为
  • turnflags volatile时,您会看到挂起。原因是其中一个线程将在写入转向时成功,因此turn将为0或1.假设turn==0:如果硬件选择执行线程0的发散分支的一部分,那么一切都好。但是如果它选择执行线程1的发散分支的一部分,那么它将在while循环上旋转,线程0将永远不会轮到它,因此挂起。

你可以通过确保你的两个线程处于不同的warp中来避免挂起,但我认为warp必须同时驻留在SM上,以便指令可以从两者发出并且可以取得进展。 (可能在不同的SM上使用并发warp,因为这是全局内存;但是这可能需要__threadfence()而不仅仅是__threadfence_block()。)

一般来说,这是一个很好的例子,说明为什么这样的代码在GPU上是不安全的,不应该使用。我意识到这只是一个调查实验。一般情况下,CUDA GPU没有 - 如你所说,大多数处理器都没有实现顺序一致性。

原始答案

  1. 变量turnflag必须为volatile,否则flag的加载不会重复,条件turn == 1-threadIdx.X将不会重复 - 评估,但将被视为true
  2. 商店与__threadfence_block()之间应该有flag,并存储到turn以获得正确的订购。
  3. 共享变量增量之前应该有__threadfence_block()(也应该声明为volatile)。在增量之后,您可能还需要__syncthreads()或至少__threadfence_block(),以确保其他线程可见。
  4. 我有预感,即使在进行这些修复后,您仍可能遇到麻烦。让我们知道它是怎么回事。

    顺便说一句,你在这一行中有一个语法错误,所以很明显这不是你真正的代码:

    while(flag[1-threadIdx.x] == 1 and turn==[1- threadIdx.x]);
    

答案 1 :(得分:2)

在没有额外的内存障碍(如__threadfence())的情况下,全局内存的顺序一致性仅在给定的线程中强制执行。