计算 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 时间是否不同。
我正在研究 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 时间是否不同。