用 sse 执行内在函数

Posted

技术标签:

【中文标题】用 sse 执行内在函数【英文标题】:performance of intrinsic functions with sse 【发布时间】:2011-03-11 18:25:16 【问题描述】:

我目前正在开始使用 SSE。 我之前关于 SSE 的问题 (Mutiplying vector by constant using SSE) 的答案让我想到了测试使用像 _mm_mul_ps() 这样的内在函数与像 * 这样只使用“普通运算符”(不确定最好的术语是什么)之间的区别的想法。

所以我写了两个测试用例,它们只是计算结果的方式不同: 方法一:

int main(void)
    float4 a, b, c;

    a.v = _mm_set_ps(1.0f, 2.0f, 3.0f, 4.0f);
    b.v = _mm_set_ps(-1.0f, -2.0f, -3.0f, -4.0f);

    printf("method 1\n");
    c.v = a.v + b.v;      // <---
    print_vector(a);
    print_vector(b);
    printf("1.a) Computed output 1: ");
    print_vector(c);

    exit(EXIT_SUCCESS);
  

方法二:

int main(void)
    float4 a, b, c;

    a.v = _mm_set_ps(1.0f, 2.0f, 3.0f, 4.0f);
    b.v = _mm_set_ps(-1.0f, -2.0f, -3.0f, -4.0f);

    printf("\nmethod 2\n");
    c.v = _mm_add_ps(a.v, b.v);      // <---
    print_vector(a);
    print_vector(b);
    printf("1.b) Computed output 2: ");
    print_vector(c);

    exit(EXIT_SUCCESS);

两个测试用例共享以下内容:

typedef union float4
    __m128  v;
    float   x,y,z,w;
 float4;

void print_vector (float4 v)
    printf("%f,%f,%f,%f\n", v.x, v.y, v.z, v.w);

所以要比较我编译的两种情况生成的代码:gcc -ggdb -msse -c t_vectorExtensions_method1.c

导致(仅显示添加两个向量的部分 - 不同): 方法一:

    c.v = a.v + b.v;
  a1:   0f 57 c9                xorps  %xmm1,%xmm1
  a4:   0f 12 4d d0             movlps -0x30(%rbp),%xmm1
  a8:   0f 16 4d d8             movhps -0x28(%rbp),%xmm1
  ac:   0f 57 c0                xorps  %xmm0,%xmm0
  af:   0f 12 45 c0             movlps -0x40(%rbp),%xmm0
  b3:   0f 16 45 c8             movhps -0x38(%rbp),%xmm0
  b7:   0f 58 c1                addps  %xmm1,%xmm0
  ba:   0f 13 45 b0             movlps %xmm0,-0x50(%rbp)
  be:   0f 17 45 b8             movhps %xmm0,-0x48(%rbp)

方法二:

    c.v = _mm_add_ps(a.v, b.v);
  a1:   0f 57 c0                xorps  %xmm0,%xmm0
  a4:   0f 12 45 a0             movlps -0x60(%rbp),%xmm0
  a8:   0f 16 45 a8             movhps -0x58(%rbp),%xmm0
  ac:   0f 57 c9                xorps  %xmm1,%xmm1
  af:   0f 12 4d b0             movlps -0x50(%rbp),%xmm1
  b3:   0f 16 4d b8             movhps -0x48(%rbp),%xmm1
  b7:   0f 13 4d f0             movlps %xmm1,-0x10(%rbp)
  bb:   0f 17 4d f8             movhps %xmm1,-0x8(%rbp)
  bf:   0f 13 45 e0             movlps %xmm0,-0x20(%rbp)
  c3:   0f 17 45 e8             movhps %xmm0,-0x18(%rbp)
/* Perform the respective operation on the four SPFP values in A and B.  */

extern __inline __m128 __attribute__((__gnu_inline__, __always_inline__, __artificial__))
_mm_add_ps (__m128 __A, __m128 __B)

  return (__m128) __builtin_ia32_addps ((__v4sf)__A, (__v4sf)__B);
  c7:   0f 57 c0                xorps  %xmm0,%xmm0
  ca:   0f 12 45 e0             movlps -0x20(%rbp),%xmm0
  ce:   0f 16 45 e8             movhps -0x18(%rbp),%xmm0
  d2:   0f 57 c9                xorps  %xmm1,%xmm1
  d5:   0f 12 4d f0             movlps -0x10(%rbp),%xmm1
  d9:   0f 16 4d f8             movhps -0x8(%rbp),%xmm1
  dd:   0f 58 c1                addps  %xmm1,%xmm0
  e0:   0f 13 45 90             movlps %xmm0,-0x70(%rbp)
  e4:   0f 17 45 98             movhps %xmm0,-0x68(%rbp)

显然,使用内部_mm_add_ps() 时生成的代码要大得多。为什么是这样?它不应该产生更好的代码吗?

【问题讨论】:

使用gcc -ggdb -msse -O3 -c t_vectorExtensions_method1.c 编译后,两种情况都会生成完全相同的输出。所以使用内在函数没有任何好处。总是这样吗? 【参考方案1】:

真正重要的是addps。在一个更现实的用例中,比如说,在循环中添加两个大的浮点向量,循环体将只包含addps、两个加载和一个存储,以及一些用于地址算术的标量整数指令.在现代超标量 CPU 上,许多这些指令将并行执行。

还请注意,您在禁用优化的情况下进行编译,因此您不会获得特别高效的代码。试试gcc -O3 -msse3 ...

【讨论】:

-O3 编译了两个案例,用于 sse、sse2 和 sse3。所有六种情况都生成完全相同的机器代码。 (因为这是一个简单的添加,这并不让我感到惊讶)。但是:当似乎没有区别时,使用内部函数有意义吗?使用它们会使代码变得难以阅读。 @emanuel:您无法将一个非常简单的测试用例外推到一般情况下。大多数现代编译器在自动矢量化代码(尤其是 ICC)方面可以做得很好,这就是您所看到的,但大多数编译器都适用于复杂代码或边缘情况。 Imo,坚持内在函数,让您的代码保持清晰,并且不会让您依赖编译器来做正确的事情 @Emanuel:例如能力使用+ 等添加向量类型是特定于 gcc 的,并且只是可能的 SSE 操作的一小部分的有用简写。如果您要进行任何实质性的 SIMD 编程,您确实需要熟悉内在函数。

以上是关于用 sse 执行内在函数的主要内容,如果未能解决你的问题,请参考以下文章

SSE 内在函数优化

数组乘法与 sse 内在函数乘法的时序?

SSE 内在函数检查零标志

内在函数和寄存器(SSE)

SSE 内在函数向右移位

修改函数以使用 SSE 内在函数