代码之家  ›  专栏  ›  技术社区  ›  Royi

在“C”函数中定义“static const”SIMD变量

  •  3
  • Royi  · 技术社区  · 8 年前

    我有一个这样的函数(从 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 兼容的。
    但是编译器不接受这种形式 static __m128i const b = _mm_set1_epi32(127 * (1 << 23) - 486411); 当我使用 C级

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

    有什么问题吗 C级

    谢谢您。

    3 回复  |  直到 8 年前
        1
  •  4
  •   user555045    8 年前

    删除 static 和 const

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

    __m128 a = _mm_set1_ps(12102203.2f); 次,在程序的数据段中准备常量向量,并在运行时简单地加载,而不需要它周围的垃圾

    检查asm以确保 静止的 事情就是这样:( 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
    
        2
  •  4
  •   Peter Cordes    8 年前

    _mm_set1_ps(-87); _mm_set 内在函数不是当前编译器的有效静态初始值设定项,因为它未被视为 constant expression .

    在C++中,它编译为运行时初始化的 static static __m128 在函数内部,有一个保护变量来保护它。

    在C中,它只是拒绝编译,因为C不支持非常量初始值设定项/构造函数。 _mm\u套 不像@benjarobin的答案显示的那样,是底层gnuc本机向量的带括号的初始值设定项。


    这真的很愚蠢,在所有4个主流的x86 C++编译器(Gcc/CLAN/ICC/MSVC)中,似乎是一个缺失的优化。即使每个人 static const __m128 var 如果有一个不同的地址,编译器可以通过使用初始化的只读存储器而不是在运行时复制来实现这一点。

    因此,似乎恒定传播无法一直到转弯 即使启用了优化,也会转换为常量初始值设定项。


    从不使用 static const __m128 var = _mm_set... 即使在C++中,它也是低效的。

    相反,避免 静止的 . 你还可以用 const 阻止自己意外地分配了其他的东西,告诉人类读者这是一个常数。没有 静止的 常数 在自动存储上,只执行编译时检查,确保不修改对象。

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

    编译器擅长于此,而且 将 优化多个函数使用相同向量常量的情况,就像它们消除重复的字符串文本并将其放入只读内存一样。

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

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

        3
  •  2
  •   benjarobin    8 年前

    这个解决方案显然是可行的 可移植,使用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);
    }
    

    所以总的来说最好去掉 静止的 常数 关键词(更好和更简单的代码在C++中,在C代码是便携式的,因为我建议黑客代码不是真正的便携式)