How to have atomic load in CUDA

时间:2015-09-01 21:19:38

标签: cuda gpu-atomics

My question is how I can have atomic load in CUDA. Atomic exchange can emulate atomic store. Can atomic load be emulated non-expensively in a similar manner? I can use an atomic add with 0 to load the content atomically but I think it is expensive because it does an atomic read-modify-write instead of only a read.

2 个答案:

答案 0 :(得分:2)

据我所知,目前无法在CUDA中请求原子负载,这将是一个很棒的功能。

有两种 - 替代品,有其优点和缺点:

  1. 按照建议使用无操作原子读 - 修改 - 写。我过去提供过similar answer。保证原子性和内存的一致性,但是您需要支付不必要的写入费用。

  2. 在实践中,与原子载荷最接近的第二个可能是标记变量volatile,尽管严格来说语义完全不同。该语言保证负载的原子性(例如,理论上 可能会导致读取错误),但您可以保证获得最新版本值。但是在实践中,正如@Robert Crovella的评论所指出的那样,对于最多32个字节的正确对齐事务,不可能得到一个撕裂的读取,这确实使它们成为原子。

  3. 解决方案2是一种hacky,我不推荐它,但它是目前唯一的无写入替代方案1.理想的解决方案是添加一种直接用语言表达原子载荷的方法。

答案 1 :(得分:0)

除了按照其他答案中的建议使用volatile之外,还需要适当地使用__threadfence来获得具有安全内存顺序的原子负载。

尽管有些评论说只使用普通读法是因为它不会撕裂,但这与原子加载不同。原子不仅仅是撕裂:

正常读取可能会重用寄存器中已经存在的先前加载,因此可能无法反映具有所需内存顺序的其他SM所做的更改。例如,int *flag = ...; while (*flag) { ... }只能读取一次flag,并在循环的每次迭代中重用此值。如果您正在等待另一个线程更改标志的值,则永远不会观察到更改。 volatile修饰符可确保每次访问都从内存中实际读取该值。有关更多信息,请参见CUDA documentation on volatile

此外,您需要使用内存围墙在调用线程中强制执行正确的内存顺序。没有栅栏,您会在C ++ 11中措辞“松弛”的语义,并且在使用原子进行通信时这可能是不安全的。

例如,假设您的代码(非原子方式)将一些大数据写入内存,然后使用常规写入操作设置一个原子标志以指示该数据已被写入。指令可能会重新排序,设置标志之前可能不会刷新硬件高速缓存行,等等。结果是,这些操作不能保证以任何顺序执行,并且其他线程可能不会按照您期望的顺序观察这些事件。 :允许在写入保护数据之前 写入标志。

同时,如果读取线程还在有条件地加载数据之前也使用普通读取来检查标志,则将在硬件级别发生争用。在标志的读取完成之前,乱序和/或推测执行可能会加载数据。然后使用推测加载的数据,由于它是在读取标志之前加载的,因此可能无效。

放置在适当位置的内存隔离栅通过强制指令重新排序不会影响您所需的内存排序并且使先前的写入对于其他线程可见,可以防止此类问题。 __threadfence()和朋友也都受到保护in the CUDA docs

将所有这些放在一起,在CUDA中编写自己的原子加载方法看起来像:

// addr must be aligned properly.
__device__ unsigned int atomicLoad(const unsigned int *addr)
{
  const volatile unsigned int *vaddr = addr; // volatile to bypass cache
  __threadfence(); // for seq_cst loads. Remove for acquire semantics.
  const unsigned int value = *vaddr;
  // fence to ensure that dependent reads are correctly ordered
  __threadfence(); 
  return value; 
}

// addr must be aligned properly.
__device__ void atomicStore(unsigned int *addr, unsigned int value)
{
  volatile unsigned int *vaddr = addr; // volatile to bypass cache
  // fence to ensure that previous non-atomic stores are visible to other threads
  __threadfence(); 
  *vaddr = value;
}

对于其他非撕裂的装载/存储大小,可以类似地编写。

通过与一些从事CUDA原子工作的NVIDIA开发人员的交谈,看来我们应该开始看到对CUDA中原子的更好支持,并且PTX已经包含load/store instructions with acquire/release memory ordering语义-但无法访问它们目前不求助于嵌入式PTX。他们希望在今年的某个时候添加它们。一旦到位,完整的std::atomic实现就应该紧随其后。