我试图在计算机着色器中实现一个自旋锁。但我的实现似乎没有锁定任何东西。
下面是实现自旋锁的方法:
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)。
下面是单线程版本,生成正确的结果:
[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是由不起作用的锁造成的。
[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();
}
使用的数据:
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);
调度代码:
FImmediateContext->CSSetShader(FComputeShaderGrayAutoComputeSumSqr, NULL, 0);
FImmediateContext->Dispatch(FImageParams.RawImageSize.X, FImageParams.RawImageSize.Y, 1);
函数GetPixel16BitGrayFromRawImage访问RawImage字节地址缓冲区,从灰度图像中获取16位像素值。它产生了预期的结果。
任何帮助都很感激。
发布于 2019-09-23 14:04:33
你在这里是XY问题的受害者。
让我们从Y问题开始。您的自旋锁不锁定。要理解为什么您的自旋锁不工作,您需要检查GPU如何处理您正在创建的情况。您发出一个翘曲,由一个或多个线程组组成,每个线程由多个线程组成。只要执行是并行的,翘曲的执行就会很快,这意味着所有产生翘曲的线程(如果您愿意的话)都必须同时执行相同的指令。每次插入一个条件(比如algo中的while
循环),您的一些线程必须选择一条路由,而另一些则是另一条。这被称为线程的发散。问题是不能并行执行不同的指令。
在这种情况下,GPU可以选择两条路线中的一条:
有趣的是:
没有强制转换规则来说明GPU应该如何处理分支.
您无法预测GPU是否会使用一种方法或另一种方法,而且在动态分支情况下,无法预先知道GPU是否会让直路、另一条、线程较少的分支或线程较多的分支休眠。不可能事先知道,不同的GPU可能会以不同的方式执行代码(而且会这样做)。同一个GPU甚至可以用不同的驱动程序版本更改其执行。
在spinlock的情况下,您的GPU (及其驱动程序,以及当前使用的编译器版本)很可能采用平面分支策略。这意味着两个分支都由翘曲的所有线程执行,因此基本上根本没有锁。
如果更改代码(或在循环之前添加[branch]
属性),则可以强制动态分支流。但这不能解决你的问题。在自旋锁的特殊情况下,您要求GPU所做的是关闭除一个线程之外的所有线程。这并不是GPU想要做的。GPU将尝试执行相反的操作,并关闭唯一以不同方式计算条件的线程。这确实会减少分歧,提高性能…。但在您的情况下,它将关闭唯一的线程,而不是在一个无休止的循环。因此,您可能会得到一个完整的线程波前锁在一个没完没了的循环,因为唯一一个可能解除loop...is睡眠。您的自旋锁实际上已成为死锁。
现在,在您的特定机器中,程序甚至可能运行良好。但是您的零保证程序将在其他机器上运行,甚至在不同的驱动程序版本上运行。你更新驱动和繁荣,你的程序突然命中GPU超时和崩溃。
GPU中关于自旋锁的最好建议是…。.不要用.永远不会。
现在让我们回到Y问题。
您真正需要的是一种计算大型二维数组中的数据和的方法。所以你真正想要的是一个很好的约简算法。有一些在互联网上,或者你可以自己编码,取决于你的需要。
如果你需要的话,我只会添加几个链接让你开始。
NVIDIA - GPU技术会议2010年
多诺万- GPU并行扫描
发布于 2019-10-25 09:10:39
正如kefren所提到的,您的自旋锁不工作,因为翘曲发散。但是,有一种方法可以设计不导致死锁的gpu自旋锁。我使用这个自旋锁的像素着色器,但它也应该工作在一个计算着色器。
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级别,这会导致原子操作的未定义行为。这可能导致对这些助手调用的循环无限执行,从而导致死锁。
https://stackoverflow.com/questions/58006663
复制相似问题