【问题标题】:"threadgroup_barrier" makes no difference“threadgroup_barrier”没有区别
【发布时间】:2019-08-29 12:07:00
【问题描述】:

目前我正在使用 Metal 计算着色器,并试图了解 GPU 线程同步如何在那里工作。

我写了一个简单的代码,但它并没有像我期望的那样工作:

假设我有 threadgroup 变量,这是一个数组,所有线程都可以同时产生一个输出。

    kernel void compute_features(device float output [[ buffer(0) ]],
                                 ushort2 group_pos [[ threadgroup_position_in_grid ]],
                                 ushort2 thread_pos [[ thread_position_in_threadgroup]],
                                 ushort tid [[ thread_index_in_threadgroup ]])
    {     
        threadgroup short blockIndices[288];

        float someValue = 0.0
        // doing some work here which fills someValue...

        blockIndices[thread_pos.y * THREAD_COUNT_X + thread_pos.x] = someValue;

        //wait when all threads are done with calculations
        threadgroup_barrier(mem_flags::mem_none);  
        output += blockIndices[thread_pos.y * THREAD_COUNT_X + thread_pos.x]; // filling out output variable with threads calculations
    }

上面的代码不起作用。输出变量不包含所有线程计算,它仅包含来自线程的值,该值可能是最后一个将值添加到output。在我看来,threadgroup_barrier 似乎什么都没做。

现在,有趣的部分。下面的代码有效:

blockIndices[thread_pos.y * THREAD_COUNT_X + thread_pos.x] = someValue;

threadgroup_barrier(mem_flags::mem_none);  //wait when all threads are done with calculations
if (tid == 0) {
    for (int i = 0; i < 288; i ++) {
        output += blockIndices[i]; // filling out output variable with threads calculations
    }
}

而且这段代码也和上一个一样好用:

blockIndices[thread_pos.y * THREAD_COUNT_X + thread_pos.x] = someValue;

if (tid == 0) {
    for (int i = 0; i < 288; i ++) {
        output += blockIndices[i]; // filling out output variable with threads calculations
    }
}

总结一下:我的代码只有在我在一个 GPU 线程中处理线程组内存时才能按预期工作,无论它的 id 是什么,它都可以是线程组中的最后一个线程,也可以是第一个线程。 threadgroup_barrier 的存在完全没有区别。我还使用了threadgroup_barriermem_threadgroup 标志,代码仍然不起作用。

我知道我可能遗漏了一些非常重要的细节,如果有人能指出我的错误,我会很高兴。提前致谢!

【问题讨论】:

    标签: multithreading gpu metal compute-shader threadgroup


    【解决方案1】:

    当你写output += blockIndices[...]时,所有线程都会尝试同时执行这个操作。但是由于output 不是原子变量,这会导致竞争条件。这不是线程安全操作。

    您的第二个解决方案是正确的。您只需要一个线程来收集结果(尽管您也可以将其拆分为多个线程)。如果您移除障碍,它仍然可以正常工作可能只是由于运气。

    【讨论】:

    • 感谢您的回复!您的回答对我来说完全有道理,谢谢您清理了我脑海中的烂摊子。顺便说一句,我试图将数据保存拆分为 2 个线程,似乎它有效。但是,如果我尝试超越这一点,我就会开始遇到一些随机问题。
    • 一个典型的解决方案是有 288/2 = 144 个线程,每个线程总结两个值。然后添加一个线程组屏障。接下来,您使用 144/2 = 72 个线程,每个线程将两个值相加。紧随其后的是线程组屏障,接下来你有 72/2 = 36 个线程。等等。这比让 1 个线程完成所有汇总更有效,但您确实需要确保到处都有障碍。
    猜你喜欢
    • 2013-06-09
    • 1970-01-01
    • 2019-08-11
    • 2012-01-13
    • 1970-01-01
    • 2021-05-23
    • 2020-12-27
    • 2017-09-04
    相关资源
    最近更新 更多