【问题标题】:bit shift operation in parallel prefix sum并行前缀和中的位移操作
【发布时间】:2022-08-10 07:56:19
【问题描述】:

代码是从OpengGL-Superbible 10 并行计算前缀总和。

所示着色器的本地工作组大小为 1024,这意味着它将处理 2048 个元素的数组,因为每次调用都会计算输出数组的两个元素。共享变量 shared_data 用于存储正在运行的数据。执行开始时,着色器将输入数组中的两个相邻元素加载到数组中。接下来,它执行 barrier() 函数。此步骤可确保所有着色器调用在内部循环开始之前已将其数据加载到共享数组中。

#version 450 core
layout (local_size_x = 1024) in;
layout (binding = 0) coherent buffer block1
{
    float input_data[gl_WorkGroupSize.x];
};
layout (binding = 1) coherent buffer block2
{
    float output_data[gl_WorkGroupSize.x];
};
shared float shared_data[gl_WorkGroupSize.x * 2];
void main(void)
{
    uint id = gl_LocalInvocationID.x;
    uint rd_id;
    uint wr_id;
    uint mask;// The number of steps is the log base 2 of the
    // work group size, which should be a power of 2
    const uint steps = uint(log2(gl_WorkGroupSize.x)) + 1;
    uint step = 0;
    // Each invocation is responsible for the content of
    // two elements of the output array
    shared_data[id * 2] = input_data[id * 2];
    shared_data[id * 2 + 1] = input_data[id * 2 + 1];
    // Synchronize to make sure that everyone has initialized
    // their elements of shared_data[] with data loaded from
    // the input arrays
    barrier();
    memoryBarrierShared();
    // For each step...
    for (step = 0; step < steps; step++)
    {
        // Calculate the read and write index in the
        // shared array
        mask = (1 << step) - 1;
        rd_id = ((id >> step) << (step + 1)) + mask;
        wr_id = rd_id + 1 + (id & mask);
        // Accumulate the read data into our element
        shared_data[wr_id] += shared_data[rd_id];
        // Synchronize again to make sure that everyone
        // has caught up with us
        barrier();
        memoryBarrierShared();
    } // Finally write our data back to the output image
    output_data[id * 2] = shared_data[id * 2];
    output_data[id * 2 + 1] = shared_data[id * 2 + 1];
}

如何直观地理解rd_idwr_id的位移操作?为什么它有效?

【问题讨论】:

    标签: opengl glsl compute-shader prefix-sum


    【解决方案1】:

    当我们说某事是“直觉的”时,我们通常是指我们的理解足够深,以至于我们没有意识到自己的思维过程,并且在没有有意识地思考的情况下“知道了答案”。在这里,作者在 CPU/GPU 中使用整数的二进制表示来使代码更短并且(可能)稍微快一些。对于非常熟悉此类编码和整数二进制运算的人来说,该代码只会是“直观的”。我不是,所以不得不考虑发生了什么。

    我建议通过此代码工作,因为此类操作确实发生在高性能图形和其他编程中。如果你觉得它有趣,它最终会变得直观。如果没有,那没关系,只要你能在必要时解决问题。

    一种方法是将此代码复制到 C/C++ 程序中并打印出掩码、rd_id、wr_id 等。您实际上不需要数据数组,也不需要调用 barrier() 和 memoryBarrierShared()。根据 SuperBible 示例所做的事情来确定调用 ID 和工作组大小的值。这对于“啊哈!我明白了”可能就足够了。

    如果您不熟悉 << 和 >> 转换,我建议您编写一些小程序并打印出结果。 Python 实际上可能稍微容易一些,因为

    print("{:016b}".format(mask))
    

    将向您显示实际位,而在 C 中您只能以十六进制打印。

    为了帮助您入门,log2 返回表示整数所需的位数。 log2(256) 将是 8,log2(4096) 12,等等。(不要相信我的话,写一些代码。)

    x << n 是将 x 乘以 2 的 n 次方,因此 x << 1 是 x * 2,x << 2 是 x * 4,依此类推。 x >> n 是除以 1, 2, 4, .. 而不是。 (非常重要:仅适用于非负整数!同样,编写一些代码来了解发生了什么。)

    掩码计算很有趣。尝试

    mask = (1 << step);
    

    首先看看有什么值出来。这是选择单个位的常见模式。额外的 -1 会生成右侧的所有位。

    Anding,即 & 运算符,其掩码的左侧为 0,右侧为 1,对于整数 % 的 2 次方而言,这是一种更快的方法。

    最后 rd_id 和 wr_id 数组索引需要从数组中的基本位置开始,从调用 ID 和工作组大小开始,并根据超级圣经文本中解释的模式递增。

    【讨论】:

    • 非常感谢!对我来说,“整数 % 2 的幂的一种更快的方法”对我来说是一个新的观点。
    猜你喜欢
    • 2016-06-17
    • 2022-11-17
    • 2016-04-14
    • 2011-12-10
    • 2016-06-19
    • 2012-10-21
    • 2012-02-23
    • 2017-09-25
    相关资源
    最近更新 更多