【发布时间】: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)))。