根据英特尔软件开发人员手册(第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
现在程序会出现seg-faults,因为使用了专用的对齐加载指令,但内存地址没有对齐。 (如果我使用_mm256_loadu_ps,或者如果我将OFFSET设置为0,则程序不会出现段错误。)
这使得程序员受到编译器的支配,并且在我的拙见中使行为部分无法预测。
我的问题是:有没有办法强制C编译器在处理指令(如vaddps)中生成直接加载或生成专用加载指令(如vmovaps)?
答案 0 :(得分:4)
无法使用内在函数显式控制负载的折叠。我认为这是内在函数的弱点。如果要显式控制折叠,则必须使用汇编。
在GCC的早期版本中,我能够使用对齐或未对齐的载荷在一定程度上控制折叠。但是,似乎不再是这种情况(GCC 4.9.2)。我的意思是例如在函数AddDot4x4_vec_block_8wide
here中,负载被折叠
vmulps ymm9, ymm0, YMMWORD PTR [rax-256]
vaddps ymm8, ymm9, ymm8
然而in a previous verison of GCC载荷没有折叠:
vmovups ymm9, YMMWORD PTR [rax-256]
vmulps ymm9, ymm0, ymm9
vaddps ymm8, ymm8, ymm9
显然,正确的解决方案是,当您知道数据已对齐并且您确实想要显式控制折叠使用程序集时,仅使用对齐的载荷。
答案 1 :(得分:3)
除了 Z boson 的答案之外,我可以告诉编译器正确地进行加载折叠,因为它假设内存区域已对齐(因为def getItems[A,B](someClass:A, id: String): Vector[B] = {
someClass.createToken(id)
someClass.getItems(id).asScala.toVector
}
标记了数组)。但是,在运行时,该属性不适用于堆栈上的值,因为堆栈只有16字节对齐(请参阅this错误)。您可以尝试通过指定__attribute__ ((aligned(32)))
和main
(请参阅here)强制编译器在-mstackrealign
中输入时将堆栈重新对齐为32个字节,但这会产生性能开销。