【问题标题】:Define a `static const` SIMD Variable within a `C` Function在`C`函数中定义`static const` SIMD变量
【发布时间】:2019-02-07 21:34:06
【问题描述】:

我有一个这种形式的函数(来自Fastest Implementation of Exponential Function Using 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 兼容。
然而,当我使用C 编译器时,编译器不接受static __m128i const b = _mm_set1_epi32(127 * (1 &lt;&lt; 23) - 486411); 的形式。

但我不希望在每个函数调用中重新计算前 3 个值。
一种解决方案是内联它(但有时编译器会拒绝它)。

如果函数没有内联,是否有C 样式来实现它?

谢谢。

【问题讨论】:

    标签: c optimization vectorization sse simd


    【解决方案1】:

    删除staticconst

    也将它们从 C++ 版本中删除。 const 还可以,但是static 太可怕了,引入了每次都要检查的保护变量,而且第一次初始化非常昂贵。

    __m128 a = _mm_set1_ps(12102203.2f); 不是函数调用,它只是表达向量常量的一种方式。 “只做一次”不能节省时间 - 它通常发生 0 次,常量向量在程序的数据段中准备好并在运行时简单地加载,没有垃圾static 介绍的。

    检查 asm 以确保没有static 会发生这种情况:(from 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
    

    【讨论】:

    • 你确定你没有混淆 __thread 关键字? Static 和 const 不应再添加任何代码。它应该生成与您显示的代码相同的代码
    • 哦,我明白了,这是因为您使用 C++ 编译器构建代码,我说的是 C 代码。你是对的,在 C++ 代码中添加静态添加“复杂代码”
    • @benjarobin 那部分是关于 C++ 的。对于 C,甚至不可能将向量常量标记为静态。
    • @harold,您说删除C 中的两者会产生该部分开销为零的代码?回答链接问题的人使用它是否有原因? +1。
    • @Royi 是的,没有真正的开销。虽然如果内联,常量的负载可能会放在循环之前,诸如此类。无论如何,我在链接的答案中看不到任何静态变量,所以 IDK
    【解决方案2】:

    _mm_set1_ps(-87); 或任何其他 _mm_set 内在函数在当前编译器中不是有效的静态初始化程序,因为它不被视为 constant expression

    在 C++ 中,它编译为 static 存储位置的运行时初始化(从其他地方的向量字面量复制)。如果它是函数内部的static __m128,则有一个保护变量来保护它。

    在 C 中,它只是拒绝编译,因为 C 不支持非常量初始化器/构造器。 _mm_set 不像 @benjarobin 的回答所示的底层 GNU C 本机向量的大括号初始化器。


    这真的很愚蠢,并且似乎是所有 4 个主流 x86 C++ 编译器 (gcc/clang/ICC/MSVC) 中的优化失误。即使每个static const __m128 var 有一个不同的地址在某种程度上很重要,编译器也可以通过使用初始化的只读存储而不是在运行时复制来实现这一点。

    因此,即使启用了优化,似乎常量传播也无法将_mm_set 转变为常量初始化器。


    即使在 C++ 中也不要使用static const __m128 var = _mm_set...;效率低下。

    在函数内部更糟糕,但全局范围仍然很糟糕。

    相反,请避免使用static。您仍然可以使用const 来阻止自己意外分配其他内容,并告诉人类读者这是一个常数。如果没有static,它不会影响变量的存储位置/方式。 const 自动存储只是在编译时检查你没有修改对象。

    const __m128 var = _mm_set1_ps(-87);    // not static
    

    编译器擅长于此,优化多个函数使用相同向量常量的情况,就像它们对字符串文字进行重复删除并将它们放入只读内存中一样.

    在小型辅助函数中以这种方式定义常量很好:编译器在内联函数后会将常量设置提升出循环。

    它还允许编译器优化掉全部 16 字节的存储空间,并使用 vbroadcastss xmm0, dword [mem] 或类似的东西加载它。

    【讨论】:

      【解决方案3】:

      这个解决方案显然不是可移植的,它适用于 GCC 8(仅用这个编译器测试过):

      #include <stdio.h>
      #include <stdint.h>
      #include <emmintrin.h>
      #include <string.h>
      
      #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 <stdio.h>
      #include <stdint.h>
      #include <emmintrin.h>
      #include <string.h>
      
      #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);
      }
      

      因此,总而言之,最好删除 staticconst 关键字(C++ 中更好、更简单的代码,而在 C 中,代码是可移植的,因为我建议 hack 代码不是很便携)

      【讨论】:

        猜你喜欢
        • 2021-10-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多