了解 OpenCL 减少浮点数的方法

Understanding the method for OpenCL reduction on float

this link之后,我试图了解内核代码的运行(这个内核代码有2个版本,一个是volatile local float *source,另一个是volatile global float *source,即localglobal 版本)。下面我取 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;
}

总而言之,每个线程单独执行:

  1. 从内存中加载 *source 的当前值到 preVal.floatVal
  2. 计算 *sourcenewVal.floatVal
  3. 中的期望值
  4. 执行上述原子 compare-exchange(使用 type-punned 值)
  5. 如果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 可以用自旋锁模拟互斥(也用原子实现),但这不是一个。