用内在函数初始化__m128i常数的最快方法?

问题描述 投票:0回答:1

当前,我有一个__m128i变量,我们称它为X。我想用一个恒定的128位值对其进行异或运算,然后将该值保存回X。因此,对于某些常数X ^= C,基本上是C

当前,我正在按照以下方式进行操作:

X = _mm_xor_si128(X, _mm_set_epi64x(C_a, C_b))

这将从__m128i的两个64位部分中为异或构建一个C

我的问题是,这似乎不是初始化Xor的__m128i常数的最有效方法。尝试从对齐的数组中进行加载会更好吗?还是其他方法?

我目前在Visual Studio中使用MSVC。

c visual-c++ sse intrinsics micro-optimization
1个回答
0
投票

此答案纯粹是关于常数C的情况。如果您有非恒定输入,那么它们的来源(内存,寄存器,您可以首先在矢量寄存器中进行的最新计算?)以及可能要对结果进行的处理就很重要向量。在ALU端口瓶颈与存储/重载的延迟和吞吐量(以及标量->向量的存储转发停顿)之间权衡取舍,将单独的标量变量混入SIMD向量中/从SIMD向量中移出,这有点糟。但是,在asm中存储/重新加载非常有用,可以在您确实想要全部小元素时获得SIMD向量的许多小元素out


对于常数C_aC_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,以便我们可以看到向量已经在寄存器中时的行为,例如此内联时),我们得到了这个相当愚蠢的asm希望内联后在更大的函数中不会有这么糟糕:
;; 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令人惊讶,但是[G0]与GCC和/或叮当声相比。同样,作为大型函数的一部分,当它可以选择寄存器时,我们可能没有多余的MSVC is well known for asm that's often not quite as good。而且,如果xor处于循环中,那么无论如何我们都希望在循环外加载一次。这不是最新的MSVC版本。 Godbolt仅为C ++(不是C)安装了最新的MSVC版本,但您标记了此C。


相比之下,GCC9.2 -O3编译为在所有CPU上均有效的预期内存源PXOR。

movdqa

[您可能会让编译器发出相同的asm,并带有一个包含常量的静态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 数组,并从中获取alignas(16)。但是为什么要打扰呢?

要避免[avoid

的一件事是在编写_mm_load_si128()-编译器对此非常笨拙,不会将static const __m128i C = _mm_set...折叠为_mm_set的静态常量初始化程序。 C编译器将拒绝编译非常量静态初始化器。 C ++编译器将保留一些BSS空间,并运行类似于构造函数的函数,以将只读常量复制到该BSS空间中,因为在这种情况下__m128i不能完全优化。
© www.soinside.com 2019 - 2024. All rights reserved.