Определите переменную SIMD `static const` внутри функции` C` - PullRequest
0 голосов
/ 02 сентября 2018

У меня есть функция в этой форме (с Самая быстрая реализация экспоненциальной функции с использованием 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 ]

0 голосов
/ 02 сентября 2018

Удалить static и const.

Также удалите их из версии C ++. const нормально, но static ужасно, вводит защитные переменные, которые проверяются каждый раз, и очень дорогую инициализацию в первый раз.

__m128 a = _mm_set1_ps(12102203.2f); это не вызов функции, это просто способ выражения константы вектора. Невозможно сэкономить время, «делая это только один раз» - обычно это происходит ноль раз, когда вектор констант готовится в сегменте данных программы и просто загружается во время выполнения, без мусора вокруг него. что static вводит.

Проверьте асм, чтобы быть уверенным, без 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
0 голосов
/ 02 сентября 2018

_mm_set1_ps(-87); или любой другой _mm_set intrinsic не является допустимым статическим инициализатором с текущими компиляторами, поскольку он не обрабатывается как константное выражение .

В 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 ++; это неэффективно.

Внутри функции еще хуже, но глобальный охват все еще плох.

Вместо этого избегайте static. Вы все еще можете использовать const, чтобы остановить себя от случайного присвоения чего-то другого и сказать читателям-людям, что это константа. Без static это не повлияет на то, где и как хранится ваша переменная. const в автоматическом хранилище просто выполняет проверку во время компиляции, что вы не изменяете объект.

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

Компиляторы хороши в этом, и оптимизирует случай, когда несколько функций используют одну и ту же векторную константу, так же, как они дублируют строковые литералы и помещают их в постоянную память.

Определение констант таким образом в небольших вспомогательных функциях хорошо: компиляторы выведут постоянную настройку из цикла после включения функции.

Это также позволяет компиляторам оптимизировать все 16 байтов памяти и загружать ее с vbroadcastss xmm0, dword [mem] или чем-то подобным.

0 голосов
/ 02 сентября 2018

Это решение явно не переносимое, оно работает с 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 - код переносимый, так как при моем предложенном хаке код не действительно портативный)

...