在`C`函数中定义`static const` SIMD变量
我有这种forms的函数(使用SSE从指数函数的最快实现 ):
__m128 FastExpSse(__m128 x) { static __m128 const a = _mm_set1_ps(12102203.2f); // (1 << 23) / ln(2) static __m128i const b = _mm_set1_epi32(127 * (1 << 23) - 486411); static __m128 const m87 = _mm_set1_ps(-87); // fast exponential function, x should be in [-87, 87] __m128 mask = _mm_cmpge_ps(x, m87); __m128i tmp = _mm_add_epi32(_mm_cvtps_epi32(_mm_mul_ps(a, x)), b); return _mm_and_ps(_mm_castsi128_ps(tmp), mask); }
我想让它兼容C
然而编译器不接受static __m128i const b = _mm_set1_epi32(127 * (1 << 23) - 486411);
当我使用C
编译器。
但我不希望在每个函数调用中重新计算前3个值。
一种解决方案是内联它(但有时编译器会拒绝它)。
如果函数没有内联,是否有C
风格来实现它?
谢谢。
删除static
和const
。
还要从C ++版本中删除它们。 const
是好的,但static
是可怕的,引入每次检查的保护变量,第一次非常昂贵的初始化。
__m128 a = _mm_set1_ps(12102203.2f);
它不是函数调用,它只是表达向量常量的一种方式。 没有时间可以通过“只做一次”来保存 – 它通常发生零次,在程序的数据段中准备常量向量并且只是在运行时加载,而没有static
引入的垃圾。
检查asm是否确定,没有static
这就是发生的事情:( 来自godbolt )
FastExpSse(float __vector(4)): movaps xmm1, XMMWORD PTR .LC0[rip] cmpleps xmm1, xmm0 mulps xmm0, XMMWORD PTR .LC1[rip] cvtps2dq xmm0, xmm0 paddd xmm0, XMMWORD PTR .LC2[rip] andps xmm0, xmm1 ret .LC0: .long 3266183168 .long 3266183168 .long 3266183168 .long 3266183168 .LC1: .long 1262004795 .long 1262004795 .long 1262004795 .long 1262004795 .LC2: .long 1064866805 .long 1064866805 .long 1064866805 .long 1064866805
_mm_set1_ps(-87);
或任何其他_mm_set
内在函数不是当前编译器的有效静态初始化程序,因为它不被视为常量表达式 。
在C ++中,它编译为static
存储位置的运行时初始化(从其他地方的矢量文字复制)。 如果它是函数内部的static __m128
,则有一个保护变量来保护它。
在C中,它只是拒绝编译,因为C不支持非常量初始化器/构造器。 _mm_set
不像底层GNU C本机向量的支撑初始化器,就像@ benjarobin的答案所示。
这真是愚蠢,似乎是所有4个主流x86 C ++编译器(gcc / clang / ICC / MSVC)中的错过优化。 即使在某种程度上重要的是每个static const __m128 var
都有一个不同的地址,编译器也可以通过使用初始化的只读存储而不是在运行时复制来实现。
因此,即使启用了优化,似乎常量传播也无法将_mm_set
转换为常量初始化器。
永远不要使用static const __m128 var = _mm_set...
即使在C ++中也是如此; 它效率低下。
function内部更糟糕,但全球范围仍然不好。
相反,避免static
。 您仍然可以使用const
来阻止自己意外地分配其他内容,并告诉人类读者这是一个常数。 如果没有static
,它对变量存储的位置和方式没有影响。 const
on automatic storage只是编译时检查你不修改对象。
const __m128 var = _mm_set1_ps(-87); // not static
编译器擅长于此, 并将优化多个函数使用相同向量常量的情况,就像它们对字符串文字进行去重复并将它们放在只读内存中一样。
在小辅助函数中以这种方式定义常量很好:编译器在内联函数后将提升循环中的常量设置。
它还允许编译器优化掉整个16字节的存储空间,并使用vbroadcastss xmm0, dword [mem]
或类似的东西加载它。
这个解决方案显然不可移植,它与GCC 8一起使用(仅使用此编译器测试):
#include #include #include #include #define INIT_M128(vFloat) {(vFloat), (vFloat), (vFloat), (vFloat)} #define INIT_M128I(vU32) {((uint64_t)(vU32) | (uint64_t)(vU32) << 32u), ((uint64_t)(vU32) | (uint64_t)(vU32) << 32u)} static void print128(const void *p) { unsigned char buf[16]; memcpy(buf, p, 16); for (int i = 0; i < 16; ++i) { printf("%02X ", buf[i]); } printf("\n"); } int main(void) { static __m128 const glob_a = INIT_M128(12102203.2f); static __m128i const glob_b = INIT_M128I(127 * (1 << 23) - 486411); static __m128 const glob_m87 = INIT_M128(-87.0f); __m128 a = _mm_set1_ps(12102203.2f); __m128i b = _mm_set1_epi32(127 * (1 << 23) - 486411); __m128 m87 = _mm_set1_ps(-87); print128(&a); print128(&glob_a); print128(&b); print128(&glob_b); print128(&m87); print128(&glob_m87); return 0; }
正如@harold(仅在C中)的答案中所解释的,以下代码(使用或不使用WITHSTATIC
构建)生成完全相同的代码。
#include #include #include #include #define INIT_M128(vFloat) {(vFloat), (vFloat), (vFloat), (vFloat)} #define INIT_M128I(vU32) {((uint64_t)(vU32) | (uint64_t)(vU32) << 32u), ((uint64_t)(vU32) | (uint64_t)(vU32) << 32u)} __m128 FastExpSse2(__m128 x) { #ifdef WITHSTATIC static __m128 const a = INIT_M128(12102203.2f); static __m128i const b = INIT_M128I(127 * (1 << 23) - 486411); static __m128 const m87 = INIT_M128(-87.0f); #else __m128 a = _mm_set1_ps(12102203.2f); __m128i b = _mm_set1_epi32(127 * (1 << 23) - 486411); __m128 m87 = _mm_set1_ps(-87); #endif __m128 mask = _mm_cmpge_ps(x, m87); __m128i tmp = _mm_add_epi32(_mm_cvtps_epi32(_mm_mul_ps(a, x)), b); return _mm_and_ps(_mm_castsi128_ps(tmp), mask); }
总而言之,最好删除static和const关键字(在C ++中更好更简单的代码,在C中,代码是可移植的,因为我提出的hack代码实际上不是可移植的)