directx - 加载/存储到 XMFLOAT4 比使用 XMVECTOR 更快?

标签 directx direct3d directx-11

我正在浏览 DirectX Math/XNA Math 库,当我读到 XMVECTOR 的对齐要求时,我很好奇(现在是 DirectX::XMVECTOR) ,以及在执行数学运算时如何使用 XMFLOAT* 作为成员,使用 XMLoad*XMStore*。我对权衡特别好奇,所以我做了一个实验,就像我相信许多其他人所做的那样,并进行了测试,以了解为每个操作加载和存储向量会损失多少。这是生成的代码:

#include <Windows.h>

#include <chrono>
#include <cstdint>
#include <DirectXMath.h>
#include <iostream>

using std::chrono::high_resolution_clock;

#define TEST_COUNT          1000000000l

int main(void)
{
    DirectX::XMVECTOR v1 = DirectX::XMVectorSet(1, 2, 3, 4);
    DirectX::XMVECTOR v2 = DirectX::XMVectorSet(2, 3, 4, 5);
    DirectX::XMFLOAT4 x{ 1, 2, 3, 4 };
    DirectX::XMFLOAT4 y{ 2, 3, 4, 5 };

    std::chrono::system_clock::time_point start, end;
    std::chrono::milliseconds duration;

    // Test with just the XMVECTOR
    start = high_resolution_clock::now();
    for (uint64_t i = 0; i < TEST_COUNT; i++)
    {
        v1 = DirectX::XMVectorAdd(v1, v2);
    }
    end = high_resolution_clock::now();
    duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);

    DirectX::XMFLOAT4 z;
    DirectX::XMStoreFloat4(&z, v1);
    std::cout << std::endl << "z = " << z.x << ", " << z.y << ", " << z.z << std::endl;
    std::cout << duration.count() << " milliseconds" << std::endl;

    // Now try with load/store
    start = high_resolution_clock::now();
    for (uint64_t i = 0; i < TEST_COUNT; i++)
    {
        v1 = DirectX::XMLoadFloat4(&x);
        v2 = DirectX::XMLoadFloat4(&y);

        v1 = DirectX::XMVectorAdd(v1, v2);
        DirectX::XMStoreFloat4(&x, v1);
    }
    end = high_resolution_clock::now();
    duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);

    std::cout << std::endl << "x = " << x.x << ", " << x.y << ", " << x.z << std::endl;
    std::cout << duration.count() << " milliseconds" << std::endl;
}

运行调试构建会产生输出:

z = 3.35544e+007, 6.71089e+007, 6.71089e+007
25817 milliseconds

x = 3.35544e+007, 6.71089e+007, 6.71089e+007
84344 milliseconds

好吧,大约慢三倍,但是有人真的认真对待调试版本的性能测试吗?以下是我进行发布构建时的结果:

z = 3.35544e+007, 6.71089e+007, 6.71089e+007
1980 milliseconds

x = 3.35544e+007, 6.71089e+007, 6.71089e+007
670 milliseconds

就像魔术一样,XMFLOAT4 的运行速度几乎快了三倍!不知何故,形势发生了逆转。看看代码,这对我来说毫无意义;第二部分运行第一部分运行的命令的超集!一定是出了什么问题,或者是我没有考虑到的事情。很难相信编译器能够将第二部分比更简单且理论上更高效的第一部分优化九倍。我唯一合理的解释涉及 (1) 缓存行为,(2) XMVECTOR 无法利用的一些疯狂的无序执行,(3) 编译器正在进行一些疯狂的优化, (4) 直接使用 XMVECTOR 会产生一些隐含的低效率问题,而在使用 XMFLOAT4 时可以对其进行优化。也就是说,编译器从内存加载和存储 XMVECTOR 的默认方式比 XMLoad*XMStore* 效率低。我尝试检查反汇编,但我对 X86 和/或 SSE2 不太熟悉,并且 Visual Studio 做了一些疯狂的优化,使得很难跟踪源代码。我还尝试了 Visual Studio 性能分析工具,但这没有帮助,因为我不知道如何让它显示反汇编而不是代码。我从中得到的唯一有用的信息是,第一次调用 XMVectorAdd 约占所有周期的 48.6%,而第二次调用 XMVectorAdd 约占所有周期的 4.4%所有周期。

编辑: 经过更多调试后,下面是在循环内运行的代码的程序集。对于第一部分:

002912E0  movups      xmm1,xmmword ptr [esp+18h]     <-- HERE
002912E5  add         ecx,1  
002912E8  movaps      xmm0,xmm2                      <-- HERE
002912EB  adc         esi,0  
002912EE  addps       xmm0,xmm1  
002912F1  movups      xmmword ptr [esp+18h],xmm0     <-- HERE
002912F6  jne         main+60h (0291300h)  
002912F8  cmp         ecx,3B9ACA00h  
002912FE  jb          main+40h (02912E0h)  

第二部分:

00291400  add         ecx,1  
00291403  addps       xmm0,xmm1  
00291406  adc         esi,0  
00291409  jne         main+173h (0291413h)  
0029140B  cmp         ecx,3B9ACA00h  
00291411  jb          main+160h (0291400h)

请注意,这两个循环确实几乎相同。唯一的区别是第一个 for 循环似乎是执行加载和存储的循环!由于 x 和 y 位于堆栈上,Visual Studio 似乎进行了大量优化。将它们都更改为位于堆上(因此必须进行写入),并且机器代码现在相同。一般情况是这样吗?使用存储类真的没有负面影响吗?我想除了完全优化的版本之外。

最佳答案

如果你定义

DirectX::XMVECTOR v3 = DirectX::XMVectorSet(2, 3, 4, 5);

并使用 v3 代替 v1 结果: ...

 for (uint64_t i = 0; i < TEST_COUNT; i++)
    {
        v3 = DirectX::XMVectorAdd(v1, v2);
    }

使用 XMLoadFloat4 和 XMStoreFloat4 获得的代码比第二部分代码更快

关于directx - 加载/存储到 XMFLOAT4 比使用 XMVECTOR 更快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23419258/

相关文章:

python - C++ Python 关闭我的程序

c++ - Directx11 项目不会显示图形输出

c# - 旧 PC 无法运行 XNA,对于创建简单的 2D 游戏有什么建议?

c++ - Direct2d + winapi 矩形未完全填充

c++ - 不再找到 DirectX 命名空间

c++ - Direct3D 11 深度缓冲区导致黑屏

c# - 将按钮控件嵌入到现有的 Direct3D 应用程序中

c++ - 你如何在 DirectX 11 中绘制文本?

directx-11 - XMVector3Project 意外行为

c++ - DirectX 拉伸(stretch)像素