使用SSE内在函数时,通常需要零向量.无论何时调用函数(每次有效调用某些xor向量指令),避免在函数内部创建零变量的一种方法是使用静态局部变量,如
static inline __m128i negate(__m128i a)
{
static __m128i zero = __mm_setzero_si128();
return _mm_sub_epi16(zero, a);
}
Run Code Online (Sandbox Code Playgroud)
似乎只在第一次调用函数时才初始化变量.(我通过调用一个真正的函数而不是_mm_setzero_si128()内在函数来检查这个.顺便说一句,它似乎只能在C++中,而不是在C中.)
(1)但是,一旦发生这种初始化:这是否阻止了xmm寄存器用于程序的其余部分?
(2)更糟糕的是:如果在多个函数中使用这样的静态局部变量,它会阻塞多个xmm寄存器吗?
(3)反过来说:如果它没有阻塞xmm寄存器,那么在调用函数时,零变量是否总是从内存中重新加载?那么静态局部变量将毫无意义,因为使用_mm_setzero_si128()会更快.
作为替代方案,我考虑将零置于一个全局静态变量中,该变量将在程序启动时初始化:
static __m128i zero = _mm_setzero_si128();
Run Code Online (Sandbox Code Playgroud)
(4)程序运行时,全局变量是否会保留在xmm寄存器中?
非常感谢你的帮助!
(由于这也适用于AVX内在函数,我还添加了AVX标记.)
Ste*_*non 10
回答这个真正应该问这里的问题是:你不应该担心这个在所有.xor在大多数情况下,通过有效地对寄存器进行归零无需任何费用.现代x86处理器识别这个习惯用法并直接在寄存器重命名中处理归零; 没有μop需要发布.只有你受到前端的约束,这才能减慢你的速度,但这是一个相当罕见的情况.
虽然这些问题的变化可能值得在其他情况下进行思考(而Mystical的评论给出了一些如何自己回答它们的良好线索),你应该真正使用setzero并称之为一天.
关于这个特殊的操作你应该在Stephen Canon说和做
static inline Vec8s operator - (Vec8s const & a) {
return _mm_sub_epi16(_mm_setzero_si128(), a);
}
Run Code Online (Sandbox Code Playgroud)
这是直接从Agner Fog的Vector Class Library中获取的.
但是让我们考虑static关键字的作用.static使用静态存储声明变量时.这将它放在目标文件的数据部分(包括.bss部分)中.
#include <x86intrin.h>
extern "C" void foo2(__m128i a);
static const __m128i zero = _mm_setzero_si128();
static inline __m128i negate(__m128i a) {
return _mm_sub_epi16(zero, a);
}
extern "C" void foo(__m128i a, __m128i b) {
foo2(negate(a));
}
Run Code Online (Sandbox Code Playgroud)
我这样做g++ -O3 -c static.cpp,然后看看diassembly和部分.我看到有一个带标签的.bss部分_ZL4zero.然后有一个代码启动部分,它将静态变量写入.bss部分.
.text.startup
pxor xmm0, xmm0
movaps XMMWORD PTR _ZL4zero[rip], xmm0
ret
Run Code Online (Sandbox Code Playgroud)
foo功能
movdqa xmm1, XMMWORD PTR _ZL4zero[rip]
psubw xmm1, xmm0
movdqa xmm0, xmm1
Run Code Online (Sandbox Code Playgroud)
所以GCC从不使用XMM寄存器作为静态变量.它从数据部分的内存中读取.
如果我们做了_mm_sub_epi16(_mm_setzero_si128(),a)什么?然后GCC生产foo
pxor xmm1, xmm1
psubw xmm1, xmm0
movdqa xmm0, xmm1
Run Code Online (Sandbox Code Playgroud)
在Sandy Bridge之后的英特尔处理器上pxor是"免费的".在之前的处理器上它几乎是免费的.所以这显然是比从内存中读取更好的解决方案.
如果我们尝试_mm_sub_epi16(_mm_set1_epi32(-1),a).在那种情况下,GCC生产
pcmpeqd xmm1, xmm1
psubw xmm1, xmm0
movdqa xmm0, xmm1
Run Code Online (Sandbox Code Playgroud)
该pcmpeqd指令不在free任何处理器上,但它仍然比使用内存读取更好movdqa.好的,所以0也-1很特别.怎么样_mm_sub_epi16(_mm_set1_epi32(1))?在这种情况下,GCC生产foo
movdqa xmm1, XMMWORD PTR .LC0[rip]
psubw xmm1, xmm0
movdqa xmm0, xmm1
Run Code Online (Sandbox Code Playgroud)
这与使用静态变量基本相同!当我看到这些部分时,我看到.LC0指向一个只读数据部分(.rodata).
编辑:这里有一种让GCC使用的方法是在寄存器中使用全局变量.
register __m128i zero asm("xmm15")= _ mm_set1_epi32(1);
这产生了
movdqa xmm2, xmm15
psubw xmm2, xmm0
movdqa xmm0, xmm2
Run Code Online (Sandbox Code Playgroud)