根据英特尔的软件开发手册(第14.9节),AVX放宽了内存访问的对齐要求。如果数据直接在处理指令中加载,例如:
vaddps ymm0,ymm0,YMMWORD PTR [rax]
负载地址不必对齐。但是,如果使用专用的对齐负载指令,例如
vmovaps ymm0,YMMWORD PTR [rax]
如果负载地址未对齐(必须为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;
}
(是的,我知道代码有缺陷,因为我在未对齐的地址上使用了对齐加载,但请谅解...)
我使用以下命令编译代码
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 可以容忍不对齐的地址。
如果我取消第二个加法 intrinsic 的注释,编译器就无法将加载和加法合并,因为 vaddps 只能有一个内存源操作数,因此会生成:
vmovaps ymm0,YMMWORD PTR [rax]
vaddps ymm1,ymm0,ymm0
vaddps ymm0,ymm1,ymm0
现在程序出现段错误,因为使用了专用的对齐加载指令,但是内存地址没有对齐。(顺便说一下,如果我使用_mm256_loadu_ps,或者将OFFSET设置为0,程序就不会出现段错误。)
这使得程序员受制于编译器,行为部分不可预测,在我看来很令人担忧。
我的问题是:有没有一种方法可以强制C编译器生成一个直接加载处理指令(例如vaddps),或者生成一个专用的加载指令(例如vmovaps)?
_mm256_loadu_ps
,gcc会生成一个未对齐的加载vmovups
和一个在寄存器操作数上工作的vaddps
,而它完全可以只生成一个带有内存操作数的vaddps
指令,因为它可以容忍未对齐的地址。 - Ralf