使用AVXAVX2固有的对齐和不对齐的内存访问。

问题描述 投票:12回答:2

根据英特尔的《软件开发者手册》(sec.14.9),AVX放宽了内存访问的对齐要求。如果在处理指令中直接加载数据,如?

vaddps ymm0,ymm0,YMMWORD PTR [rax]

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

vmovaps ymm0,YMMWORD PTR [rax]

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

让我感到困惑的是,在我的例子中,由gccg++(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;
}

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

我在编译代码时使用了

g++ -Wall -O3 -march=native -o memtest memtest.C

在一个带有AVX的CPU上。如果我检查g++生成的代码,通过使用

objdump -S -M intel-mnemonic memtest | more

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

vaddps ymm0,ymm0,YMMWORD PTR [rax]

尽管内存地址没有对齐(OFFSET为1),但代码的执行没有任何问题。这很明显,因为vaddps可以容忍不对齐的地址。

如果我取消了第二条加法本征的行,编译器不能融合加载和加法,因为vaddps只能有一个内存源操作数,并生成。

vmovaps ymm0,YMMWORD PTR [rax]
vaddps ymm1,ymm0,ymm0
vaddps ymm0,ymm1,ymm0

现在程序seg-faults,因为使用了专门的对齐加载指令,但内存地址没有对齐。 (程序没有seg-faults)。顺便说一下,如果我使用_mm256_loadu_ps,或者把OFFSET设为0,程序就不会seg-fault)。

在我看来,这使得程序员只能听从编译器的摆布,并且使得行为部分不可预测。

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

gcc avx avx2
2个回答
4
投票

就没有办法用本征来明确控制加载的折叠。我认为这是内在论的一个弱点。如果你想明确地控制折叠,那么你必须使用汇编。

在之前的GCC版本中,我可以使用对齐或不对齐的负载在一定程度上控制折叠。然而,现在似乎不再是这样了(GCC 4.9.2)。我的意思是,例如在函数 AddDot4x4_vec_block_8wide 此处 叠加

vmulps  ymm9, ymm0, YMMWORD PTR [rax-256]
vaddps  ymm8, ymm9, ymm8

然而 在以前的版本中,GCC 的载荷没有被折叠。

vmovups ymm9, YMMWORD PTR [rax-256]
vmulps  ymm9, ymm0, ymm9
vaddps  ymm8, ymm8, ymm9

正确的解决方案是,显然,只有当你知道数据是对齐的时候,才使用对齐的负载, 如果你真的想明确地控制折叠,就使用装配。


3
投票

除了 Z玻色子的答案,我可以知道问题可能是由于编译器假设内存区域是对齐的(因为...)。__attribute__ ((aligned(32))) 标记数组)。) 在运行时,这个属性可能对堆栈上的值不起作用,因为堆栈只有16字节对齐(见 这个 bug,在写这篇文章的时候,这个bug仍然是开放的,尽管一些修复已经进入了gcc 4.6)。) 编译器有权利选择实现本征的指令,所以它可以将内存负载折叠到计算指令中,也可以不折叠,它也有权利使用 vmovaps 当折叠没有发生时(因为,如前所述,内存区域应该是对齐的)。

你可以尝试强迫编译器在输入到 main 通过指定 -mstackrealign-mpreferred-stack-boundary=5 (见 此处)但会产生性能开销。

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