使用numthreads(1,1,1)的计算着色器运行速度极慢

时间:2013-11-08 13:47:12

标签: directx hlsl compute-shader directcompute

我刚开始学习DirectX编程,使用F#和SharpDX作为.NET包装器。作为测试用例,我渲染了Mandelbrot集。使用2个计算着色器完成计算。

第一个着色器计算每个像素的深度(函数“CalcMandel”),结果存储在RWStructuredBuffer中。这种计算需要大量的单次或双次乘法,但它在我的GPU(AMD 7790)上的速度非常快。 “CalcMandel”具有属性

[numthreads(16, 16, 1)]

并通过

发送
context.Dispatch (imageWidth / 16, imageHeight / 16, 1)

这里没有问题 - “核心”Mandelbrot集的1000 x 800像素图像运行速度超过1000 fps(在GPU上使用单精度)。


第二个着色器几乎没有做任何事情:它计算先前计算的最小值,最大值和平均值(函数“CalcMinMax”)。 “CalcMinMax”具有属性

[numthreads(1, 1, 1)]

并通过

调用
context.Dispatch (1,1,1)

对于当时给定的图像大小,单个GPU线程必须遍历800.000个整数的缓冲区以计算最小值,最大值和平均值。我使用单个线程,因为我不知道如何以并行方式实现此计算。

问题: “CalcMinMax”非常慢:帧速率从1000 fps下降到5 fps!

我的问题: 这有什么不对?我使用了错误的设置/参数(numthreads)吗?如何加快最小 - 最大计算?

我的想法: 我的第一个假设是访问RWBuffer可能会很慢 - 事实并非如此。当我用常量替换缓冲区访问时,帧速率没有增加。

我的GPU有appr。 900个着色器核心并使用数千个线程来计算Mandelbrot集合,而“CalcMinMax”仅使用一个线程。然而,我仍然不明白为什么事情变得如此缓慢。

我很感激任何建议!

=============================================== =

// HLSL CONTENT(省略Mandelbrot集计算):

cbuffer cbCSMandel : register( b0 )
{

double  a0, b0, da, db;
double  ja0, jb0;   
int max_iterations;
bool julia;     int  cycle;
int width;      int height;
double colorFactor;
int algoIndex;
int step;
};


struct statistics
{
  int   minDepth;
  int     axDepth;
  float avgDepth;
  int   loops;
};

RWStructuredBuffer<float4> colorOutputTable :   register (u0);
StructuredBuffer<float4> output2 :          register (t0);
RWStructuredBuffer<int> counterTable :          register (u1);
RWStructuredBuffer<float4> colorTable :     register (u2);

RWStructuredBuffer<statistics>statsTable :      register (u3);


// Mandelbrot calculations….
// Results are written to counterTable and colorOutputTable


// I limit the samples to 10000 pixels because calcMinMax is too slow
#define NUM_SAMPLES 10000;

void calcMinMax() 
{
    int minDepth = 64000;
    int maxDepth = 0;
    int len = width * height;
    int crit = len / NUM_SAMPLES;
    int steps = max (crit, 1);
    int index = 0;          
    int sumCount = 0;
    float sum = 0.0;

while (index < len)
{
    int cnt = counterTable[index];
    minDepth = cnt < minDepth & cnt > 0 ? cnt : minDepth;
    maxDepth = cnt > maxDepth ? cnt : maxDepth;
    sum += cnt > 0 ? cnt : 0.0f;
sumCount += cnt > 0 ? 1 : 0; 
    index += steps;
}

statsTable[0].minDepth = minDepth;
statsTable[0].maxDepth = maxDepth; 
statsTable[0].avgDepth = sum / sumCount;
statsTable[0].loops += 1; 
}


[numthreads(1, 1, 1)]
void CalcMinMax ( uint3 Gid : SV_GroupID, uint3 DTid : SV_DispatchThreadID, uint3 GTid :    SV_GroupThreadID, uint GI : SV_GroupIndex )

{
    switch (GI)     // this switch is used to verify GI number (always 0)
{
        case 0: calcMinMax();
    break;

        default: ;
    break;

}
}

// * ** * ** * ** * ** * ** * ** * F#程序(最小 - 最大部分) * * * * ** * ** * ** *

着色器设置:

use minMaxShaderCode = ShaderBytecode.CompileFromFile(shaderPath, "CalcMinMax", "cs_5_0")                                                                
minMaxShader <- new ComputeShader(device, minMaxShaderCode.Bytecode.Data  )  

着色器用法:

// ---------- CONNECT MinMap Shader
context.ComputeShader.Set(minMaxShader)    
context.ComputeShader.SetUnorderedAccessView(STATS_SLOT, statsBuffer.BufferView) 

context.ComputeShader.SetConstantBuffer(CONSTANT_SLOT, constantBuffer)
context.ComputeShader.SetUnorderedAccessView (COUNTER_SLOT, dataBuffer.BufferView)  
context.Dispatch (1,1,1)

// ---------- DISCONNECT MinMap Shader            
context.ComputeShader.SetConstantBuffer(CONSTANT_SLOT, null)
context.ComputeShader.SetUnorderedAccessView (STATS_SLOT, null) 
context.ComputeShader.SetUnorderedAccessView (COUNTER_SLOT, null) 
context.ComputeShader.Set (null) 

阅读统计数据:

context.CopyResource(statsBuffer.DataBuffer, statsBuffer.StagingBuffer)
let boxer, stream = context.MapSubresource(statsBuffer.StagingBuffer, MapMode.Read, MapFlags.None)                                                                                                                                    
calcStatistics <- stream.Read<CalcStatistics>()
context.UnmapSubresource(statsBuffer.DataBuffer, 0)

2 个答案:

答案 0 :(得分:6)

如果只调度1个线程,那么GPU上的每个着色器单元都将处于空闲状态,等待该线程完成。你需要并行化你的minmax算法,并且考虑到你必须计算一个值数组以得出一个单独的值,这是一个典型的减少问题。更有效的方法是递归地计算局部最小值/最大值。可以看到一个详细的解释和一个对数组值求和的例子here(从幻灯片19开始)。

答案 1 :(得分:0)

非常感谢您的反馈。我的问题得到了回答。

在他的回复中,akhanubis分享了一个PDF文档的链接,该文档描述了GPU上的map-reduce问题。在我在stackoverflow中发布我的问题之前,我在互联网上进行了广泛的搜索 - 我已经发现了这篇论文,并且已经阅读了两次!

为什么我仍然错过了这一点?在最坏的情况下,在4M阵列上减少8毫秒似乎对我来说是可接受的(仅有800,000点)。但我没有意识到,即使演示中最糟糕的情况至少比我的单线程方法快100倍,因为它使用了128个线程组。

我将使用本文中的概念来实现我的min-max-calculation的多线程版本。