首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在HLSL SpinLock着色器中实现DirectCompute

在HLSL SpinLock着色器中实现DirectCompute
EN

Stack Overflow用户
提问于 2019-09-19 08:19:18
回答 2查看 1.6K关注 0票数 3

我试图在计算机着色器中实现一个自旋锁。但我的实现似乎没有锁定任何东西。

下面是实现自旋锁的方法:

代码语言:javascript
运行
复制
void LockAcquire()
{
    uint Value = 1;

    [allow_uav_condition]
    while (Value) {
        InterlockedCompareExchange(DataOutBuffer[0].Lock, 0, 1, Value);
    };
}

void LockRelease()
{
    uint Value;
    InterlockedExchange(DataOutBuffer[0].Lock, 0, Value);
}

背景:我需要一个自旋锁,因为我必须计算一个大的二维数组中的数据和。总数是双倍的。用单线程和双循环计算和得到正确的结果。使用多线程计算和会产生错误的结果,即使在计算和时引入自旋锁以避免冲突。

我不能使用InterLockedAdd,因为和不符合32位整数,我使用的是着色模型5(编译器47)。

下面是单线程版本,生成正确的结果:

代码语言:javascript
运行
复制
[numthreads(1, 1, 1)]
void CSGrayAutoComputeSumSqr(
    uint3 Gid  : SV_GroupID,
    uint3 DTid : SV_DispatchThreadID, // Coordinates in RawImage window
    uint3 GTid : SV_GroupThreadID,
    uint  GI   : SV_GroupIndex)
{
    if ((DTid.x == 0) && (DTid.y == 0)) {
        uint2 XY;
        int   Mean = (int)round(DataOutBuffer[0].GrayAutoResultMean);
        for (XY.x = 0; XY.x < (uint)RawImageSize.x; XY.x++) {
            for (XY.y = 0; XY.y < (uint)RawImageSize.y; XY.y++) {
                int  Value  = GetPixel16BitGrayFromRawImage(RawImage, rawImageSize, XY);
                uint UValue = (Mean - Value) * (Mean - Value);
                DataOutBuffer[0].GrayAutoResultSumSqr += UValue;
            }
        }
    }
}

下面是多线程版本。这个版本在每次执行时产生相似但不同的结果,而IMO是由不起作用的锁造成的。

代码语言:javascript
运行
复制
[numthreads(1, 1, 1)]
void CSGrayAutoComputeSumSqr(
    uint3 Gid  : SV_GroupID,
    uint3 DTid : SV_DispatchThreadID, // Coordinates in RawImage window
    uint3 GTid : SV_GroupThreadID,
    uint  GI   : SV_GroupIndex)
{
    int  Value  = GetPixel16BitGrayFromRawImage(RawImage, RawImageSize, DTid.xy);
    int  Mean   = (int)round(DataOutBuffer[0].GrayAutoResultMean);
    uint UValue = (Mean - Value) * (Mean - Value);
    LockAcquire();
    DataOutBuffer[0].GrayAutoResultSumSqr += UValue;
    LockRelease();
}

使用的数据:

代码语言:javascript
运行
复制
cbuffer TImageParams : register(b0)
{
    int2   RawImageSize;       // Actual image size in RawImage
}

struct TDataOutBuffer
{
    uint   Lock;                             // Use for SpinLock
    double GrayAutoResultMean;
    double GrayAutoResultSumSqr;
};

ByteAddressBuffer                  RawImage       : register(t0);
RWStructuredBuffer<TDataOutBuffer> DataOutBuffer  : register(u4);

调度代码:

代码语言:javascript
运行
复制
FImmediateContext->CSSetShader(FComputeShaderGrayAutoComputeSumSqr, NULL, 0);
FImmediateContext->Dispatch(FImageParams.RawImageSize.X, FImageParams.RawImageSize.Y, 1);

函数GetPixel16BitGrayFromRawImage访问RawImage字节地址缓冲区,从灰度图像中获取16位像素值。它产生了预期的结果。

任何帮助都很感激。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2019-09-23 14:04:33

你在这里是XY问题的受害者。

让我们从Y问题开始。您的自旋锁不锁定。要理解为什么您的自旋锁不工作,您需要检查GPU如何处理您正在创建的情况。您发出一个翘曲,由一个或多个线程组组成,每个线程由多个线程组成。只要执行是并行的,翘曲的执行就会很快,这意味着所有产生翘曲的线程(如果您愿意的话)都必须同时执行相同的指令。每次插入一个条件(比如algo中的while循环),您的一些线程必须选择一条路由,而另一些则是另一条。这被称为线程的发散。问题是不能并行执行不同的指令。

在这种情况下,GPU可以选择两条路线中的一条:

  1. 动态分支--这意味着波前(翘曲)采取两条路由中的一条,并使应该采取另一条的线程失活。然后,它回滚,捡起剩下的熟睡的线程。
  2. 平面分支,这意味着所有线程都执行两个分支,然后每个线程丢弃不想要的结果,并保持正确的结果。

有趣的是:

没有强制转换规则来说明GPU应该如何处理分支.

您无法预测GPU是否会使用一种方法或另一种方法,而且在动态分支情况下,无法预先知道GPU是否会让直路、另一条、线程较少的分支或线程较多的分支休眠。不可能事先知道,不同的GPU可能会以不同的方式执行代码(而且会这样做)。同一个GPU甚至可以用不同的驱动程序版本更改其执行。

在spinlock的情况下,您的GPU (及其驱动程序,以及当前使用的编译器版本)很可能采用平面分支策略。这意味着两个分支都由翘曲的所有线程执行,因此基本上根本没有锁。

如果更改代码(或在循环之前添加[branch]属性),则可以强制动态分支流。但这不能解决你的问题。在自旋锁的特殊情况下,您要求GPU所做的是关闭除一个线程之外的所有线程。这并不是GPU想要做的。GPU将尝试执行相反的操作,并关闭唯一以不同方式计算条件的线程。这确实会减少分歧,提高性能…。但在您的情况下,它将关闭唯一的线程,而不是在一个无休止的循环。因此,您可能会得到一个完整的线程波前锁在一个没完没了的循环,因为唯一一个可能解除loop...is睡眠。您的自旋锁实际上已成为死锁

现在,在您的特定机器中,程序甚至可能运行良好。但是您的零保证程序将在其他机器上运行,甚至在不同的驱动程序版本上运行。你更新驱动和繁荣,你的程序突然命中GPU超时和崩溃。

GPU中关于自旋锁的最好建议是…。.不要用.永远不会。

现在让我们回到Y问题

您真正需要的是一种计算大型二维数组中的数据和的方法。所以你真正想要的是一个很好的约简算法。有一些在互联网上,或者你可以自己编码,取决于你的需要。

如果你需要的话,我只会添加几个链接让你开始。

关于发散的离题

NVIDIA - GPU技术会议2010年

戈德克-入门教程

多诺万- GPU并行扫描

多核和GPU编程

票数 5
EN

Stack Overflow用户

发布于 2019-10-25 09:10:39

正如kefren所提到的,您的自旋锁不工作,因为翘曲发散。但是,有一种方法可以设计不导致死锁的gpu自旋锁。我使用这个自旋锁的像素着色器,但它也应该工作在一个计算着色器。

代码语言:javascript
运行
复制
RWTexture2D<uint> mutex; // all values are 0 in the beginning

void doCriticalPart(int2 coord) {
   bool keepWaiting = true;
   while(keepWaiting) {
      uint originalValue;
      // try to set the mutex to 1
      InterlockedCompareExchange(mutex[coord], 0, 1, originalValue);
      if(originalValue == 0) { // nothing was locked (previous entry was 0)
         // do your stuff
         // unlock mutex again
         InterlockedExchange(mutex[coord], 0, originalValue);
         // exit loop
         keepWaiting = false;
      }
   }
}

我在第30页的学士论文中详细解释了这一点的原因。还有一个GLSL的例子。

注意:如果您想在像素着色器中使用此自旋锁,则必须在调用此函数之前检查是否为SV_SampleIndex == 0。像素着色器可以产生一些辅助调用以确定纹理获取mipmap级别,这会导致原子操作的未定义行为。这可能导致对这些助手调用的循环无限执行,从而导致死锁。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/58006663

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档