上证所,内在因素和调整

我用很多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 }; 

恩斯特提到,这个问题也可能是有问题的,但他只是链接到一个不存在的论坛,而没有解释为什么它可能是有问题的。

所以我的问题是:

  1. 定义操作符有什么问题?

  2. 为什么不将_MM_ALIGN16添加到类定义足够?

  3. 处理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字节,但不能保证。

    为了弥补,你必须重载内buildoperator new/delete来实现你自己的内存分配,并使用一个alignment的分配函数而不是老的malloc 。 重载operator new/delete本身就是一个主题,但起初看起来并不困难(尽pipe你的例子还不够),你可以在这个优秀的FAQ问题中阅读它。

    不幸的是,你必须为每个types的任何成员需要非标准的alignment,在你的情况下, SphereVector3 。 但是你可以做些简单的事情,就是为这些操作符创build一个空的基类,然后从这个基类中派生出所有需要的类。

    大多数人有时会忘记的是标准分配器std::alocator使用全局operator new来分配所有的内存,所以你的types将不能用于标准容器(而std::vector<Vector3>不是这样的罕见的用例)。 你需要做的是使自己的标准符合分配器,并使用它。 但是为了方便和安全,实际上更好的方法是为你的types专门化std::allocator (也许只是派生它自定义的分配器),这样它总是被使用,你不需要每次都使用合适的分配器你使用一个std::vector 。 不幸的是,在这种情况下,你必须再次针对每种alignment的types进行专门化,但是一个小小的邪恶的macros有助于这个。

    此外,你必须使用全局operator new/delete来代替自定义的其他东西,比如std::get_temporary_bufferstd::return_temporary_buffer ,并且关心那些必要的东西。

不幸的是,对于这些问题,我认为还没有一个更好的方法,除非你在一个本质上alignment到16的平台上并且知道这个问题 。 或者你可能只是重载全局operator new/delete以便始终将每个内存块alignment到16个字节,并且无需关心包含SSE成员的每个类的alignment方式,但我不知道这个含义做法。 在最坏的情况下,它只会导致浪费内存,但是通常不会在C ++中dynamic地分配小对象(尽pipestd::liststd::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要求。

这需要做以下的事情:

  1. 确保Vector3在堆栈或结构的成员上正确alignment。 这是通过将__attribute__((aligned(32)))应用于Vector3类(或编译器支持的任何属性)来完成的。 请注意,您不需要将属性应用于包含Vector3结构,这不是必需的,也不够(即不需要将其应用于Sphere )。

  2. 确保使用堆分配时, Vector3或其包围结构已正确alignment。 这是通过使用posix_memalign() (或类似的函数为您的平台),而不是使用普通malloc()operator new()因为后两个alignment内存types的内存(通常8或16字节)保证对于SIMDtypes是足够的。

  1. 运营商面临的问题是, 他们自己是不够的。 他们不影响堆栈分配,你仍然需要__declspec(align(16))

  2. __declspec(align(16))会影响编译器如何将对象放置在内存中,当且仅当它有select。 对于new'ed对象,编译器别无select,只能使用operator new返回的内存。

  3. 理想情况下,使用一个本地处理它们的编译器。 为什么他们需要不同于double对待,没有理论上的理由。 否则,请阅读编译器文档了解变通方法。 每个有障碍的编译器都会有自己的一套问题,因此也有自己的一套解决方法。