根据英特尔的软件开发人员手册(第 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 可以容忍未对齐的地址。
如果我取消注释带有第二个附加内在函数的行,编译器无法融合加载和附加,因为 vaddps 只能有一个内存源操作数,并生成:
vmovaps ymm0,YMMWORD PTR [rax]
vaddps ymm1,ymm0,ymm0
vaddps ymm0,ymm1,ymm0
现在程序段错误,因为使用了专用的对齐加载指令,但内存地址未对齐。(顺便说一句,如果我使用 _mm256_loadu_ps,或者如果我将 OFFSET 设置为 0,则程序不会出现段错误。)
以我的拙见,这使程序员受到编译器的摆布,并使行为部分不可预测。
我的问题是:有没有办法强制 C 编译器在处理指令中生成直接加载(例如 vaddps)或生成专用加载指令(例如 vmovaps)?