局部变量未在内联函数中对齐
Local variable not aligned in inline function
在使用 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)))
而内联函数中的局部变量不对齐?
_mm256_store_pd 要求您存储的内存地址必须与 32 字节边界对齐。但是在 C 中,我只认为 8 字节双精度和 8 字节双精度的标准对齐是 8 字节边界。
如果我不得不猜测函数何时未内联,它会在 32 字节边界上启动 localvar 数组。我不确定这是保证还是运气。我猜是运气,因为理论上内联一个函数不应该改变任何东西。编译器可能会将正确数量的字节压入堆栈,以便它对齐。我也看不出为什么它会保证 32 字节对齐。
当它被内联时,它的行为就好像代码只是在您调用函数的地方键入的一样。因此,您只能保证 localvar 是 8 字节对齐的,而不是保证的 32 字节对齐。我认为正确的解决方案是使用 aligned 属性来解决您的问题。您还可以使用 _mm256_storeu_pd
内在函数,它在没有对齐要求的情况下做同样的事情。根据我使用 haswell CPU 的经验,它同样快。
提供 32 字节对齐需要额外的指令(因为 ABI 只保证 16 字节对齐;只需查看 alignas(32)
或 __attribute__((aligned(32)))
版本的 asm)。 如果你不要求编译器当然不会做,因为它不是免费的。 (另请参阅 gcc 的 -mpreferred-stack-boundary
which controls this, and the 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
让编译器知道一个单独的定义不是需要。
在使用 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)))
而内联函数中的局部变量不对齐?
_mm256_store_pd 要求您存储的内存地址必须与 32 字节边界对齐。但是在 C 中,我只认为 8 字节双精度和 8 字节双精度的标准对齐是 8 字节边界。
如果我不得不猜测函数何时未内联,它会在 32 字节边界上启动 localvar 数组。我不确定这是保证还是运气。我猜是运气,因为理论上内联一个函数不应该改变任何东西。编译器可能会将正确数量的字节压入堆栈,以便它对齐。我也看不出为什么它会保证 32 字节对齐。
当它被内联时,它的行为就好像代码只是在您调用函数的地方键入的一样。因此,您只能保证 localvar 是 8 字节对齐的,而不是保证的 32 字节对齐。我认为正确的解决方案是使用 aligned 属性来解决您的问题。您还可以使用 _mm256_storeu_pd
内在函数,它在没有对齐要求的情况下做同样的事情。根据我使用 haswell CPU 的经验,它同样快。
提供 32 字节对齐需要额外的指令(因为 ABI 只保证 16 字节对齐;只需查看 alignas(32)
或 __attribute__((aligned(32)))
版本的 asm)。 如果你不要求编译器当然不会做,因为它不是免费的。 (另请参阅 gcc 的 -mpreferred-stack-boundary
which controls this, and the 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
让编译器知道一个单独的定义不是需要。