这个答案纯粹是关于常量C 的情况。如果您有非常量的输入,那么它们来自哪里(内存、寄存器、最近的计算,您可能首先可以在向量寄存器中进行?)以及您对结果可能做的事情很重要向量。将单独的标量变量混洗进/出 SIMD 向量有点糟糕,在 ALU 端口瓶颈与延迟和存储/重新加载的吞吐量(以及标量 -> 向量的存储转发停止)之间进行权衡。但是,当您确实需要它们时,存储/重新加载在 asm 中非常适合从 SIMD 向量中out获取大量小元素。
对于常量C_a 和C_b,即使是MSVC 在通过_mm_set 进行常量传播方面也做得很好。所以编写像SSE Error - Using m128i_i32 to define fields of a __m128i variable这样的特定于实现的初始化程序没有任何优势
请记住,性能的真正决定因素是您可以诱使编译器生成的程序集,而不是您使用哪些内在函数来执行此操作。
#include <immintrin.h>
__m128i xor_const(__m128i v) {
return _mm_xor_si128(v, _mm_set_epi64x(0x789abc, 0x123456));
}
使用 x64 MSVC -O2 Gv 编译 (on Godbolt)(使用 vectorcall 以便我们可以看到当向量已经在寄存器中时它会做什么,例如内联时),我们得到这个相当愚蠢的汇编,希望在内联后在更大的函数中不会那么糟糕:
;; MSVC 19.10
;; this is in the .rdata section; godbolt just filters directives that aren't interesting
;; "everyone knows" that compilers put data in the right sections
__xmm@0000000000789abc0000000000123456 DB 'V4', 012H, 00H, 00H, 00H, 00H, 00H
DB 0bcH, 09aH, 'x', 00H, 00H, 00H, 00H, 00H
xor_const@@16 PROC ; COMDAT
movdqa xmm1, XMMWORD PTR __xmm@0000000000789abc0000000000123456
pxor xmm1, xmm0
movdqa xmm0, xmm1
ret 0
xor_const@@16 ENDP
我们可以看到 _mm_set 内部函数在静态存储中编译为一个 16 字节的常量,就像我们想要的那样。未能使用 pxor xmm0, xmm1 令人惊讶,但与 GCC 和/或 clang 相比,MSVC is well known for asm that's often not quite as good。同样,作为一个大函数的一部分,当它可以选择寄存器时,我们可能没有额外的movdqa。如果 xor 在循环中,那么在循环外加载一次就是我们想要的。这不是最新的 MSVC 版本。 Godbolt 只为 C++ 安装了最新的 MSVC 版本,而不是 C,但你标记了这个 C。
相比之下,GCC9.2 -O3 编译为在所有 CPU 上都高效的预期内存源 PXOR。
xor_const:
pxor xmm0, XMMWORD PTR .LC0[rip]
ret
.section .rodata # Godbolt strips out stuff like section directive; re-added manually
.LC0:
.quad 1193046
.quad 7903932
您可能会让编译器发出相同的 asm,其中包含一个静态的 alignas(16) 数组,其中包含常量,_mm_load_si128() 来自该数组。但何必呢?
要避免的一件事是写static const __m128i C = _mm_set... - 编译器对此非常愚蠢,不会将_mm_set 折叠成@987654338 的静态常量初始化器@。 C 编译器将拒绝编译非常量静态初始化程序。 C++ 编译器将保留一些 BSS 空间并运行一个类似构造函数的函数以从只读常量复制到该 BSS 空间,因为在这种情况下 _mm_set 不会完全优化。