计算 DirectX11 和 OpenGL 之间的着色器执行时间

Compute Shader execution time between DirectX11 and OpenGL

我正在研究 DirectX 和 OpenGL 中的计算着色器

我写了一些代码来测试计算着色器并检查了执行时间。

但是 DirectX 的执行时间和 Opengl 的

有一些不同

和上图代表相差多少(左边是DirectX,右边是Opengl,时间代表纳秒)

甚至 DirectX 计算着色器也比 cpu

这是我计算两个向量之和的代码 一个用于计算着色器,一个用于 cpu

        std::vector<Data> dataA(32);
        std::vector<Data> dataB(32);

        for (int i = 0; i < 32; ++i)
        {
            dataA[i].v1 = glm::vec3(i, i, i);
            dataA[i].v2 = glm::vec2(i, 0);

            dataB[i].v1 = glm::vec3(-i, i, 0.0f);
            dataB[i].v2 = glm::vec2(0, -i);
        }

        InputBufferA = ShaderBuffer::Create(sizeof(Data), 32, BufferType::Read, dataA.data());
        InputBufferB = ShaderBuffer::Create(sizeof(Data), 32, BufferType::Read, dataB.data());
        OutputBufferA =ShaderBuffer::Create(sizeof(Data), 32, BufferType::ReadWrite);

        computeShader->Bind();
        InputBufferA->Bind(0, ShaderType::CS);
        InputBufferB->Bind(1, ShaderType::CS);
        OutputBufferA->Bind(0,ShaderType::CS);

        // Check The Compute Shader Calculation time
        std::chrono::system_clock::time_point time1 = std::chrono::system_clock::now();
        RenderCommand::DispatchCompute(1, 1, 1);
        std::chrono::system_clock::time_point time2 = std::chrono::system_clock::now();
        std::chrono::nanoseconds t =time2- time1;
        QCAT_CORE_INFO("Compute Shader time : {0}", t.count());
        
        // Check The Cpu Calculation time
        std::vector<Data> dataC(32);
        time1 = std::chrono::system_clock::now();
        for (int i = 0; i < 32; ++i)
        {
            dataC[i].v1 = (dataA[i].v1 + dataB[i].v1);
            dataC[i].v2 = (dataA[i].v2 + dataB[i].v2);
        }
        time2 = std::chrono::system_clock::now();
        t = time2 - time1;
        QCAT_CORE_INFO("CPU time : {0}", t.count() );

这是 glsl 代码

#version 450 core
struct Data
{
    vec3 a;
    vec2 b;
};
layout(std430,binding =0) readonly buffer Data1
{
    Data input1[];
};

layout(std430,binding =1) readonly buffer Data2
{
    Data input2[];
};

layout(std430,binding =2) writeonly buffer Data3
{
    Data outputData[];
};

layout (local_size_x = 32, local_size_y = 1, local_size_z = 1) in;

void main()
{
  uint index = gl_GlobalInvocationID.x;

  outputData[index].a = input1[index].a + input2[index].a;
  outputData[index].b = input1[index].b + input2[index].b;
}

和hlsl代码


struct Data
{
    float3 v1;
    float2 v2;
};
StructuredBuffer<Data> gInputA : register(t0);
StructuredBuffer<Data> gInputB : register(t1);
RWStructuredBuffer<Data> gOutput : register(u0);

[numthreads(32,1,1)]
void CSMain(int3  dtid : SV_DispatchThreadID)
{
    gOutput[dtid.x].v1 = gInputA[dtid.x].v1 + gInputB[dtid.x].v1;
    gOutput[dtid.x].v2 = gInputA[dtid.x].v2 + gInputB[dtid.x].v2;
}

是不是很简单的代码?

但 Opengl 的性能时间是 DirectX 的 10 倍

我不明白为什么会这样,是不是性能变慢了??

这是我创建 RWStructuredBuffer 时唯一与 StructuredBuffer 不同的代码 BindFlags = D3D11_BIND_SHADER_RESOURCE

        desc.Usage = D3D11_USAGE_DEFAULT;
        desc.ByteWidth = size * count;
        desc.BindFlags = D3D11_BIND_UNORDERED_ACCESS;
        desc.CPUAccessFlags = 0;
        desc.StructureByteStride = size;
        desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;

        D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc;
        uavDesc.Format = DXGI_FORMAT_UNKNOWN;
        uavDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
        uavDesc.Buffer.FirstElement = 0;
        uavDesc.Buffer.Flags = 0;
        uavDesc.Buffer.NumElements = count;

在 opengl 中,我以这种方式创建 SSBO

    glGenBuffers(1, &m_renderID);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_renderID);
    glBufferData(GL_SHADER_STORAGE_BUFFER, int(size * count), pData, GL_STATIC_DRAW);

这是 API

中执行计算着色器的所有代码

每个结果都告诉我 opengl 比 directx 好

是什么属性造成了这种差异?

在 Buffer 或 ShaderCode 中?

所以首先,正如评论中提到的,你不是在测量 GPU 执行时间,而是记录命令本身的时间(gpu 将在稍后执行它,然后它决定刷新命令)。

为了测量GPU执行时间,需要使用Queries

在您的情况下(Direct3D11,但与 OpenGL 类似),您需要创建 3 个查询:

  • 2 必须是 D3D11_QUERY_TIMESTAMP 类型(以测量开始和结束时间)
  • 1 必须是 D3D11_QUERY_TIMESTAMP_DISJOINT 类型(不相交查询将指示时间戳结果不再有效,例如,如果您的 gpu 时钟频率发生变化)。不相交查询还会为您提供频率,需要将其转换为毫秒。

所以要测量你的 gpu 时间(在设备上下文中,你会发出以下问题):

 d3d11DeviceContext->Begin(yourDisjointQuery);
 d3d11DeviceContext->Begin(yourFirstTimeStampQuery);

 Dispatch call goes here

 d3d11DeviceContext->Begin(yourSecondTimeStampQuery);
 d3d11DeviceContext->Begin(yourDisjointQuery);

请注意,时间戳查询仅调用 begin,这是完全正常的,您只需询问“gpu 时钟”即可简化。

然后就可以调用了(顺序无所谓):

d3d11DeviceContext->GetData(yourDisjointQuery);
d3d11DeviceContext->GetData(yourSecondTimeStampQuery);
d3d11DeviceContext->GetData(yourFirstTimeStampQuery);

检查不相交结果是否不相交,并从中获取频率:

double delta = end - start;
double frequency;
double ticks = delta / (freq / 10000000);

那么现在为什么“仅”记录该命令比仅在 CPU.

上进行相同的计算要花费大量时间

您只需对 32 个元素执行少量加法,这对于 CPU.

来说是极其简单且快速的操作

如果您开始增加元素数量,那么 GPU 最终将接管。

首先,如果您创建的 D3D 设备带有 DEBUG 标志,请移除该标志以进行配置。某些驱动程序(尤其是 NVIDIA)命令记录在使用该标志时表现非常差。

其次,当您调用 Dispatch 时,驱动程序将执行大量检查(检查资源格式是否正确、步幅是否正确、资源是否仍然存在……)。 DirectX 驱动程序往往会进行大量检查,因此它可能比 GL 驱动程序稍慢(但不会慢那么多,这导致了最后一点)。

最后,GPU/Driver 可能会在您的着色器上进行预热(某些驱动程序将 dx 字节码异步转换为其本机对应字节码,因此当您调用

device->CreateComputeShader();

它可能会立即完成或放入队列中(AME 执行队列操作,请参阅此 link Gpu Open Shader Compiler controls)。 如果您在此任务有效处理之前调用 Dispatch,您也可能需要等待。

另请注意,如今大多数 GPU 在磁盘上都有缓存,因此第一个 compile/use 也可能会影响性能。

因此您应该尝试多次调用 Dispatch,并检查第一次调用后 CPU 时间是否不同。