2

我有一些在 4D 向量上运行的代码,我目前正在尝试将其转换为使用 SSE。我在 64b linux 上同时使用 clang 和 gcc。
仅对向量进行操作都可以很好地掌握。但是现在有一部分我必须将整个向量乘以一个常数 - 像这样:

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} 但这只是让我觉得很傻,即使我写了一个宏来做到这一点。另外,我很确定这不会产生非常有效的代码。

谷歌搜索没有带来明确的答案(请参阅将常量浮点数加载到 SSE 寄存器中)。

那么将整个向量乘以相同常数的最佳方法是什么?

4

3 回答 3

10

只需使用内在函数并让编译器处理它,例如

__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一次,然后进入一个您将完成大部分实际工作的循环,因此它往往不是性能关键。

于 2011-03-11T14:16:51.773 回答
2

没有理由为此必须使用内在函数。OP 只想进行广播。这与 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 代码非常相似。

于 2013-12-08T20:24:45.793 回答
1

这可能不是最好的方法,但这是我在 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);
于 2013-12-08T15:24:15.867 回答