C++ 静态/静态本地SSE/AVX变量是否阻塞xmm/ymm寄存器?
使用SSE内部函数时,通常需要零向量。避免在函数被调用时(每次有效地调用某个xor向量指令)在函数内部创建零变量的一种方法是使用静态局部变量,如C++ 静态/静态本地SSE/AVX变量是否阻塞xmm/ymm寄存器?,c++,sse,avx,C++,Sse,Avx,使用SSE内部函数时,通常需要零向量。避免在函数被调用时(每次有效地调用某个xor向量指令)在函数内部创建零变量的一种方法是使用静态局部变量,如 static inline __m128i negate(__m128i a) { static __m128i zero = __mm_setzero_si128(); return _mm_sub_epi16(zero, a); } 似乎只有在第一次调用函数时才初始化变量。(我通过调用一个true函数而不是yMMJStEnZoosis
static inline __m128i negate(__m128i a)
{
static __m128i zero = __mm_setzero_si128();
return _mm_sub_epi16(zero, a);
}
似乎只有在第一次调用函数时才初始化变量。(我通过调用一个true函数而不是yMMJStEnZoosisi128)来检查它,它似乎只可能在C++中,而不是在C中。
(1) 然而,一旦初始化发生:这个块是否为程序的其余部分阻塞了xmm寄存器
(2) 更糟糕的是:如果在多个函数中使用这样一个静态局部变量,它会阻止多个xmm寄存器吗
(3) 反过来说:如果它没有阻塞xmm寄存器,当调用函数时,zero变量是否总是从内存中重新加载?那么静态局部变量就没有意义了,因为使用_mm_setzero_si128()会更快
作为替代方案,我考虑将零放入全局静态变量中,该变量将在程序启动时初始化:
static __m128i zero = _mm_setzero_si128();
(4) 当程序运行时,全局变量是否会保留在xmm寄存器中
非常感谢你的帮助
(由于这也适用于AVX intrinsic,因此我还添加了AVX标记。)由于使用向量提高效率,因此代码存在问题 未使用常量初始化的静态变量将在运行时初始化。以线程安全的方式。第一次调用内联函数时,静态变量将被初始化。之后每次调用时,都会检查静态变量是否需要初始化 所以在每次通话中,都会有一个检查,然后是内存加载。如果不使用静态变量,则可能只有一条指令创建值,还有大量优化机会。从内存加载很慢
您可以拥有任意多的静态变量。编译器将处理您向它抛出的任何内容 回答这里真正应该问的问题:你根本不应该担心这个问题。通过
xor
对寄存器进行调零在大多数情况下都不会产生任何成本。现代x86处理器认识到这一习惯用法,并直接在寄存器重命名中处理零;根本不需要发布任何µop。唯一能让你慢下来的是如果你被前端束缚住了,但这是一种非常罕见的情况
虽然在其他情况下,这些问题的变化可能值得思考(Mystical的评论提供了一些关于如何自己回答这些问题的好线索),但你真的应该使用
setzero
,到此为止。关于你应该在Stephen Canon执行的特定操作
static inline Vec8s operator - (Vec8s const & a) {
return _mm_sub_epi16(_mm_setzero_si128(), a);
}
那是直接从我的照片上取的
<>但是让我们考虑一下<代码>静态< /COD>关键字。当您使用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));
}
foo函数
movdqa xmm1, XMMWORD PTR _ZL4zero[rip]
psubw xmm1, xmm0
movdqa xmm0, xmm1
因此GCC从不为静态变量使用XMM寄存器。它从数据部分的内存中读取数据
如果我们做了\u mm\u sub\u epi16(\u mm\u setzero\u si128(),a)
,会怎么样?然后GCC为foo
pxor xmm1, xmm1
psubw xmm1, xmm0
movdqa xmm0, xmm1
movdqa xmm1, XMMWORD PTR .LC0[rip]
psubw xmm1, xmm0
movdqa xmm0, xmm1
在英特尔处理器上,自Sandy Bridge以来,pxor
是“免费”的。在之前的处理器上,它几乎是免费的。因此,这显然是一个比从内存中读取更好的解决方案
如果我们尝试\u mm\u sub\u epi16(\u mm\u set1\u epi32(-1),a)
,会怎么样。在这种情况下,GCC产生
pcmpeqd xmm1, xmm1
psubw xmm1, xmm0
movdqa xmm0, xmm1
在任何处理器上,pcmpeqd
指令都不是免费的,但它仍然比使用movdqa
从内存中读取要好。好的,所以0
和-1
是特殊的。那么\u mm\u sub\u epi16(\u mm\u set1\u epi32(1)
)呢?在这种情况下,GCC为foo
pxor xmm1, xmm1
psubw xmm1, xmm0
movdqa xmm0, xmm1
movdqa xmm1, XMMWORD PTR .LC0[rip]
psubw xmm1, xmm0
movdqa xmm0, xmm1
这与使用静态变量基本相同!当我查看这些节时,我看到.LC0指向一个只读数据节(.rodata)
编辑:下面是一种使用GCC的方法
寄存器uum128i零asm(“xmm15”)=mm_set1_epi32(1)
这就产生了
movdqa xmm2, xmm15
psubw xmm2, xmm0
movdqa xmm0, xmm2
我想我可以在讨论中添加一个有趣的观点,特别是我对_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使用整数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)));
因此,在大多数情况下,可能根本不必担心在某个函数中重复将某个xmm寄存器设置为常量。1和2,创建16个包含16个静态的函数,然后查看是否编译并运行。4、创建16个全局变量,看看它是否编译并运行。3受到编译器优化的影响。(顺便说一句,您正在进行微优化,因为在当前处理器上,寄存器归零基本上是免费的)如果您正在广播一个非零值,例如
\u mm\u set1\u epi32(-1)
,您的问题会更有趣。事实证明-1
也很特别。所以除了0
和-1
之外,广播任何东西都会很有趣。e、 g.\u mm\u set1\u epi32(1)
。感谢@Zboson提供两个有用的链接。关于你最后的评论:为什么-1也很特别?此外,我还查看了广播操作(至少对于-mssse3而言),它们似乎非常昂贵(编译为两条指令,其中一条指令是矢量混洗指令)。我不知道(或者我忘了)这是通过寄存器重命名免费处理的。那真的很酷。我读了一点,很明显SB有一个物理零寄存器,它只是将寄存器重命名为零寄存器,这就是为什么它不需要一个µop。我想这只适用于SB,因此在Nehalem上调零至少需要1µop?是的,调零仅在SNB上“免费”。梅罗姆·韦斯特默