在C函数中定义一个static const SIMD变量

时间:2018-09-02 17:21:24

标签: c optimization vectorization sse simd

我有一个这样的函数(来自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

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

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

谢谢。

3 个答案:

答案 0 :(得分:3)

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

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

在C语言中,它只是拒绝编译,因为C语言不支持非恒定的初始化程序/构造函数。 _mm_set不像底层GNU C本机向量的支撑初始化程序,如@benjarobin的答案显示。


这真是愚蠢,似乎在所有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]或类似的东西进行加载。

答案 1 :(得分:2)

此解决方案显然可移植,它与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);
}

因此,总而言之,最好删除 static const 关键字(在C ++中更好,更简单的代码,在C中,该代码是可移植的,因为我建议使用hack的代码)不是真正可移植的)

答案 2 :(得分:2)

删除staticconst

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

__m128 a = _mm_set1_ps(12102203.2f);不是函数调用,它只是表达矢量常量的一种方式。 “仅执行一次”无法节省任何时间-通常发生次,其中常量向量在程序的数据段中准备,并且只需在运行时加载,而不会浪费时间static引入的内容。

确定没有static的情况下会检查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