OpenCL并行缓冲区压缩障碍问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenCL并行缓冲区压缩障碍问题相关的知识,希望对你有一定的参考价值。

作为一个学校项目,我们4人正在使用OpenCL进行并行光线跟踪。这是我们使用OpenCL的第一个项目,所以我们可能会对它有些不满。

我们正在尝试实现并行缓冲区压缩以移除已完成的光线,或者不与任何事物发生碰撞的光线,以便下一次迭代处理的数据更少。基本上,我们有一个缓冲区需要尽可能多的s_ray_states渲染,跟踪它们,获取碰撞数据,压缩缓冲区,这样只有光线与其内部的物体相撞,然后对它们进行着色。

所以我们有一个缓冲区uint *prefix_sum,它包含每个s_ray_state必须在缓冲区s_ray_state *ray_states中移动的索引,以减少发送到着色内核的光线数量,以及跟踪/阴影内核的下一次迭代。

遗憾的是,下面的ray_sort内核似乎没有正常工作,我们验证了输入prefix_sum数据,这是100%正确,ray_states缓冲区相同,但我们在输出中得到了不需要的数据。

我们正在启动一个工作组(全局工作大小=本地工作大小),光线总是在缓冲区中移动到比原始索引更小的索引。我们设置了障碍,并使用s_ray_state *tmp缓冲区来防止并行执行写入彼此的数据,但它似乎不起作用,即使在消除障碍时我们也会得到相同的结果。

我们两个人已经工作了4天,已经向其他学生寻求帮助,但似乎没有人能够弄清楚什么是错的。我们可能无法理解障碍/内存足以确保其实际上可行。

我们已经尝试在一个工​​作组中制作一个工作项对整个数组进行排序,这样可以工作,甚至可以提供更好的性能。

下面的代码应该有效吗?根据我们对OpenCL的理解,它应该有效,我们做了很多研究,但从未真正得到任何明确的答案。

kernel void ray_sort(
    global read_only uint *prefix_sum,
    global read_write struct s_ray_state *ray_states,
    global read_only uint *ray_states_size,
    local read_write struct s_ray_state *tmp
)
{
    int l_size = get_local_size(0);
    int l_id = get_local_id(0);
    int group_id = -1;
    int group_nb = *ray_states_size / l_size;
    int state_id;

    while (++group_id < group_nb)
    {
        state_id = group_id * l_size + l_id;
        tmp[l_id] = ray_states[state_id];
        barrier(CLK_LOCAL_MEM_FENCE);
        if (did_hit(tmp[l_id]))
            ray_states[prefix_sum[state_id]] = tmp[l_id];
        barrier(CLK_GLOBAL_MEM_FENCE);
    }
}

ray_states长度是ray_states_size

prefix_sum包含必须移动每个ray_states元素的索引

tmplocal_work_size大小的本地缓冲区

local_work_size = global_work_size

如果射线击中物体,则did_hit()返回1,否则返回0

我们期待将ray_states元素移动到prefix_sum中包含的指数

示例:每个ray_states[id]都被移动到prefix_sum[id]ray_states指数

prefix_sum: 0 | 0 | 1 | 1 | 2 | 3 | 3 | 3 | 4

did_hit(ray_states[id]): 0 | 1 | 0 | 1 | 1 | 0 | 0 | 1 | 0

did_hit(output[id]): 1 | 1 | 1 | 1 | X | X | X | X | X

Xs可以是任何东西

答案

我可以完全离开这里,但在我看来,did_hit(ray_states[state_id])你正在读同一块全局内存,你把它放在一个本地内存缓冲区tmp上面只有2行。除了您正在为输入和输出使用该缓冲区之外,哪个不会有问题。

我看到它的方式,硬件上实际发生的是:

    tmp[l_id] = ray_states[state_id];
    tmp[l_id] = ray_states[state_id];
    tmp[l_id] = ray_states[state_id];
    tmp[l_id] = ray_states[state_id];
    tmp[l_id] = ray_states[state_id];

       ... local-work-size times

    barrier(CLK_LOCAL_MEM_FENCE);

    if (did_hit(ray_states[state_id]))
        ray_states[prefix_sum[state_id]] = tmp[l_id];
    if (did_hit(ray_states[state_id]))
        ray_states[prefix_sum[state_id]] = tmp[l_id];
    if (did_hit(ray_states[state_id]))
        ray_states[prefix_sum[state_id]] = tmp[l_id];
    if (did_hit(ray_states[state_id]))
        ray_states[prefix_sum[state_id]] = tmp[l_id];

      ... again local-work-size times

考虑到WItem并行执行顺序是未定义的(硬件可能选择它想要的任何顺序),这将导致随机结果。你可以试试这个:

    if (did_hit(tmp[l_id]))
        ray_states[prefix_sum[state_id]] = tmp[l_id];

顺便说一句,如果ray_states_size只是一个简单的整数,你可以通过使参数“uint ray_states_size”直接传递它。不需要在那里闲逛缓冲区。

EDIT1:我的建议只有在prefix_sum[state_id]在每个本地工作大小的ID中没有任何重复项时才有效,否则仍会有数据竞争。所以例如如果对于state_id-s 1和3,prefix_sum[state_id]数组为0,并且您的本地WG大小> = 4,则会有数据竞争。

此外,是否有一些非常好的理由必须使用相同的缓冲区进行输入和输出?在我看来,如果你有单独的输入/输出缓冲区,它会复杂得多。

编辑2:我刚刚注意到你说“光线总是在缓冲区中移动到比原来更小的索引”(抱歉,我错过了)。这很好,但还不够 - 它们总是被移动到比同一个本地工作组中任何其他射线索引更小的索引吗?如果是,那很好,但我还提到了其他数据竞赛。

以上是关于OpenCL并行缓冲区压缩障碍问题的主要内容,如果未能解决你的问题,请参考以下文章

在块中找到最大值的 OpenCL 障碍

OpenCL 中的障碍

GPU 中的并行性 - CUDA / OpenCL

1 个 cpu 设备上 OpenCL 的并行性

如何在 OpenCL 内核中更新 OpenCL-OpenGL 共享缓冲区数据?

OpenCL 管道