了解 OpenCL 减少浮点数的方法
Understanding the method for OpenCL reduction on float
在this link之后,我试图了解内核代码的运行(这个内核代码有2个版本,一个是volatile local float *source
,另一个是volatile global float *source
,即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);
}
如果我理解得很好,由于限定符“volatile
”,每个工作项共享对 source
变量的访问权,不是吗?
之后,如果我获取一个工作项,代码会将 operand
值添加到 newVal.floatVal
变量。然后,在此操作之后,我调用 atomic_cmpxchg
函数来检查之前的赋值(preVal.floatVal = *source;
和 newVal.floatVal = prevVal.floatVal + operand;
)是否已经完成,即通过比较存储在地址 source
的值与preVal.intVal
.
在这个原子操作期间(根据定义不是不可中断的),由于存储在source
的值与prevVal.intVal
不同,因此存储在source
的新值是newVal.intVal
,它实际上是一个浮点数(因为它像整数一样被编码为 4 个字节)。
我们可以说每个工作项都具有对位于 source address
的值的互斥访问(我的意思是锁定访问)。
但是对于each work-item
线程,while loop
是否只有一次迭代?
我认为会有一次迭代,因为比较“*source== prevVal.int ? newVal.intVal : newVal.intVal
”总是会将 newVal.intVal
值分配给存储在 source address
的值,不是吗?
欢迎任何帮助,因为我还没有理解这个内核代码的技巧的所有微妙之处。
更新 1:
对不起,我几乎明白所有的细节,尤其是while loop
:
第一种情况:对于给定的单线程,在调用atomic_cmpxchg之前,如果prevVal.floatVal
仍然等于*source
,那么atomic_cmpxchg
会改变source
指针中包含的值,return会改变old pointer
中包含的值,等于prevVal.intVal
,所以我们从while loop
。
第二种情况:如果在prevVal.floatVal = *source;
指令和atomic_cmpxchg
的调用之间,值*source
已经改变(由另一个线程??) 然后 atomic_cmpxchg returns old
值不再等于 prevVal.floatVal
,所以进入 while loop
的条件为真,我们留在这个循环中直到不再检查之前的条件。
我的解释正确吗?
谢谢
If I understand well, each work-item shares the access to source variable thanks to the qualifier "volatile
", doesn't it?
volatile
是 C 语言的关键字,它阻止编译器优化对内存中特定位置的访问(换句话说,在每个 read/write 处强制使用 load/store表示内存位置)。它对底层存储的所有权没有影响。在这里,它用于强制编译器在每次循环迭代时从内存中 re-read 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
中的期望值
- 执行上述原子 compare-exchange(使用 type-punned 值)
- 如果
atomic_cmpxchg == newVal.intVal
的结果,说明compare-exchange成功,break。否则交换没有发生,转到1重试。
上面的循环最终终止,因为最终,每个线程都成功地完成了他们的atomic_cmpxchg
。
Can we say that each work-item has a mutex access (I mean a locked access) to value located at source address.
互斥体是锁,而这是一个 lock-free 算法。 OpenCL 可以用自旋锁模拟互斥(也用原子实现),但这不是一个。
在this link之后,我试图了解内核代码的运行(这个内核代码有2个版本,一个是volatile local float *source
,另一个是volatile global float *source
,即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);
}
如果我理解得很好,由于限定符“volatile
”,每个工作项共享对 source
变量的访问权,不是吗?
之后,如果我获取一个工作项,代码会将 operand
值添加到 newVal.floatVal
变量。然后,在此操作之后,我调用 atomic_cmpxchg
函数来检查之前的赋值(preVal.floatVal = *source;
和 newVal.floatVal = prevVal.floatVal + operand;
)是否已经完成,即通过比较存储在地址 source
的值与preVal.intVal
.
在这个原子操作期间(根据定义不是不可中断的),由于存储在source
的值与prevVal.intVal
不同,因此存储在source
的新值是newVal.intVal
,它实际上是一个浮点数(因为它像整数一样被编码为 4 个字节)。
我们可以说每个工作项都具有对位于 source address
的值的互斥访问(我的意思是锁定访问)。
但是对于each work-item
线程,while loop
是否只有一次迭代?
我认为会有一次迭代,因为比较“*source== prevVal.int ? newVal.intVal : newVal.intVal
”总是会将 newVal.intVal
值分配给存储在 source address
的值,不是吗?
欢迎任何帮助,因为我还没有理解这个内核代码的技巧的所有微妙之处。
更新 1:
对不起,我几乎明白所有的细节,尤其是while loop
:
第一种情况:对于给定的单线程,在调用atomic_cmpxchg之前,如果prevVal.floatVal
仍然等于*source
,那么atomic_cmpxchg
会改变source
指针中包含的值,return会改变old pointer
中包含的值,等于prevVal.intVal
,所以我们从while loop
。
第二种情况:如果在prevVal.floatVal = *source;
指令和atomic_cmpxchg
的调用之间,值*source
已经改变(由另一个线程??) 然后 atomic_cmpxchg returns old
值不再等于 prevVal.floatVal
,所以进入 while loop
的条件为真,我们留在这个循环中直到不再检查之前的条件。
我的解释正确吗?
谢谢
If I understand well, each work-item shares the access to source variable thanks to the qualifier "
volatile
", doesn't it?
volatile
是 C 语言的关键字,它阻止编译器优化对内存中特定位置的访问(换句话说,在每个 read/write 处强制使用 load/store表示内存位置)。它对底层存储的所有权没有影响。在这里,它用于强制编译器在每次循环迭代时从内存中 re-read 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
中的期望值
- 执行上述原子 compare-exchange(使用 type-punned 值)
- 如果
atomic_cmpxchg == newVal.intVal
的结果,说明compare-exchange成功,break。否则交换没有发生,转到1重试。
上面的循环最终终止,因为最终,每个线程都成功地完成了他们的atomic_cmpxchg
。
Can we say that each work-item has a mutex access (I mean a locked access) to value located at source address.
互斥体是锁,而这是一个 lock-free 算法。 OpenCL 可以用自旋锁模拟互斥(也用原子实现),但这不是一个。