使用AVX/AVX2内在函数对齐和未对齐的内存访问

Ral*_*alf 12 gcc avx avx2

根据英特尔软件开发人员手册(第14.9节),AVX放宽了内存访问的对齐要求.如果数据直接加载到处理指令中,例如

vaddps ymm0,ymm0,YMMWORD PTR [rax]
Run Code Online (Sandbox Code Playgroud)

加载地址不必对齐.但是,如果使用专用的对齐加载指令,例如

vmovaps ymm0,YMMWORD PTR [rax]
Run Code Online (Sandbox Code Playgroud)

必须对齐加载地址(为32的倍数),否则会引发异常.

令我困惑的是内在函数的自动代码生成,在我的例子中是gcc/g ++(4.6.3,Linux).请查看以下测试代码:

#include <x86intrin.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#define SIZE (1L << 26)
#define OFFSET 1

int main() {
  float *data;
  assert(!posix_memalign((void**)&data, 32, SIZE*sizeof(float)));
  for (unsigned i = 0; i < SIZE; i++) data[i] = drand48();
  float res[8]  __attribute__ ((aligned(32)));
  __m256 sum = _mm256_setzero_ps(), elem;
  for (float *d = data + OFFSET; d < data + SIZE - 8; d += 8) {
    elem = _mm256_load_ps(d);
    // sum = _mm256_add_ps(elem, elem);
    sum = _mm256_add_ps(sum, elem);
  }
  _mm256_store_ps(res, sum);
  for (int i = 0; i < 8; i++) printf("%g ", res[i]); printf("\n");
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

(是的,我知道代码有问题,因为我在未对齐的地址上使用对齐的加载,但请耐心等待...)

我编译代码

g++ -Wall -O3 -march=native -o memtest memtest.C
Run Code Online (Sandbox Code Playgroud)

在带AVX的CPU上.如果我通过使用检查g ++生成的代码

objdump -S -M intel-mnemonic memtest | more
Run Code Online (Sandbox Code Playgroud)

我看到编译器不生成对齐的加载指令,而是直接在向量加法指令中加载数据:

vaddps ymm0,ymm0,YMMWORD PTR [rax]
Run Code Online (Sandbox Code Playgroud)

代码执行没有任何问题,即使内存地址未对齐(OFFSET为1).这很清楚,因为vaddps可以容忍未对齐的地址.

如果我使用第二个加法内在函数取消注释该行,则编译器无法融合加载和加法,因为vaddps只能有一个内存源操作数,并生成:

vmovaps ymm0,YMMWORD PTR [rax]
vaddps ymm1,ymm0,ymm0
vaddps ymm0,ymm1,ymm0
Run Code Online (Sandbox Code Playgroud)

现在程序会出现seg-fault,因为使用了专用的对齐加载指令,但是内存地址没有对齐.(如果我使用_mm256_loadu_ps,或者如果我将OFFSET设置为0,那么程序不会出现seg-fault.)

这使得程序员受到编译器的支配,并且在我的拙见中使行为部分无法预测.

我的问题是:有没有办法强制C编译器在处理指令(如vaddps)中生成直接加载或生成专用加载指令(如vmovaps)?

Z b*_*son 7

无法使用内在函数显式控制负载的折叠。我认为这是内在的弱点。如果要显式控制折叠,则必须使用程序集。

在以前版本的 GCC 中,我能够使用对齐或未对齐的负载在一定程度上控制折叠。但是,情况似乎不再如此(GCC 4.9.2)。我的意思是,例如在AddDot4x4_vec_block_8wide 此处的功能中,负载被折叠

vmulps  ymm9, ymm0, YMMWORD PTR [rax-256]
vaddps  ymm8, ymm9, ymm8
Run Code Online (Sandbox Code Playgroud)

然而,在以前的 GCC 版本中,负载没有折叠:

vmovups ymm9, YMMWORD PTR [rax-256]
vmulps  ymm9, ymm0, ymm9
vaddps  ymm8, ymm8, ymm9
Run Code Online (Sandbox Code Playgroud)

显然,正确的解决方案是,仅当您知道数据已对齐并且您确实想明确控制折叠使用程序集时才使用对齐的加载。

  • @PeterCordes 我发现 ICC15 有时会折叠负载,即使这意味着要复制它。(多个折叠加载到同一地址)这通常是在寄存器压力的情况下。 (2认同)