是静态/静态本地SSE / AVX变量阻塞xmm / ymm寄存器?

时间:2014-11-07 18:02:04

标签: c++ sse avx

使用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标记。)

4 个答案:

答案 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寄存器为常量。