为什么在使用__syncthreads时我们不需要使用volatile变量

时间:2019-01-08 15:16:24

标签: cuda

所有问题都在这里。 我了解为什么在使用volatile及其类似功能时需要变量为__threadfence_block

  

请注意,为了使此订购保证为真,请遵守   线程必须真正观察内存,而不是缓存版本;   这可以通过使用Volatile中详细介绍的volatile关键字来确保   预选赛。

但是我想知道为什么在使用volatile函数时我们不需要变量为__syncthreads

1 个答案:

答案 0 :(得分:3)

根据the programming guide__syncthreads()既是执行障碍,又是内存屏障:

  

等待直到线程块中的所有线程都达到此点为止,并且这些线程在__syncthreads()之前进行的所有全局和共享内存访问对于该块中的所有线程可见。 / p>

内存隔离功能(即“可见性”)“强制”对共享和全局内存的所有更新对其他线程可见。

我想这就是你要问的。我不认为做出诸如“在使用__syncthreads()时不需要使用volatile”这样的笼统声明是明智的。这将取决于代码。但是在某些情况下,例如classical parallel reduction,在块级缩减的每个步骤中使用__syncthreads()将意味着用于这种缩减的共享内存不必标记为{{1} }。

由于volatile既是执行障碍,又是内存屏障,所以对于__syncthreads()的使用,我们可以做出某些陈述,这些陈述仅适用于__syncthreads()的使用。 / p>

假设我有以下代码:

__threadfence()

在这种情况下,保证执行if语句的特定块中的任何线程都将__global__ void k(int *data){ ... *data = 1; __syncthreads(); if (*data == 1){ ...} ... } 视为1。这有两个组成部分:

  1. *data是(设备范围内的)内存围墙。它强制所有写入该值的线程使该值可见。这有效地意味着,因为这是设备范围的内存屏障,所以写入的值至少已填充了L2高速缓存(L2缓存是全局内存的设备范围的中介程序,实际上是全局内存的代理)。

    < / li>
  2. __syncthreads()是(整个线程块)执行障碍。它会强制所有线程到达障碍处,然后再继续进行操作。这种执行排序行为意味着,当任何线程执行上述if语句时,以上第1项中的保证即生效。

请注意,这里有一个细微的区别。在其他块中,在代码的其他位置上的其他线程,可能会看到也可能看不到其他块所写的值。

只有将执行同步和内存防护结合在一起时,才能确定一个线程填充的值对另一个线程是真正可见的。而且,由于不使用协作组,CUDA没有提供跨单独的块同步执行的机制。

__syncthreads()本身会使值最终可见,但是如果不了解写入线程和读取线程之间的相对执行顺序,就不可能仅基于代码检查。

__threadfence()类似,volatile保证与__threadfence()类似(对于写线程),但也有所不同。 __threadfence()确保写入线程最终会将其数据推送到L2(即使其可见)。 volatile做类似的事情,但也保证读取线程将不会在L1中读取“过时的副本”,但会在至少一次读取L2中的值时进入L2(至少)以获取当前值。代码。

请注意,永远不会因另一个SM上的设备代码活动而触发的L1缓存数据“无效”。 volatile有效地保证了负载将绕过L1。 volatile还保证商店将直接转到L2。 __threadfence()的行为与后者类似(至少从线程前进到__threadfence()之后),但不保证其他SM中的L1状态或其他SM中的线程将如何读取值。