Умножение вектора на константу с использованием SSE - PullRequest
2 голосов
/ 11 марта 2011

У меня есть код, который работает с 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} но просто заставляет меня чувствовать себя глупо, даже если я напишу макрос для этого. Кроме того, я почти уверен, что это не приведет к очень эффективному коду.

Поиск в Google не дал четких ответов (см. Загрузка констант с плавающей запятой в регистры SSE ).

Так, каков наилучший способ умножить целый вектор на одну и ту же константу?

Ответы [ 3 ]

10 голосов
/ 11 марта 2011

Просто используйте встроенные функции и позвольте компилятору позаботиться об этом, например:

__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 голосов
/ 09 декабря 2013

Нет причин, по которым нужно использовать встроенные функции для этого. ОП просто хочет сделать трансляцию. Это такая же базовая операция 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

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

1 голос
/ 08 декабря 2013

Возможно, это не самый лучший способ, но именно этот подход я использовал, когда баловался с 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);
...