CUDA 的块间同步方法

举报
ShaderJoy 发表于 2021/12/31 23:10:36 2021/12/31
【摘要】 CUDA 块间同步方法有以下三种 ① Lock-Based Synchronization ② Lock-Free Synchronization ③ __threadfence()   基于锁的块间同步 CUDA 基于锁的同步的基本思想是使用一个全局互斥量变量来计算到达同步点的线程块的数量。如下代码所示,在 ba...

CUDA 块间同步方法有以下三种

① Lock-Based Synchronization

② Lock-Free Synchronization

③ __threadfence()

 

基于锁的块间同步

CUDA 基于锁的同步的基本思想是使用一个全局互斥量变量来计算到达同步点的线程块的数量。如下代码所示,在 barrier 函数 __gpu_sync() 中,在一个块完成它的计算之后,它的一个线程 (这里人为设置为 0 号线程,我们称之为主导线程) 将自动地向 g_mutex 添加 1 (原子操作)。然后,主导线程将重复将 g_mutex 和一个目标值 goalVal 进行比较。如果 g_mutex 等于 goalVal,那么就意味着同步完成,即每个线程块都可以进行下一阶段的计算。在该设计中,当第一次调用 barrier 函数时,将 goalVal 设置为内核中的块数 N 。然后,当连续调用 barrier 函数时,goalVal的值每次递增 N 。这种设计比保持 goalVal 常量并在每个 barrier 之后重新设置 g_mutex 更有效,因为前者节省了指令的数量并避免了条件分支


  
  1. // lock-based
  2. __device__ volatile int g_mutex;
  3. // GPU lock-based synchronization function
  4. __device__ void __gpu_sync(int goalVal)
  5. {
  6. // thread ID in a block
  7. int tid_in_block = threadIdx.x * blockDim.y + threadIdx.y;
  8. // only thread 0 is used for synchronization
  9. if (tid_in_block == 0)
  10. {
  11. atomicAdd((int*) &g_mutex, 1);
  12. // only when all blocks add 1 go g_mutex
  13. // will g_mutex equal to goalVal
  14. while (g_mutex != goalVal)
  15. {
  16. // Do nothing here
  17. }
  18. }
  19. __syncthreads();
  20. }

无锁的块间同步

在 CUDA 基于锁的同步中,互斥量变量 g_mutex 是通过原子函数 atomicAdd() 添加的。这意味着 g_mutex 的添加只能按顺序执行,即使这些操作是在不同的块中执行的。因此,提出一种完全避免使用原子操作的无锁同步方法。这种方法的基本思想是为每个线程块分配一个同步变量,这样每个块就可以独立地记录其同步状态,而不必争用单个全局互斥锁变量。如下代码所示,我们的无锁同步方法使用两个数组 Arrayin 和 Arrayout 来协调来自不同块的同步请求。在这两个数组中,每个元素都映射到内核中的一个线程块,即,将元素 i 映射到线程块 i ,算法大致分为三个步骤:

 

  1. 一开始,当 block i 准备好通信时,它的主导线程 (线程 0) 将 Arrayin 中的元素 i 设置为目标值 goalVal。block i 中的主导线程然后忙等 Arrayout 的元素 i 被设置为 goalVal 。
  2. 然后人为的让 block 1中的前 N 个(N 等于块数)线程重复检查 Arrayin 中的所有元素是否等于 goalVal ,线程 i 负责检查 Arrayin 中的第 i 个元素(一对一检查)。将 Arrayin 中的所有元素设置为 goalVal 后,每个检查线程将 Arrayout 中的相应元素设置为 goalVal 。注意,在更新 Arrayout的元素之前,block 1 的每个检查线程都会调用块内 barrier 函数  __syncthreads()。
  3. 最后,当 block i 的主导线程看到 Arrayout 中的对应元素被设置为 goalVal 时,该 block 将继续执行。

 


  
  1. // lock-free
  2. __device__ void __gpu_sync(int goalVal, volatile int *Arrayin, volatile int *Arrayout)
  3. {
  4. // thread ID in a block
  5. int tid_in_blk = threadIdx.x * blockDim.y + threadIdx.y;
  6. int nBlockNum = gridDim.x * gridDim.y;
  7. int bid = blockIdx.x * gridDim.y + blockIdx.y;
  8. // only thread 0 is used for synchonization
  9. if (tid_in_blk == 0)
  10. {
  11. Arrayin[bid] = goalVal;
  12. }
  13. if (bid == 1)
  14. {
  15. if (tid_in_blk < nBlockNum)
  16. {
  17. while (Arrayin[tid_in_blk] != goalVal)
  18. {
  19. // Do nothing here
  20. }
  21. }
  22. __syncthreads();
  23. if (tid_in_blk < nBlockNum)
  24. {
  25. Arrayout[tid_in_blk] = goalVal;
  26. }
  27. }
  28. if (tid_in_blk = 0)
  29. {
  30. while (Arrayout[bid] != goalVal)
  31. {
  32. // Do nothing here
  33. }
  34. }
  35. __syncthreads();
  36. }

从以上代码可以看出,CUDA 无锁同步中没有原子操作。所有的操作都可以并行执行。不同线程块的同步由单个块 (block 1) 中的 N 个线程来控制,可以通过调用块内 barrier 函数 __syncthreads() 来有效地同步。

 

__threadfence() 

最后,值得注意的是,另外一种保证 CUDA 块间同步通信的正确性的办法是使用 __threadfence() (CUDA 2.2中引入了一个新的函数 )。这个函数将阻塞调用线程,直到之前对 全局内存 或 共享内存 的写入对其他线程可见为止。但是使用 __threadfence() 也会引起一定的额外开销,所以需要进行实际测试和权衡。

文章来源: panda1234lee.blog.csdn.net,作者:panda1234lee,版权归原作者所有,如需转载,请联系作者。

原文链接:panda1234lee.blog.csdn.net/article/details/88294382

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。