使用SSE将常数乘以常数

Ema*_* Ey 2 c gcc sse simd clang

我有一些代码操作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];  
} 
Run Code Online (Sandbox Code Playgroud)

这样的事情:

float4 y;
float a1 =   25.0/216.0;  

y = a1 * x;  
Run Code Online (Sandbox Code Playgroud)

哪里:

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

typedef union float4{
    v4sf v;
    float x,y,z,w;
} float4;
Run Code Online (Sandbox Code Playgroud)

这当然不会起作用,因为我试图对不兼容的数据类型进行乘法运算.
现在,我可以做类似的事情:
float4 a1 = (v4sf){25.0/216.0, 25.0/216.0, 25.0/216.0, 25.0/216.0} 但只是让我感到愚蠢,即使我写了一个宏来做这件事.此外,我非常肯定不会产生非常有效的代码.

谷歌搜索没有明确答案(请参阅加载常量浮点到SSE寄存器).

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

Pau*_*l R 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
Run Code Online (Sandbox Code Playgroud)

如果查看生成的代码,它应该非常有效 - 该25.0f / 16.0f值将在编译时计算,并且_mm_set1_ps生成通常会生成合理有效的代码来映​​射向量.

另请注意,您通常只会va在进入一个循环之前初始化一个常量向量,例如一次,然后您将完成大部分实际工作,因此它往往不是性能关键.


Z b*_*son 5

没有理由必须为此使用内在函数。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; */
Run Code Online (Sandbox Code Playgroud)

下面的代码编译得很好

#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
}
Run Code Online (Sandbox Code Playgroud)

您可以在http://coliru.stacked-crooked.com/a/de79cca2fb5d4b11查看结果

将该代码与内部代码进行比较,就可以清楚哪一个更具可读性。它不仅更具可读性,而且更容易移植到 ARM Neon 等。它看起来也与 OpenCL C 代码非常相似。

  • 除了编译器支持之外,您还需要考虑有许多重要的内在函数不能很好地映射到更通用的模型,例如“_mm_madd_epi16”。如果您正在做相对简单直接的事情,特别是如果它只是浮点/双精度,那么某种抽象层或编译器扩展很快可能是一个好方法,但它不是万能药 - 有很多用例,内在函数(甚至 asm)是更好的选择。 (2认同)