我有一些操作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}
但只是让我感到愚蠢,即使我写一个宏来做这件事。
此外,我非常肯定不会产生非常有效的代码。
谷歌搜索没有给出明确答案(见Load constant floats into SSE registers)。
那么将整个矢量乘以相同常数的最佳方法是什么?
答案 0 :(得分: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
,因此它往往不会对性能至关重要。< / p>
答案 1 :(得分:2)
没有理由为此需要使用内在函数。 OP只想做一个广播。这与SIMD添加的SIMD操作基本相同。任何体面的SIMD库/扩展都必须支持广播。正如OpenCL所做的那样,Agner Fog的矢量类确实如此,GCC documention清楚地表明它确实如此。
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霓虹灯。它看起来与OpenCL C代码非常相似。
答案 2 :(得分: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);