使用SSE内在函数时,通常需要零向量。无论何时调用函数(每次有效地调用某些xor向量指令),避免在函数内部创建零变量的一种方法是使用静态局部变量,如
static inline __m128i negate(__m128i a)
{
static __m128i zero = __mm_setzero_si128();
return _mm_sub_epi16(zero, a);
}
似乎只在第一次调用函数时初始化变量。 (我通过调用一个真正的函数而不是_mm_setzero_si128()内在函数来检查这个。顺便说一句,它似乎只能在C ++中,而不是在C中。)
(1)然而,一旦发生这种初始化:这是否阻止了xmm寄存器用于程序的其余部分?
(2)更糟糕的是:如果在多个函数中使用这样的静态局部变量,它会阻塞多个xmm寄存器吗?
(3)反过来说:如果不阻塞xmm寄存器,那么当调用该函数时,是否总是会从内存重新加载零变量?那么静态局部变量将毫无意义,因为使用_mm_setzero_si128()会更快。
作为替代方案,我考虑将零置于一个全局静态变量中,该变量将在程序启动时初始化:
static __m128i zero = _mm_setzero_si128();
(4)程序运行时全局变量是否会保留在xmm寄存器中?
非常感谢你的帮助!
(因为这也适用于AVX内在函数,我还添加了AVX标记。)
答案 0 :(得分:10)
回答这里应该提出的问题:你不应该担心这个 。通过xor
归零寄存器实际上在大多数情况下都没有任何成本。现代x86处理器识别这个习惯用法并直接在寄存器重命名中处理归零;没有μop需要发布。唯一一次这会减慢你的速度是因为你受到了前端的约束,但这种情况很少见。
虽然这些问题的变化可能值得在其他情况下进行思考(并且Mystical的评论给出了一些如何自己回答它们的良好线索),但您应该只使用setzero
并将其称为一天。
答案 1 :(得分:5)
关于这个特殊的操作你应该在Stephen Canon说并且做
static inline Vec8s operator - (Vec8s const & a) {
return _mm_sub_epi16(_mm_setzero_si128(), a);
}
直接来自Agner Fog's 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));
}
我做g++ -O3 -c static.cpp
然后查看反汇编和部分。我知道了
有一个带有标签_ZL4zero
的.bss部分。然后有一个代码启动部分,它将静态变量写入.bss部分。
.text.startup
pxor xmm0, xmm0
movaps XMMWORD PTR _ZL4zero[rip], xmm0
ret
foo功能
movdqa xmm1, XMMWORD PTR _ZL4zero[rip]
psubw xmm1, xmm0
movdqa xmm0, xmm1
因此GCC从不使用XMM寄存器作为静态变量。它从数据部分的内存中读取。
如果我们_mm_sub_epi16(_mm_setzero_si128(),a)
怎么办?然后GCC为foo
生成
pxor xmm1, xmm1
psubw xmm1, xmm0
movdqa xmm0, xmm1
自Sandy Bridge以来,在英特尔处理器上pxor
是免费的#34;在之前的处理器上,它几乎是免费的。所以这显然是比从内存中读取更好的解决方案。
如果我们尝试_mm_sub_epi16(_mm_set1_epi32(-1),a)
怎么办?在那种情况下,GCC产生
pcmpeqd xmm1, xmm1
psubw xmm1, xmm0
movdqa xmm0, xmm1
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
这与使用静态变量基本相同!当我看到这些部分时,我看到.LC0指向一个只读数据部分(.rodata)。
编辑:以下是让GCC使用的方法global variable in register。
注册__m128i零asm(&#34; xmm15&#34;)= _mm_set1_epi32(1);
这会产生
movdqa xmm2, xmm15
psubw xmm2, xmm0
movdqa xmm0, xmm2
答案 2 :(得分:2)
由于您使用向量来提高效率,因此您的代码存在问题。
未使用常量初始化的静态变量将在运行时初始化。以线程安全的方式。第一次调用内联函数时,会初始化静态变量。在此之后的每次调用中,检查静态变量是否需要初始化。
所以在每次通话时都有一个检查,然后是内存负载。如果你不使用静态变量,那么可能只有一条指令可以创建该值,还有很多优化机会。从内存加载速度很慢。
您可以拥有任意数量的静态变量。编译器将处理您抛出的任何内容。
答案 3 :(得分:2)
我想我可以在讨论中添加一个有趣的观点,尤其是我对_mm_abs_ps()的评论。如果我定义
static inline __m128 _mm_abs_ps_2(__m128 x) {
__m128 signMask = _mm_set1_ps(-0.0F);
return _mm_andnot_ps(signMask, x);
}
(Agner Fog的VCL http://www.agner.org/optimize/#vectorclass使用整数set1,强制转换和AND操作,但实际上应该是相同的)并在循环中使用该函数
float *p = data;
for (int i = 0; i < LEN; i += 4, p += 4)
_mm_store_ps(p, _mm_abs_ps_2(_mm_load_ps(p)));
然后gcc(4.6.3,-O3)足够聪明,可以避免重复执行_mm_set1_ps 将其移出循环:
vmovaps xmm1, XMMWORD PTR .LC1[rip] # tmp108,
mov rax, rsp # p,
.L3:
vandnps xmm0, xmm1, XMMWORD PTR [rax] # tmp102, tmp108, MEM[base: p_54, offset: 0B]
vmovaps XMMWORD PTR [rax], xmm0 # MEM[base: p_54, offset: 0B], tmp102
add rax, 16 # p,
cmp rax, rbp # p, D.7371
jne .L3 #,
.LC1:
.long 2147483648
.long 2147483648
.long 2147483648
.long 2147483648
因此,在大多数情况下,人们不应该担心在某个函数内重复设置一些xmm寄存器为常量。