Перемещение вектора константой с использованием SSE

У меня есть код, который работает с 4D-векторами, и в настоящее время я пытаюсь преобразовать его в SSE. Я использую как clang, так и gcc на 64b linux.
Работа только на векторах - все это прекрасно. Но теперь приходит часть, где мне нужно умножить целый вектор на одну константу - Что-то вроде этого:

float y[4];
float a1 =   25.0/216.0;  

for(j=0; j<4; j++){  
    y[j] = a1 * x[j];  
} 

примерно так:

float4 y;
float a1 =   25.0/216.0;  

y = a1 * x;  

где:

typedef double v4sf __attribute__ ((vector_size(4*sizeof(float)))); 

typedef union float4{
    v4sf v;
    float x,y,z,w;
} float4;

Это, конечно, не сработает, потому что я пытаюсь сделать умножение несовместимых типов данных.
Теперь я могу сделать что-то вроде:
float4 a1 = (v4sf){25.0/216.0, 25.0/216.0, 25.0/216.0, 25.0/216.0} но просто заставляет меня чувствовать себя глупо, даже если я напишу макрос, чтобы сделать это. Кроме того, я уверен, что это не приведет к очень эффективному коду.

Googling это не дало четких ответов (см. Загрузка констант поплавков в регистры SSE).

Итак, что является лучшим способом для умножения целого вектора на ту же константу?

Ответ 1

Просто используйте intrinsics и пусть компилятор позаботится об этом, например.

__m128 vb = _mm_set_ps(1.0f, 2.0f, 3.0f, 4.0f); // vb = { 1.0, 2.0, 3.0, 4.0 }
__m128 va = _mm_set1_ps(25.0f / 216.0f); // va = { 25.0f / 216.0f, 25.0f / 216.0f, 25.0f / 216.0f, 25.0f / 216.0f }
__m128 vc = _mm_mul_ps(va, vb); // vc = va * vb

Если вы посмотрите на сгенерированный код, он должен быть достаточно эффективным - значение 25.0f / 16.0f будет вычисляться во время компиляции, а _mm_set1_ps генерирует обычно генерирует разумно эффективный код для разбиения вектора.

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

Ответ 2

Возможно, это не лучший способ, но это был подход, который я предпринял, когда я был в SSE.

float4 scale(const float s, const float4 a)
{
  v4sf sv = { s, s, s, 0.0f };
  float4 r = { .v = __builtin_ia32_mulps(sv, a.v) };
  return r;
}

float4 y;
float a1;

y = scale(a1, y);

Ответ 3

Нет причин, по которым нужно использовать для этого встроенные функции. ОП просто хочет сделать трансляцию. Это базовая операция SIMD как дополнение SIMD. Любая достойная библиотека/расширение SIMD должна поддерживать трансляции. Конечно, векторный класс Agner Fog делает, OpenCL делает, документация GCC ясно показывает, что это так.

a = b + 1;    /* a = b + {1,1,1,1}; */
a = 2 * b;    /* a = {2,2,2,2} * b; */

Следующий код компилируется просто отлично

#include <stdio.h>
int main() {     
    typedef float float4 __attribute__ ((vector_size (16)));

    float4 x = {1,2,3,4};
    float4 y = (25.0f/216.0f)*x;
    printf("%f %f %f %f\n", y[0], y[1], y[2], y[3]);
    //0.115741 0.231481 0.347222 0.462963
}

Вы можете увидеть результаты в http://coliru.stacked-crooked.com/a/de79cca2fb5d4b11

Сравните этот код с внутренним кодом и ясно, какой из них более читабельным. Мало того, что это более читаемо, проще переносить, например, ARM Neon. Он также очень похож на код OpenCL C.