c++ - AVX指令中寄存器和指针的客观区别

标签 c++ performance avx opcode

场景:您正在使用 SIMD 编写一个复杂的算法。使用了一些常量和/或不经常变化的值。最终,该算法最终使用超过 16 个 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 个额外的延迟”这样的概念?或者它是不确定的,因此它因具体情况而异,只能通过反复试验 + 分析才能完全优化?

最佳答案

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

帮助编译器的最好办法是尽可能少地使用不同的常量。例如而不是同时使用 set1_epi16(0x00FF)0xFF00_mm_and_si128,使用 _mm_andn_si128 来掩盖另一种方式。您通常不能做任何事情来影响它选择将哪些内容保存在寄存器中,而哪些内容不保存在寄存器中,但幸运的是,编译器非常擅长这一点,因为它对于标量代码也是必不可少的。


编译器会将常量提升到循环之外(甚至内联包含常量的辅助函数),或者如果仅在分支的一侧使用,则将设置带到分支的那一侧。

源代码计算完全相同的东西,在可见的副作用上没有区别,因此 as-if 规则允许编译器自由地执行此操作。


我认为编译器通常会在执行 CSE(公共(public)子表达式消除)并识别可以提升的循环不变量和常量后进行寄存器分配并选择要溢出/重新加载的内容(或只使用只读 vector 常量)。

当它发现它没有足够的寄存器来将所有变量和常量保存在循环内的 regs 中时,首选保存在寄存器中的东西通常是循环不变的vector,编译时常量或在循环之前计算的东西。

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

用 C++ 编写的部分要点是您有一个编译器可以为您做出这个决定。由于允许对两个源执行相同的操作,因此对至少一种情况而言,执行不同的操作将是优化失误。 (在任何特定情况下最好的做法取决于周围的代码,但当编译器在 regs 上运行不足时,通常使用 vector 常量作为内存源操作数是可以的。)

Is it a matter of some concept like "ptr adds 2 extra latency"?

内存源操作数的微融合不会延长从非常量输入到输出的关键路径。加载 uop 可以在地址准备好后立即开始,对于 vector 常量,它通常是 RIP 相关或 [rsp+constant] 寻址模式。所以通常负载一旦被发送到核心的无序部分就可以立即执行。假设 L1d 缓存命中(因为如果每次循环迭代都加载它会在缓存中保持热),这只有大约 5 个周期,因此如果 vector 寄存器输入上存在依赖链瓶颈,它很容易及时准备好。

它甚至不会影响前端吞吐量。除非您在加载端口吞吐量方面遇到瓶颈(在现代 x86 CPU 上每个时钟 2 个加载),否则它通常没有什么区别。 (即使使用高度精确的测量技术。)

关于c++ - AVX指令中寄存器和指针的客观区别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52711237/

相关文章:

C++ 段错误

c++ - 在 ";"错误之前继续丢失 "."

database - 速度、CouchDB View 和替代方案

wpf - "Measure"的性能问题

c++ - 将 vector 以不匹配的大小加载到AVX2寄存器中

assembly - avx指令中的源寄存器何时可以重用

c++ - sort_heap 存在的原因

c++ - MySQL,C++ : Retrieving auto-increment ID

c - fprintf() 与 fwrite() 的速度

x86 - 为什么是两个按位或 AVX 指令?