代码之家  ›  专栏  ›  技术社区  ›  M-Pixel Pierre Fourgeaud

AVX指令中寄存器和指针的客观差异

  •  0
  • M-Pixel Pierre Fourgeaud  · 技术社区  · 7 年前

    ymm ,导致使用堆栈指针(例如,操作码包含 vaddps ymm0,ymm1,ymmword ptr [...] vaddps ymm0,ymm1,ymm7

    const auto pi256{ _mm256_set1_ps(PI) };
    for (outer condition)
    {
        ...
        const auto radius_squared{ _mm256_mul_ps(radius, radius) };
        ...
        for (inner condition)
        {
            ...
            const auto area{ _mm256_mul_ps(radius_squared, pi256) };
            ...
        }
    }
    

    for (outer condition)
    {
        ...
        for (inner condition)
        {
            ...
            const auto area{ _mm256_mul_ps(_mm256_mul_ps(radius, radius), _mm256_set1_ps(PI)) };
            ...
        }
    }
    

    不管讨论中的可支配变量是一个常量,还是很少计算(计算的外循环),如何确定哪种方法可以获得最佳吞吐量?这是一个类似“ptr增加2个额外延迟”的概念问题吗?或者它是不确定性的,以至于它在个案的基础上有所不同,并且只能通过试错+分析来完全优化?

    1 回复  |  直到 7 年前
        1
  •  3
  •   Peter Cordes    7 年前

    一个好的优化编译器应该为两个版本生成相同的机器代码。只需将向量常量定义为局部变量,或者匿名使用它们以获得最大的可读性;让编译器担心寄存器分配问题,如果发生这种情况,选择最便宜的方法来处理寄存器耗尽的问题。

    帮助编译器的最佳方法是尽可能少地使用不同的常量。e、 g.代替 _mm_and_si128 set1_epi16(0x00FF) 0xFF00 _mm_andn_si128 以另一种方式掩饰。您通常不能做任何事情来影响它选择在寄存器中保留哪些内容,但幸运的是编译器在这方面做得相当好,因为它对于标量代码也是必不可少的。


    编译器将把常量从循环中提升出来(甚至内联包含常量的助手函数),或者如果只在分支的一侧使用,则将设置带到分支的那一侧。

    源代码计算的是完全相同的东西,没有明显的副作用差异,因此“好像”规则允许编译器自由地执行此操作。


    在L1d缓存中命中的额外负载比在循环中存储(又称溢出)/重新加载变量便宜。因此,编译器将选择从内存加载常量,而不管您将定义放在源代码中的什么位置。

    在C++中写作的一部分是你有一个编译器来为你做这个决定。因为允许对两个源执行相同的操作,所以对至少一种情况来说,执行不同的操作可能会错过优化(在任何特定情况下,最好的做法取决于周围的代码,但通常情况下,当编译器运行的regs不足时,使用向量常量作为内存源操作数就可以了。)

    这是一个类似“ptr增加2个额外延迟”的概念问题吗?

    [rsp+constant] 寻址模式。所以通常情况下,一旦加载到核心的无序部分,它就可以立即执行。假设L1d缓存命中(因为如果每次循环迭代都加载,它将在缓存中保持热状态),这只有~5个周期,因此如果向量寄存器输入上存在依赖链瓶颈,它将很容易及时准备就绪。

    推荐文章