Вычислите время выполнения шейдера между DirectX11 и OpenGL

#c #opengl #directx #directx-11

Вопрос:

Я изучаю вычислительные шейдеры в DirectX и OpenGL

и я написал некоторый код для тестирования вычислительного шейдера и проверил время выполнения.

но была некоторая разница между временем выполнения DirectX и Opengl

введите описание изображения здесь

и изображение выше показывает, насколько оно отличается (слева-DirectX, справа-Opengl, время-наносекунды).

даже вычислительный шейдер DirectX работает медленнее, чем процессор

вот мой код, который вычисляет сумму обоих векторов: один для вычислительного шейдера и один для процессора

         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;
}
 

and hlsl code

 
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 в 10 раз лучше, чем у DirectX

я не понимаю, почему это произошло, есть ли что-то замедляющее производительность??

это код, который при создании 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, amp;m_renderID);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_renderID);
    glBufferData(GL_SHADER_STORAGE_BUFFER, int(size * count), pData, GL_STATIC_DRAW);
 

это весь код для выполнения вычислительного шейдера в обоих API

и каждый результат показывает мне, что opengl лучше, чем directx

Какие свойства делают это различие?

находится в буфере или шейдерном коде?

Комментарии:

1. Вы вообще не измеряете время, необходимое графическому процессору для выполнения шейдера, вы измеряете только время, необходимое драйверу графического процессора для выполнения ваших команд. Цифры, которые вы получаете, совершенно бессмысленны.

2. Даже если бы вы измерили время работы графического процессора, цифры все равно были бы бессмысленными, поскольку вы в основном измеряете только накладные расходы на отправку одной вычислительной команды, что является гигантским по сравнению с фактической рабочей нагрузкой, которую выполняет ваш шейдер (что практически ничего не значит).

3. @derhass Не могли бы мы снова открыть этот вопрос? На самом деле об этом можно многое сказать, что принесет пользу сообществу в целом

4. @mrvux ты получил мое повторное голосование, понятия не имею, почему оно было закрыто в первую очередь, так как оно в значительной степени отвечает.


Ответ №1:

Итак, во-первых, как упоминалось в комментариях, вы измеряете не время выполнения GPU, а время для записи самой команды (gpu выполнит ее позже в какой-то момент, а затем решит сбросить команды).

Чтобы измерить время выполнения GPU, вам необходимо использовать запросы

В вашем случае (Direct3D11, но аналогично для OpenGL) вам нужно создать 3 запроса :

  • 2 должно иметь тип D3D11_QUERY_TIMESTAMP (для измерения времени начала и окончания)
  • 1 должен иметь тип D3D11_QUERY_TIMESTAMP_DISJOINT (непересекающийся запрос укажет, что результаты метки времени больше недействительны, например, если изменяется тактовая частота вашего графического процессора). Непересекающийся запрос также даст вам частоту, необходимую для преобразования в миллисекунды.

поэтому, чтобы измерить время вашего графического процессора (в контексте устройства, вы должны решить следующую проблему):

  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);
 

Итак, теперь, почему «просто» запись этой команды занимает много времени по сравнению с простым выполнением тех же вычислений на процессоре.

Вы выполняете только несколько добавлений к 32 элементам, что является чрезвычайно тривиальной и быстрой операцией для центрального процессора.

Если вы начнете увеличивать количество элементов, то GPU в конечном итоге возьмет верх.

Во-первых, если у вас есть устройство D3D, созданное с флагом ОТЛАДКИ, удалите флаг в профиль. Некоторые драйверы (в частности, NVIDIA) записывают команды очень плохо с этим флагом.

Во-вторых, драйвер выполнит довольно много проверок при вызове диспетчерской (проверьте, что ресурсы имеют правильный формат, правильные шаги, ресурс все еще жив….). Драйвер DirectX, как правило, выполняет много проверок, поэтому он может быть немного медленнее, чем GL (но не на такую величину, что приводит к последнему пункту).

Наконец, вполне вероятно, что графический процессор/драйвер выполняет прогрев вашего шейдера (некоторые драйверы асинхронно преобразуют байт-код dx в свой собственный аналог, поэтому при вызове

 device->CreateComputeShader();
 

Это может быть сделано немедленно или помещено в очередь (AME выполняет функцию очереди, см. Эту ссылку Gpu Открывает элементы управления компилятором шейдеров).
Если вы вызовете диспетчеризацию до того, как эта задача будет эффективно обработана, вам также может потребоваться подождать.

Также обратите внимание, что в настоящее время большинство графических процессоров имеют кэш на диске, поэтому первая компиляция/использование также может повлиять на производительность.

Поэтому вам следует попробовать позвонить диспетчеру несколько раз и проверить, отличается ли время процессора после первого вызова.

Комментарии:

1. Извините за позднюю проверку и спасибо вам! это мне очень помогло , я протестировал массивные элементы и увидел, что между графическим процессором и процессором существует огромная разница во времени вычислений . каждая отправка , которую я выполнял , между dx и gl составляет 10000 наносекунд, и, как вы сказали, я предположил, что это своего рода отладка, ваш ответ проясняет 🙂