Intereting Posts

Определить переменную SIMD «static const» в функции `C`

У меня есть функция в этой форме (от самой быстрой реализации экспоненциальной функции с использованием 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 значения были пересчитаны в каждом вызове функции.
Одним из решений является inline it (Но иногда компиляторы отвергают это).

Есть ли стиль 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 intrinsic не является допустимым статическим инициализатором с текущими компиляторами, поскольку он не рассматривается как постоянное выражение .

В C ++ он компилируется для инициализации среды static хранения (копирование из векторного литерала где-то в другом месте). И если это static __m128 внутри функции, для ее защиты есть защитная переменная.

В C он просто отказывается компилировать, потому что C не поддерживает непостоянные инициализаторы / конструкторы. _mm_set не похож на скопированный инициализатор для базового нативного вектора GNU C, как показывает ответ @ benjarobin.


Это действительно глупо и кажется пропущенной оптимизацией во всех 4 основных х86 C ++ компиляторах (gcc / clang / ICC / MSVC). Даже если это имеет значение, каждый static const __m128 var имеет отдельный адрес, компилятор мог бы добиться этого, используя инициализированное хранилище только для чтения, а не копирование во время выполнения.

Таким образом, кажется, что постоянное распространение не позволяет полностью превратить _mm_set в постоянный инициализатор, даже когда оптимизация включена.


Никогда не используйте static const __m128 var = _mm_set... даже в C ++; это неэффективно.

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

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

 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); } 

Итак, в заключение лучше удалить статические и константные ключевые слова (лучший и простой код на C ++, а в C код переносимый, поскольку с моим предложенным взломом код не очень портативен)