使用Intrinsics进行编程时出现了以下问题。 当我想加载或存储局部变量时,在内联函数中,我得到了内存冲突错误,但仅当函数内联时。我不知道为什么在内联函数中堆栈变量没有对齐。
我已经使用许多不同版本的GCC 4.9,5.3,6.1进行了测试。
失败的示例:
static inline foo(double *phi){
double localvar[4];
__m256d var = _mm256_load_pd (phi);
__m256d res = _mm256_mul_pd(var, var);
_mm256_store_pd (localvar, res); // <- failed due to memory violation
...
}
如果我添加__attribute__ ((aligned (32)))
或删除inline
,则该功能可以正常工作。
那么有人可以解释一下(请详细说明),为什么局部变量一般是在没有添加__attribute__ ((aligned (32)))
的情况下对齐而内联函数中的局部变量没有?
答案 0 :(得分:2)
_mm256_store_pd要求您存储的内存地址必须与32字节边界对齐。但是在C中我只认为8字节双精度的标准对齐是8字节边界。
如果我不得不猜测函数是否未内联,则会在32字节边界上启动localvar数组。我不确定这是保证还是运气。我猜运气,因为在理论上内联函数不应该改变任何东西。编译器可能正在将正确数量的字节推入堆栈,以使其变得对齐。此外,我认为没有理由保证32字节对齐。
当它被内联时,它就像在你调用函数的地方输入代码一样。因此,只保证localvar将是8字节对齐而不是保证32字节对齐。我认为正确的解决方案是使用对齐的属性来解决您的问题。您也可以使用_mm256_storeu_pd
内在函数,它在没有对齐要求的情况下执行相同的操作。根据我对我的haswell CPU的体验,它同样快。
答案 1 :(得分:2)
提供32字节对齐需要额外的指令(因为ABI仅保证16字节对齐;只需查看带有alignas(32)
或__attribute__((aligned(32)))
的版本的asm。 当然,如果你没有要求它,编译器就不会这样做,因为它不是免费的。 (另请参阅gcc&#39; s -mpreferred-stack-boundary
which controls this和x86代码wiki以获取ABI文档的链接。)
double localvar[4];
只需要8字节对齐。 SysV x86-64 ABI确保C99可变大小阵列的16字节对齐。我不确定普通编译时常量大小的数组是否默认为16-B对齐。
但是,当前版本的gcc由于某种原因在具有__m256d
局部变量的测试函数中将堆栈与32B对齐。在-O3
,它不会将它们泄漏到堆栈中,因此它们会被浪费掉(除了制作这样的错误代码之外)。 gcc没有删除这些东西的事实是一个错过的优化。 (-O0
需要它,gcc会将所有内容泄漏到内存中。)
由于我的测试函数版本(实际编译的版本)没有任何其他本地版本,因此双精度数组也是32B对齐的。据推测,您将其内联到具有其他本地人的调用者中,这会导致阵列的对齐方式不同。
Here's the code on the Godbolt compiler explorer:
extern void use_buffer(double*);
// static inline
void no_alignment(const double *phi){
double localvar[4];
__m256d var = _mm256_load_pd (phi);
__m256d res = _mm256_mul_pd(var, var);
_mm256_storeu_pd (localvar, res); // use an unaligned store since we didn't request alignment for the buffer
use_buffer(localvar);
}
lea r10, [rsp+8] // save old RSP (in a clumsy way)
and rsp, -32 // truncate RSP to the next 32B boundary
push QWORD PTR [r10-8] // save more stuff
push rbp
mov rbp, rsp
push r10
sub rsp, 40
... vmovupd YMMWORD PTR [rbp-48], ymm0 ... // function body
add rsp, 40
pop r10
pop rbp
lea rsp, [r10-8]
这就是为什么你的代码没有内联时就能正常工作的原因。尽管很奇怪,即使没有inline
关键字,它也没有内联,除非你没有优化编译,或者你没有使用static
让编译器知道不需要单独的定义。