GCC __attribute__ 在 32 字节处对齐的 AVX 矢量化代码中的段错误
Posted
技术标签:
【中文标题】GCC __attribute__ 在 32 字节处对齐的 AVX 矢量化代码中的段错误【英文标题】:Seg Fault in AVX Vectorized Code with GCC __attribute__ aligned at 32 bytes 【发布时间】:2014-10-29 00:27:37 【问题描述】:只有当循环在 AVX 机器(Intel(R) Core(TM) i5-3570K CPU @ 3.40GHz)上完全矢量化时,我才会在循环中遇到段错误。
使用 gcc -c -march=native MyClass.cpp -O3 -ftree-vectorizer-verbose=6 编译
我正在尝试对齐数组,以便避免来自 -ftree-vectorizer-verbose=6 的这些消息:
MyClass.cpp:352: note: dependence distance modulo vf == 0 between this_7(D)->x[i_101] and this_7(D)->x[i_101]
MyClass.cpp:352: note: vect_model_load_cost: unaligned supported by hardware.
MyClass.cpp:352: note: vect_get_data_access_cost: inside_cost = 2, outside_cost = 0.
MyClass.cpp:352: note: vect_model_store_cost: unaligned supported by hardware.
MyClass.cpp:352: note: vect_get_data_access_cost: inside_cost = 2, outside_cost = 0.
MyClass.cpp:352: note: Alignment of access forced using peeling.
MyClass.cpp:352: note: vect_model_load_cost: aligned.
MyClass.cpp:352: note: vect_model_load_cost: inside_cost = 1, outside_cost = 0 .
MyClass.cpp:352: note: vect_model_simple_cost: inside_cost = 1, outside_cost = 1 .
MyClass.cpp:352: note: vect_model_store_cost: aligned.
MyClass.cpp:352: note: vect_model_store_cost: inside_cost = 1, outside_cost = 0 .
MyClass.cpp:352: note: cost model: prologue peel iters set to vf/2.
MyClass.cpp:352: note: cost model: epilogue peel iters set to vf/2 because peeling for alignment is unknown .
我想看到(并且确实看到)的是:
MyClass.cpp:352: note: dependence distance modulo vf == 0 between this_7(D)->x[i_101] and this_7(D)->x[i_101]
MyClass.cpp:352: note: vect_model_load_cost: aligned.
MyClass.cpp:352: note: vect_get_data_access_cost: inside_cost = 1, outside_cost = 0.
MyClass.cpp:352: note: vect_model_store_cost: aligned.
MyClass.cpp:352: note: vect_get_data_access_cost: inside_cost = 2, outside_cost = 0.
MyClass.cpp:352: note: vect_model_load_cost: aligned.
MyClass.cpp:352: note: vect_model_load_cost: inside_cost = 1, outside_cost = 0 .
MyClass.cpp:352: note: vect_model_simple_cost: inside_cost = 1, outside_cost = 1 .
MyClass.cpp:352: note: vect_model_store_cost: aligned.
MyClass.cpp:352: note: vect_model_store_cost: inside_cost = 1, outside_cost = 0 .
现在,无论如何,我都不是 C/C++/Assembler 大师,但是当我遇到 seg 错误时,我认为我的代码中有一些指针/数组/其他错误,并且完全矢量化的循环只是暴露了这个.但是经过两天学习汇编程序后,我无法找到它。所以我在这里。
代码如下所示(希望我包含了所有相关内容——我无法在此处完整分享实际的 .cpp):
class MyClass
private:
static const long maxElems = 1024;
static const double otherVar = 0.9;
double x[maxElems] __attribute__ ((aligned (32))); <-- gcc reports fully vectorized
//double x[maxElems]; <-- leads to unaligned peeling
public:
void myFunc()
// Always works
for (int i=0; i<maxElems; ++i) printf("Test: %d %.4e\n", i, x[i]);
// Seg fault if fully vectorized (no peeling)
for (int i=0; i<maxElems; ++i)
x[i] = x[i] - 42;
// Works if no seg fault earlier
for (int i=0; i<maxElems; ++i) printf("Test: %d %.4e\n", i, x[i]);
当它完全矢量化时(使用 -Wa,-alh 标志查看汇编程序):
989 00
990 0b56 488B4424 movq 40(%rsp), %rax
990 28
991 0b5b C5FD280D vmovapd .LC8(%rip), %ymm1
991 00000000
992 .p2align 4,,10
993 0b63 0F1F4400 .p2align 3
993 00
994 .L153:
995 0b68 C5FD2800 vmovapd (%rax), %ymm0
996 0b6c C5FD5CC1 vsubpd %ymm1, %ymm0, %ymm0
997 0b70 C5FD2900 vmovapd %ymm0, (%rax)
998 0b74 4883C020 addq $32, %rax
999 0b78 4C39E0 cmpq %r12, %rax
1000 0b7b 75EB jne .L153
同样,关于“不知道汇编程序”的常见警告,但我确实花了相当多的时间打印指针并检查汇编程序,以说服自己这个循环在数组的开头和结尾开始和结束。但是当我得到段错误时,x 的起始地址不能被 32 整除。我认为这就是造成麻烦的原因。
是的,我知道我可以在堆上分配 x 并选择它最终的位置以使其对齐。但是我在这里的部分实验是让 MyClass 的大小固定,所有数据都在里面(想想:缓存效率),所以我在堆上分配了 MyClass 的实例,在集合中指向它们的指针,x 在 MyClass 里面.
难道 align attribute 不应该将 x 放在 32 字节的边界上吗?编译器假设,然后 vmovapd 正在爆炸,因为它不是,对吧?
关于对齐的 GCC 文档:https://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html
我是否必须以某种方式在堆上对齐 MyClass?我怎么做?我如何告诉 GCC 我这样做了,所以它像我想要的那样矢量化?
编辑:我已经解决了这个问题(部分感谢下面的 cmets 和答案)。通过覆盖默认的new
运算符,可以保证在堆上创建对象时对齐。当我这样做时,我没有遇到段错误,并且我的代码仍然可以按照我的意愿完美地矢量化。我是怎么做到的:
static void* operator new(size_t size) throw (std::bad_alloc)
void *alignedPointer;
int alignError = 0;
// Try to allocate the required amount of memory (using POSIX standard aligned allocation)
alignError = posix_memalign(&alignedPointer, VECTOR_ALIGN_BYTES, size);
// Throw/Report error if any
if (alignError)
throw std::bad_alloc();
// Return a pointer to this aligned memory location
return alignedPointer;
static void operator delete(void* alignedPointer)
// POSIX aligned memory allocation can be freed normally with free()
free(alignedPointer);
C++ 在调用运算符之后/之前为您调用构造函数/析构函数。因此对齐是由类本身控制的。如果您有不同的偏好,还有其他对齐的内存分配器。我使用了 POSIX。
两个警告:如果有人使用任意地址调用placement new
,您仍然会不对齐。如果有人将你的类声明为他们类的成员,并且他们的类是在堆上分配的,那么你可能是未对齐的。我已经在我的构造函数中进行了检查,如果检测到,则会抛出错误。
【问题讨论】:
也尝试对齐MyClass
。非静态数据成员的对齐属性只能控制偏移量,不能控制内存中的绝对位置。
通过“对齐 MyClass”我假设你的意思是当我在堆上创建它时,我确保它在 32 字节的边界上?数据成员上的 align 属性会确保它的偏移量保持对齐吗?
不,我的意思是坚持__attribute__ ((aligned (32)))
class MyClass
。
@BenVoigt,这不起作用。我将 align 属性放在类声明中并再次尝试但没有更改。该对象是在“任何地方”的堆中创建的,但我仍然遇到段错误。如果对象“恰好”分配在 32 字节边界上,则该实例可以正常工作,但此 align 属性不能确保这一点。如果数组是成员并存储在对象中,我还没有找到一种完全在类内强制对齐的方法。我将尝试在初始化时强制对齐,但这意味着一个类无法控制自己的对齐,我认为这是一件坏事。
我错过了关于您使用new
使用堆(或免费存储)的部分。当然,您需要将堆上的内存与posix_memalign
之类的东西对齐(我使用了_mm_malloc
,因为它适用于GCC、MinGW、ICC 和MSVC)。但是对于静态分配和堆栈分配的数据,您应该只需要__attribute__ ((aligned (32)))
。
【参考方案1】:
__attribute__((aligned(32))
可能不会像我们认为的那样做(错误?功能?)。
它基本上告诉编译器它可以假设这个东西是对齐的,它可能不是。
如果它在堆上,则需要使用posix_memalign
或类似名称进行分配。
如果设置了 __attribute__((aligned(...))
但分配未对齐,则 GCC 实际上会导致指针运算错误。
s2->aligned_var = 0x199c030
&s2->aligned_var % 0x40 = 0x0
https://gcc.gnu.org/ml/gcc/2014-06/msg00308.html
【讨论】:
__builtin_assume_aligned
告诉 GCC 假设对齐。
"强制编译器确保(尽可能)类型为 struct S 或 more_aligned_int 的每个变量将被分配并至少在 8 字节边界上对齐。在 Sparc 上,具有所有 struct S 类型的变量与 8 字节边界对齐,允许编译器在将一个 struct S 类型的变量复制到另一个变量时使用 ldd 和 std(双字加载和存储)指令,从而提高运行时效率。” gcc.gnu.org/onlinedocs/gcc-3.1/gcc/Type-Attributes.html 但它似乎就像 __builtin_assume_aligned 一样工作。我称之为 gcc 错误...
我不知道我是否同意这个属性“基本上告诉编译器它可以假设”对齐,除非你的意思是“除了尝试对齐”成员。我认为它确实对齐了成员,但仅相对于对象的其余部分......至少如果对象是在堆上分配的。在我的情况下,我可以确认 x 始终是堆中对象地址的 32 个字节,而不管我在它前面声明的成员的排列方式。我认为除了“尝试”对齐成员之外,gcc 假设它成功地这样做了,这就是我遇到 seg 错误的原因。
是的,我说的是 gcc 的实际作用,并链接到 gcc 列表邮件,示例显示了它的实际作用。是的,它应该对齐成员(或者至少文档不应该建议这样做),但通常它不会对齐成员。堆上似乎没有保证。当它不对齐时,它的行为就好像它已经对齐并且假设对齐,即使它没有这样做并且 BOOM。 0x199c030 % 0x40 == 0x0 ?真的吗?
我未能正确拼出的含义(对不起)是它可能没有对齐内存,因为它会覆盖地址算术假设它在失败时已经这样做了 - 你不能 检查它是否失败。 attribute(aligned(32)) 可能不会像我们认为的那样做,或者文档所说的那样。 posix_memalign 或类似的(例如滚动你自己的)是必需的。以上是关于GCC __attribute__ 在 32 字节处对齐的 AVX 矢量化代码中的段错误的主要内容,如果未能解决你的问题,请参考以下文章
GCC 的 __attribute__((__packed__)) 是不是保留原始顺序?
GCC __attribute __((模式(XX))实际上做了什么?
GCC __attribute__((constructor)) 在对象构造函数之前调用
GCC的__attribute__ ((constructor))和__attribute__ ((destructor))