上证所,内在因素和调整
我用很多SSE编译器内在函数编写了一个3Dvector类。 一切正常,直到我开始安装具有3Dvector的类作为新成员。 我在发布模式下遇到了一些奇怪的崩溃,但是在debugging模式下却不是这样。
所以我读了一些文章,并认为我需要将拥有3Dvector类的实例的类也alignment到16字节。 所以我刚刚在类的前面添加了_MM_ALIGN16
( _MM_ALIGN16
( __declspec(align(16)
),如下所示:
_MM_ALIGN16 struct Sphere { // .... Vector3 point; float radius };
这似乎首先解决了这个问题。 但是在改变一些代码之后,我的程序又开始以奇怪的方式崩溃了。 我search了更多的networking,发现了一篇博客文章。 我尝试了作者恩斯特·霍特(Ernst Hot)为解决这个问题所做的工作。 我添加了新的和删除操作符到我的类,如下所示:
_MM_ALIGN16 struct Sphere { // .... void *operator new (unsigned int size) { return _mm_malloc(size, 16); } void operator delete (void *p) { _mm_free(p); } Vector3 point; float radius };
恩斯特提到,这个问题也可能是有问题的,但他只是链接到一个不存在的论坛,而没有解释为什么它可能是有问题的。
所以我的问题是:
-
定义操作符有什么问题?
-
为什么不将
_MM_ALIGN16
添加到类定义足够? -
处理SSE内在函数的alignment问题的最好方法是什么?
首先你必须关心两种types的内存分配:
-
静态分配。 为了使自动variables正确alignment,你的types需要一个合适的alignment规范(例如
__declspec(align(16))
,__attribute__((aligned(16)))
或_MM_ALIGN16
)。 但幸运的是,如果types成员(如果有的话)给出的alignment要求不够,你只需要这个。 所以你不需要这个Sphere
,因为你的Vector3
已经正确alignment了。 如果你的Vector3
包含一个__m128
成员(这很可能,否则我会build议这样做),那么你甚至不需要Vector3
。 所以你通常不必混淆编译器特定的alignment属性。 -
dynamic分配。 非常容易的部分。 问题在于,C ++在最底层使用了一个types不可知的内存分配函数来分配任何dynamic内存。 这只能保证所有标准types的正确alignment,这可能是16字节,但不能保证。
为了弥补,你必须重载内build
operator new/delete
来实现你自己的内存分配,并使用一个alignment的分配函数而不是老的malloc
。 重载operator new/delete
本身就是一个主题,但起初看起来并不困难(尽pipe你的例子还不够),你可以在这个优秀的FAQ问题中阅读它。不幸的是,你必须为每个types的任何成员需要非标准的alignment,在你的情况下,
Sphere
和Vector3
。 但是你可以做些简单的事情,就是为这些操作符创build一个空的基类,然后从这个基类中派生出所有需要的类。大多数人有时会忘记的是标准分配器
std::alocator
使用全局operator new
来分配所有的内存,所以你的types将不能用于标准容器(而std::vector<Vector3>
不是这样的罕见的用例)。 你需要做的是使自己的标准符合分配器,并使用它。 但是为了方便和安全,实际上更好的方法是为你的types专门化std::allocator
(也许只是派生它自定义的分配器),这样它总是被使用,你不需要每次都使用合适的分配器你使用一个std::vector
。 不幸的是,在这种情况下,你必须再次针对每种alignment的types进行专门化,但是一个小小的邪恶的macros有助于这个。此外,你必须使用全局
operator new/delete
来代替自定义的其他东西,比如std::get_temporary_buffer
和std::return_temporary_buffer
,并且关心那些必要的东西。
不幸的是,对于这些问题,我认为还没有一个更好的方法,除非你在一个本质上alignment到16的平台上并且知道这个问题 。 或者你可能只是重载全局operator new/delete
以便始终将每个内存块alignment到16个字节,并且无需关心包含SSE成员的每个类的alignment方式,但我不知道这个含义做法。 在最坏的情况下,它只会导致浪费内存,但是通常不会在C ++中dynamic地分配小对象(尽pipestd::list
和std::map
可能对此有不同的看法)。
所以总结一下:
-
使用诸如
__declspec(align(16))
类的东西来保持静态内存的正确alignment,但是只有在任何成员没有关心的情况下(通常是这种情况)。 -
重载
operator new/delete
对于每个具有非标准alignment要求的成员的types。 -
使一个cunstom符合标准的分配器在alignmenttypes的标准容器中使用,或者更好的是,为每个alignmenttypes专门设置
std::allocator
。
最后一些一般的build议。 当执行许多向量操作时,通常只有在计算量大的块中才能从SSE中获利。 为了简化所有这些alignment问题,特别是关心包含Vector3
每个types的alignment问题,做一个特殊的SSE向量types可能是一个很好的办法,只能在长时间的计算中使用它, -SSEvector存储和成员variables。
基本上,您需要确保向量正确alignment,因为SIMD向量types通常比任何内置types具有更大的alignment要求。
这需要做以下的事情:
-
确保
Vector3
在堆栈或结构的成员上正确alignment。 这是通过将__attribute__((aligned(32)))
应用于Vector3
类(或编译器支持的任何属性)来完成的。 请注意,您不需要将属性应用于包含Vector3
结构,这不是必需的,也不够(即不需要将其应用于Sphere
)。 -
确保使用堆分配时,
Vector3
或其包围结构已正确alignment。 这是通过使用posix_memalign()
(或类似的函数为您的平台),而不是使用普通malloc()
或operator new()
因为后两个alignment内存types的内存(通常8或16字节)保证对于SIMDtypes是足够的。
-
运营商面临的问题是, 他们自己是不够的。 他们不影响堆栈分配,你仍然需要
__declspec(align(16))
。 -
__declspec(align(16))
会影响编译器如何将对象放置在内存中,当且仅当它有select。 对于new'ed对象,编译器别无select,只能使用operator new
返回的内存。 -
理想情况下,使用一个本地处理它们的编译器。 为什么他们需要不同于
double
对待,没有理论上的理由。 否则,请阅读编译器文档了解变通方法。 每个有障碍的编译器都会有自己的一套问题,因此也有自己的一套解决方法。