为什么简单的代码在现代编译器中不能使用 SSE 和 AVX 自动向量化?

问题描述 投票:0回答:1

我最近深入研究了 x86-64 架构并探索了 SSE 和 AVX 的功能。我尝试编写一个简单的向量加法函数,如下所示:

void compute(const float *a, const float *b, float *c) {
    c[0] = a[0] + b[0];
    c[1] = a[1] + b[1];
    c[2] = a[2] + b[2];
    c[3] = a[3] + b[3];
}

同时使用

gcc
clang
,我使用以下选项进行编译:

cc -std=c23 -march=native -O3 -ftree-vectorize main.c

但是,当我检查反汇编时,输出在矢量化方面并不完全符合我的预期:

compute:
  vmovss xmm0, dword ptr [rdi]
  vaddss xmm0, xmm0, dword ptr [rsi]
  vmovss dword ptr [rdx], xmm0
  vmovss xmm0, dword ptr [rdi + 4]
  vaddss xmm0, xmm0, dword ptr [rsi + 4]
  vmovss dword ptr [rdx + 4], xmm0
  vmovss xmm0, dword ptr [rdi + 8]
  vaddss xmm0, xmm0, dword ptr [rsi + 8]
  vmovss dword ptr [rdx + 8], xmm0
  vmovss xmm0, dword ptr [rdi + 12]
  vaddss xmm0, xmm0, dword ptr [rsi + 12]
  vmovss dword ptr [rdx + 12], xmm0
  ret

这看起来像标量代码,一次处理一个元素。但是当我手动使用内在函数时,我得到了预期的向量化实现:

#include <xmmintrin.h>

void compute(const float *a, const float *b, float *c) {
    __m128 va = _mm_loadu_ps(a);
    __m128 vb = _mm_loadu_ps(b);
    __m128 vc = _mm_add_ps(va, vb);
    _mm_storeu_ps(c);
}

据我了解,现代处理器非常强大,SSE(1999 年推出)和 AVX(自 2011 年起)现已成为标准。然而,即使我明确启用优化,编译器似乎并不总是自动充分利用这些指令。

感觉有点像我们发明了隐形传送,但人们仍然更喜欢乘船穿越大西洋。现代编译器可能会犹豫是否为如此简单的事情生成矢量化代码,是否有合理的理由?


正如 Barmar 所建议的,4 个元素可能不足以获得使用矢量化的好处。我尝试了以下方法并得到了相同的欺骗性结果:

float a[512];
float b[512];
float c[512];

void compute() {  
    for (size_t i = 0; i < 512; i++) 
        c[i] = a[i] + b[i];
}

在 Godbolt 上,GCC

-O3 -march=x86-64-v3
会使用 256 位 AVX 指令自动向量化。)

c optimization sse avx auto-vectorization
1个回答
6
投票

别名就是这里的问题。编译器无法知道

a
b
c
相关的内存区域是否可以重叠。编译器有时可以生成特定的代码,但在这里,不值得检查是否有任何重叠:它会使函数变慢。

restrict
关键字旨在解决此问题。 这里是一个适用于所有主流编译器的示例:

void compute(const float * restrict a, const float * restrict b, float * restrict c)
{
    c[0] = a[0] + b[0];
    c[1] = a[1] + b[1];
    c[2] = a[2] + b[2];
    c[3] = a[3] + b[3];
}

提供的带有全局数组的代码确实在 GoldBolt 上生成 SIMD 汇编代码。请注意,GCC 代码未展开,但这是另一个问题(可以使用

#pragma GCC unroll(4)
等指令解决)。

© www.soinside.com 2019 - 2024. All rights reserved.