说明
原子性操作不是所有的显卡都支持的,只有算力在1.1及以上的才支持全局内存的原子操作,只有1.2及以上算力的才支持共享内存上的原子操作。
由于显卡算力高的是低的一个超集,类似可理解为于高算力版本可以向下兼容。同时,在编译代码的时候,可以告诉nvcc编译器需要什么样的版本,nvcc -arch=sm_1.2
即需要1.2算力版本的支持。
如果不支持就需要考虑升级硬件了。
原子操作
原子操作的概念是由多部操作构成的一个操作集合,如果执行该操作集合,就必须全部执行,不能执行部分操作。
如:
x++;
定义上述++操作是一个原子操作,其共分为三步:
- 读取x
- x + 1
- 将2中的结果重新赋值给x
那么这三步是不能被打断,熟悉线程编码的可能很好理解,如果线程A和线程B同时对x执行这个操作,是无法保证这三步都是按照顺序执行的。可能出现A先读取x,在进行+1操作时,B线程完成了读取和+1以及写入的操作,A再进行+1赋值操作后,值就不再是我们预想的样子。
而原子操作,就不会出现这样的问题,如果A在执行这个原子操作,B线程会进入阻塞态,只有A全部执行完成后,B才能获取到资源继续执行。
常见的CUDA原子操作,如下表:
函数名 | 释义 |
---|---|
atomicAdd(&value, add_num) | 加法:value = value + add_num |
atomicSub(&value, sub_num) | 减法:value = value + sub_num |
atomicExch(&value, num) | 赋值:value = num |
atomicMax(&value, num) | 求最大:value = max(value, num) |
atomicMin(&value, num) | 求最小:value = min(value, num) |
atomicInc(&value, compare) | 向上计数:如果(value <= compare)则value++,否则value = 0 |
atomicDec(&value, compare) | 向下计数:如果(value > compare或value == 0), 则value–,否则value = 0 |
atomicCAS(&value, compare) | 比较并交换:如果(value != compare),则value = compare |
atomicAnd(&value, add_num) | 与运算:value = value & num |
atomicOr(&value, add_num) | 或运算 value = value |
atomicXor(&value, add_num) | 异或运算 value = value ^ num |
但是CUDA中线程更多,操作相同地址的内存时,因为竞争关系,就变成了一系列线程在排队执行串行化操作。就失去了CUDA并行计算的优势,甚至会比cpu版本的更慢。
可以使用全局内存和共享内存的原子操作结合的方式,在每个block中将需要进行原子操作的变量存入共享内存中。此时发生竞争的线程只是当前block内的线程,多个block是可以并行计算的。
每个block计算完成后,再将block内的临时变量进行原子操作存入全局内存中,得到最终的结果。
例如:
需要统计一个数组中元素分布情况。
可以在block内统计完该block内所有线程应该访问的地址上的数据,再讲所有的block在合并起来,就能得到最终的结果。
由上可知,原子操作会影响程序性能,所以需要谨慎设置代码逻辑,不然会适得其反。
Reference
https://blog.youkuaiyun.com/q583956932/article/details/78826987