C语言 fC -我如何在函数之外定义SIMD变量?


const __m128i ___n = _mm_set_epi32( 0x80808080, 0x80808080, 0x80808080, 0x80808080 );
const __m128i w___ = _mm_set_epi32( 0x80808080, 0x80808080, 0x80808080, 0x0f0e0d0c );
const __m128i z___ = _mm_set_epi32( 0x80808080, 0x80808080, 0x80808080, 0x0b0a0908 );
const __m128i zw__ = _mm_set_epi32( 0x80808080, 0x80808080, 0x0f0e0d0c, 0x0b0a0908 );
const __m128i y___ = _mm_set_epi32( 0x80808080, 0x80808080, 0x80808080, 0x07060504 );
const __m128i yw__ = _mm_set_epi32( 0x80808080, 0x80808080, 0x0f0e0d0c, 0x07060504 );
const __m128i yz__ = _mm_set_epi32( 0x80808080, 0x80808080, 0x0b0a0908, 0x07060504 );
const __m128i yzw_ = _mm_set_epi32( 0x80808080, 0x0f0e0d0c, 0x0b0a0908, 0x07060504 );
const __m128i x___ = _mm_set_epi32( 0x80808080, 0x80808080, 0x80808080, 0x03020100 );
const __m128i xw__ = _mm_set_epi32( 0x80808080, 0x80808080, 0x0f0e0d0c, 0x03020100 );
const __m128i xz__ = _mm_set_epi32( 0x80808080, 0x80808080, 0x0b0a0908, 0x03020100 );
const __m128i xzw_ = _mm_set_epi32( 0x80808080, 0x0f0e0d0c, 0x0b0a0908, 0x03020100 );
const __m128i xy__ = _mm_set_epi32( 0x80808080, 0x80808080, 0x07060504, 0x03020100 );
const __m128i xyw_ = _mm_set_epi32( 0x80808080, 0x0f0e0d0c, 0x07060504, 0x03020100 );
const __m128i xyz_ = _mm_set_epi32( 0x80808080, 0x0b0a0908, 0x07060504, 0x03020100 );
const __m128i xyzw = _mm_set_epi32( 0x0f0e0d0c, 0x0b0a0908, 0x07060504, 0x03020100 );
const __m128i LUT[16] = { ___n, x___, y___, xy__, z___, xz__, yz__, xyz_, w___, xw__, yw__, xyw_, zw__, xzw_, yzw_, xyzw };

对于SSE/SSSE3版本的比较和左打包例程,我使用了类似于上面的查找表。一组比较每秒发生多次(60)次,我希望它在内存中而不是每次设置,并将范围限制在一个。c文件,但试图将其定义为具有或不具有静态的函数产生错误:初始化器元素不是常量。对于每个"集合"。为什么会发生这种情况,我怎样才能正确地做到这一点?

编译器报告初始化项元素不是常量,因为_mm_set_epi32是一个函数调用,不满足初始化项的"常量"要求。此外,您定义的各种变量___n和诸如此类的变量不能作为初始化LUT的常量。

你可以定义你的数组:

const __v4su LUT[16] =
{
{ 0x80808080, 0x80808080, 0x80808080, 0x80808080 },
{ 0x03020100, 0x80808080, 0x80808080, 0x80808080 },
{ 0x07060504, 0x80808080, 0x80808080, 0x80808080 },
{ 0x03020100, 0x07060504, 0x80808080, 0x80808080 },
{ 0x0b0a0908, 0x80808080, 0x80808080, 0x80808080 },
{ 0x03020100, 0x0b0a0908, 0x80808080, 0x80808080 },
{ 0x07060504, 0x0b0a0908, 0x80808080, 0x80808080 },
{ 0x03020100, 0x07060504, 0x0b0a0908, 0x80808080 },
{ 0x0f0e0d0c, 0x80808080, 0x80808080, 0x80808080 },
{ 0x03020100, 0x0f0e0d0c, 0x80808080, 0x80808080 },
{ 0x07060504, 0x0f0e0d0c, 0x80808080, 0x80808080 },
{ 0x03020100, 0x07060504, 0x0f0e0d0c, 0x80808080 },
{ 0x0b0a0908, 0x0f0e0d0c, 0x80808080, 0x80808080 },
{ 0x03020100, 0x0b0a0908, 0x0f0e0d0c, 0x80808080 },
{ 0x07060504, 0x0b0a0908, 0x0f0e0d0c, 0x80808080 },
{ 0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c },
};

由于类型从__m128i更改为__v4su,您可能需要强制类型转换来处理它。(vector操作就是为此而设计的)

但是,在任何函数之外和/或具有静态存储持续时间的情况下定义它并不能确保它将在内存中。

_mm_set_epi32与编译时间常数args被优化为向量常数,通常从内存加载。您不需要帮助编译器处理这个问题,事实上,如果您尝试这样做,情况会更糟,因为编译器奇怪地不擅长此道。如果你确实需要/想要帮助编译器使用常量布局,使用alignas(16) static const int32_t LUT[] = {...};_mm_load_si128( (__m128i*)&LUT[i*4] )之类的类型的数组。

像全0或全1位这样的简单向量甚至可以在pxor xmm0,xmm0pcmpeqd xmm1, xmm1寄存器中实现,甚至比加载更有效,因此确保编译器在优化函数时可以看到常量值是在函数内部使用_mm_set*定义常量的一个很好的理由。

想象_mm_set_epi32就像一个字符串字面量:编译器会找出把它放在哪里,如果多个函数需要相同的向量常量,它甚至会进行重复合并。(与字符串字面值不同,它是一个值,而不是衰减为指向存储的指针的东西,因此只有部分类比有效。)


唯一适合_mm_set*的地方是函数中;当前的编译器在处理__m128i类型的全局/static变量方面很糟糕,无法将其作为静态初始化项进行处理,因此它们实际上在.rodata节中放置了一个匿名向量常量,并在运行时构造函数/初始化函数中从该常量复制到.bss的静态存储中指定变量的空间。

(在C中,这只能发生在函数内部的static __m128i,这使得它需要一个保护变量。在c++中,允许使用非常量全局初始化式,比如全局作用域的int foo = bar(123);,即使bar不是constexpr。在C语言中,你会得到你遇到的错误。)

例如:

#include <immintrin.h>
__m128i foo() {
return _mm_setr_epi32(1,2,3,4);
}

使用GCC11.2 -O3(在Godbolt上)编译到此asm。(clang和ICC,我认为MSVC,对于下面所有的代码块都是相似的。)

# in .text
foo():
movdqa  xmm0, XMMWORD PTR .LC0[rip]
ret
# in .rodata
.LC0:
.quad   8589934593     # 0x200000001
.quad   17179869187    # 0x400000003
__m128i bar(__m128i v) {
return _mm_add_epi32(v, _mm_setr_epi32(1,2,3,4));
}
bar(long long __vector(2)):
paddd   xmm0, XMMWORD PTR .LC1[rip]
ret
.set    .LC1,.LC0     # make LC1 a synonym for LC0
# GCC noticed and merged at compile time, not leaving it for the linker.
__m128i retzero() {
//return _mm_setzero_si128();
return _mm_setr_epi32(0,0,0,0);  // optimizes the same
}
pxor    xmm0, xmm0
ret

但这是你从c++编译器得到的对于全局向量:

__m128i globvec = _mm_setr_epi32(1,2,3,4);
_GLOBAL__sub_I_foo():                          # static initializer code
movdqa  xmm0, XMMWORD PTR .LC0[rip]        # copy from anonymous .rodata
movaps  XMMWORD PTR globvec[rip], xmm0     # to named .bss space
ret
# in .bss
globvec:
.zero   16

这就是C的错误信息"saving"你从;C不允许非常量静态初始化(函数除外)。

对于函数内部的static __m128i,情况会更糟:你需要一个保护变量来确保非常量初始化器只在第一次调用时运行。


实际上使用globvec的代码基本上是好的,它将使用它作为内存源操作数或正常加载它,但是任何优化,例如基于一些元素,通过某些操作具有已知值的常量传播将是不可能的。你还使用了两倍的空间,尽管初始化器数据只在启动时被触摸一次,所以对缓存占用没有影响。

或者:

constexpr _m128 _val = {0.5f,0.5f,0.5f,0.5f};

最新更新