在this link之后,我尝试理解内核代码的操作(此内核代码有两个版本,一个版本为volatile local float *source
,另一个版本为volatile global float *source
,即{{1 }和local
版本)。我在下面采用global
版本:
local
如果我理解的话,由于限定符“float sum=0;
void atomic_add_local(volatile local float *source, const float operand) {
union {
unsigned int intVal;
float floatVal;
} newVal;
union {
unsigned int intVal;
float floatVal;
} prevVal;
do {
prevVal.floatVal = *source;
newVal.floatVal = prevVal.floatVal + operand;
} while (atomic_cmpxchg((volatile local unsigned int *)source, prevVal.intVal, newVal.intVal) != prevVal.intVal);
}
”,每个工作项共享对source
变量的访问权限,不是吗?
之后,如果我拿一个工作项,代码会将volatile
值添加到operand
变量。然后,在此操作之后,我调用newVal.floatVal
函数,该函数检查先前的分配(atomic_cmpxchg
和preVal.floatVal = *source;
)是否已完成,即通过比较地址newVal.floatVal = prevVal.floatVal + operand;
中存储的值source
。
在此原子操作期间(根据定义不可中断),由于preVal.intVal
存储的值与source
不同,prevVal.intVal
存储的新值为source
,实际上是一个浮点数(因为它像整数一样编码在4个字节上)。
我们可以说每个工作项都有一个互斥锁访问权限(我的意思是锁定访问权限),位于newVal.intVal
。
但是对于source address
线程,只有一次迭代进入each work-item
?
我认为会有一次迭代,因为比较“while loop
”会始终将*source== prevVal.int ? newVal.intVal : newVal.intVal
值分配给存储在newVal.intVal
的值,不是吗?
欢迎任何帮助,因为我还没有理解这个内核代码的所有细微之处。
更新1:
抱歉,我几乎了解所有的细微差别,尤其是source address
:
第一种情况:对于给定的单个线程,在调用atomic_cmpxchg之前,如果while loop
仍然等于prevVal.floatVal
,则*source
将更改atomic_cmpxchg
指针中包含的值,并返回source
中包含的值,该值等于old pointer
,因此我们从prevVal.intVal
中断。
第二种情况:如果在while loop
指令和prevVal.floatVal = *source;
的调用之间,值atomic_cmpxchg
已经改变(由另一个线程??),那么atomic_cmpxchg返回*source
值不再等于old
,因此prevVal.floatVal
中的条件为真,我们将保持此循环,直到不再检查先前条件为止。
我的解释是对的?
由于
答案 0 :(得分:1)
如果我理解的话,由于限定符“
volatile
”,每个工作项共享对源变量的访问权限,不是吗?
volatile
是C语言的关键字,它阻止编译器优化对内存中特定位置的访问(换句话说,在每次读/写所述内存位置时强制加载/存储)。它对底层存储的所有权没有影响。这里,它用于强制编译器在每次循环迭代时从内存中重新读取source
(否则编译器将被允许将该负载移出循环,这会破坏算法)。
do {
prevVal.floatVal = *source; // Force read, prevent hoisting outside loop.
newVal.floatVal = prevVal.floatVal + operand;
} while(atomic_cmpxchg((volatile local unsigned int *)source, prevVal.intVal, newVal.intVal) != prevVal.intVal)
删除限定符(为简单起见)和重命名参数后,atomic_cmpxchg
的签名如下:
int atomic_cmpxchg(int *ptr, int expected, int new)
它的作用是:
atomically {
int old = *ptr;
if (old == expected) {
*ptr = new;
}
return old;
}
总结一下,每个线程分别执行:
*source
的当前值从内存加载到preVal.floatVal
*source
newVal.floatVal
的所需值
atomic_cmpxchg == newVal.intVal
的结果,则表示比较交换成功,中断。否则,交换没有发生,请转到1再试一次。上面的循环最终终止,因为最终,每个线程成功完成atomic_cmpxchg
。
我们可以说每个工作项都有一个互斥锁访问(我的意思是一个锁定的访问权限)到位于源地址的值。
互斥锁是锁,而这是一种无锁算法。 OpenCL可以用自旋锁模拟互斥锁(也用原子实现),但这不是一个。