内置函数在使用SSE指令时的性能表现

4

我现在开始学习SSE技术。 关于SSE的前一个问题的答案(使用SSE对向量乘以常数)让我想测试使用内置函数如_mm_mul_ps()和使用普通运算符(不确定什么是最好的术语)如*的差异。

因此,我编写了两个测试用例,只有计算结果的方法不同:
方法1:

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);
}  

方法2:

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

结果如下(只显示两个向量相加的部分-有所不同):
方法1:

    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编译后,两种情况生成完全相同的输出。因此,在使用内在函数方面没有任何好处。这总是成立的吗? - Emanuel Ey
1个回答

2

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

还要注意,您正在使用禁用优化的编译方式,因此不会得到特别高效的代码。请尝试gcc -O3 -msse3 ...


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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接