Перемещение вектора константой с использованием 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.