gpt4 book ai didi

c++ - 分析_mm_setzero_ps和{0.0f,0.0f,0.0f,0.0f}

转载 作者:搜寻专家 更新时间:2023-10-31 00:55:47 24 4
gpt4 key购买 nike

编辑:正如科迪·格雷在他的评论中指出的那样,使用禁用的优化进行性能分析完全是浪费时间。那我应该如何进行这项测试?

如果定义了XMVectorZero,则使用_XM_SSE_INTRINSICS_,如果未定义,则在其_mm_setzero_ps中使用Microsoft。我决定检查获胜人数。因此,我在Release x86中使用了以下程序,并将Configuration Properties> C / C++> Optimization> Optimization设置为{0.0f,0.0f,0.0f,0.0f}

constexpr __int64 loops = 1e9;
inline void fooSSE() {
for (__int64 i = 0; i < loops; ++i) {
XMVECTOR zero1 = _mm_setzero_ps();
//XMVECTOR zero2 = _mm_setzero_ps();
//XMVECTOR zero3 = _mm_setzero_ps();
//XMVECTOR zero4 = _mm_setzero_ps();
}
}
inline void fooNoIntrinsic() {
for (__int64 i = 0; i < loops; ++i) {
XMVECTOR zero1 = { 0.f,0.f,0.f,0.f };
//XMVECTOR zero2 = { 0.f,0.f,0.f,0.f };
//XMVECTOR zero3 = { 0.f,0.f,0.f,0.f };
//XMVECTOR zero4 = { 0.f,0.f,0.f,0.f };
}
}
int main() {
fooNoIntrinsic();
fooSSE();
}

我只用零1运行了两次程序,第二次没有注释所有行。在第一种情况下,内在性损失,在第二种情况下是明显的赢家。因此,我的问题是:
  • 为什么内在并不总是赢?
  • 我使用的探查器是否适合进行此类测量?

  • enter image description here

    enter image description here

    最佳答案

    在禁用优化的情况下进行性能分析会给您毫无意义的结果,并且完全是浪费时间。如果您由于其他原因而禁用了优化,则优化器会发现您的基准测试实际上没有任何用处,而是将其完全删除,因此欢迎您进行微基准测试!

    通常,很难构想一个实际完成了足够实际工作的测试用例,以至于没有一个足够聪明的优化器将其删除,但是该工作的成本不会使您不堪重负,也不会使您的结果变得毫无意义。例如,很多人的第一个直觉是使用printf之类的东西打印出增量结果,但这并不是一个开始,因为printf的运行速度非常慢,并且绝对会破坏您的基准测试。使收集中间值的变量成为volatile有时会起作用,因为它有效地禁用了该特定变量的加载/存储优化。尽管这依赖于定义不明确的语义,但这对于基准测试并不重要。另一种选择是对中间结果执行一些毫无意义但相对便宜的操作,例如将它们加在一起。这依赖于优化程序不会使您望而却步,并且为了验证基准测试结果是否有意义,您必须检查编译器发出的目标代码,并确保代码确实在起作用。不幸的是,制作微基准测试没有万灵丹。

    最好的技巧通常是在函数内部隔离代码的相关部分,在一个或多个不可预测的输入值上对其进行参数化,安排要返回的结果,然后将此函数放入外部模块中,以便优化器可以不要让它肮脏的爪子在上面。

    由于无论如何您都需要查看反汇编以确认您的微基准测试案例是否合适,因此这通常是一个很好的起点。如果您有足够的能力来阅读汇编语言,并且已经充分地提炼了所讨论的代码,那么这甚至足以使您判断代码的效率。如果您无法编写代码的开头或结尾,那么它可能很复杂,您可以继续对其进行基准测试。

    这是一个很好的例子,当粗略检查生成的目标代码足以回答该问题而无需制定基准时。

    按照上面的建议,让我们编写一个简单的函数来测试内部函数。在这种情况下,我们没有任何要参数化的输入,因为代码实际上只是将寄存器设置为0。因此,让我们从函数返回归零的结构:

    DirectX::XMVECTOR ZeroTest_Intrinsic()
    {
    return _mm_setzero_ps();
    }

    这是另一个看似幼稚的方式执行初始化的候选人:
    DirectX::XMVECTOR ZeroTest_Naive()
    {
    return { 0.0f, 0.0f, 0.0f, 0.0f };
    }

    这是由编译器为这两个函数生成的目标代码(无论是哪个版本,无论是针对x86-32还是x86-64进行编译,还是针对大小或速度进行优化;结果都是相同的):
    ZeroTest_Intrinsic
    xorps xmm0, xmm0
    ret
    ZeroTest_Naive
    xorps xmm0, xmm0
    ret

    (如果支持AVX或AVX2指令,那么它们都将是 vxorps xmm0, xmm0, xmm0。)

    即使对于无法阅读汇编代码的人来说,这也很明显。他们都是一样的!我要说的是,它绝对可以回答哪个更快的问题:它们将是相同的,因为优化器可以识别看似幼稚的初始化器,并将其转换为单个优化的汇编语言指令,以清除寄存器。

    现在,当然有可能在某些情况下将其深深嵌入各种复杂的代码构造中,从而阻止优化器识别它并发挥其魔力。换句话说,“您的测试功能太简单了!”异议。这很可能就是为什么库的实现者选择在可用时显式使用内在函数的原因。它的使用保证了代码生成将发出所需的指令,因此代码将得到尽可能的优化。

    显式使用内部函数的另一个可能的好处是,即使在没有SSE / SSE2支持的情况下编译代码,也可以确保获得所需的指令。正如我想象的那样,这不是一个特别引人注目的用例,因为如果可以接受使用这些指令,那么如果没有SSE / SSE2支持,您将无法进行编译。而且,如果您明确地尝试禁用SSE / SSE2指令的生成,以便可以在旧系统上运行,则内在函数会破坏您的一天,因为它将强制发出 xorps指令,而旧系统将引发无效操作触及此指令后立即异常(exception)。

    我确实看到了一个有趣的案例。 xorps是此指令的单精度版本,仅需要SSE支持。但是,如果仅使用SSE支持而不使用SSE2来编译上面显示的功能,则会得到以下信息:
    ZeroTest_Intrinsic
    xorps xmm0, xmm0
    ret
    ZeroTest_Naive
    push ebp
    mov ebp, esp
    and esp, -16
    sub esp, 16

    mov DWORD PTR [esp], 0
    mov DWORD PTR [esp+4], 0
    mov DWORD PTR [esp+8], 0
    mov DWORD PTR [esp+12], 0
    movaps xmm0, XMMWORD PTR [esp]

    mov esp, ebp
    pop ebp
    ret

    显然,由于某种原因,即使SSE2指令支持不可用,优化器也无法将优化应用于初始化器的使用,即使将要使用的 xorps指令不需要SSE2指令支持!可以说,这是优化器中的一个错误,但是围绕它的内在工作的明确使用。

    关于c++ - 分析_mm_setzero_ps和{0.0f,0.0f,0.0f,0.0f},我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41268081/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com