我使用大量 SSE 编译器内在函数编写了一个 3D 矢量类。一切都工作正常,直到我开始使用 new 来实例化具有 3D 向量作为成员的类。我在发布模式下经历了奇怪的崩溃,但在调试模式下却没有,反之亦然。
因此,我阅读了一些文章,并认为我需要将拥有 3D 矢量类实例的类也对齐到 16 字节。所以我刚刚添加了_MM_ALIGN16
(__declspec(align(16)
)在类前面,如下所示:
_MM_ALIGN16 struct Sphere
{
// ....
Vector3 point;
float radius
};
起初这似乎解决了问题。但是在更改了一些代码之后,我的程序再次开始以奇怪的方式崩溃。我又在网上搜索了一下,发现了一个blog http://ernsthot.blogspot.de/2009/03/diving-into-sse-alignment-issues.html文章。我尝试了作者 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 内在函数带来的对齐问题的最佳方法是什么?
首先,您必须关心两种类型的内存分配:
静态分配。为了使自动变量正确对齐,您的类型需要正确的对齐规范(例如__declspec(align(16))
, __attribute__((aligned(16)))
,或者你的_MM_ALIGN16
)。但幸运的是,只有当类型成员(如果有)给出的对齐要求不充分时,您才需要这样做。所以你不需要这个Sphere
,鉴于你的Vector3
已经正确对齐。如果你的Vector3
包含一个__m128
成员(这很可能,否则我建议这样做),那么你甚至不需要它Vector3
。因此,您通常不必弄乱编译器特定的对齐属性。
-
动态分配。简单的部分就讲这么多。问题是,C++ 在最低级别上使用与类型无关的内存分配函数来分配任何动态内存。这仅保证所有标准类型的正确对齐,这些标准类型可能恰好是 16 字节,但不能保证。
为了弥补这一点,你必须重载内置的operator new/delete
实现您自己的内存分配并在后台使用对齐的分配函数而不是旧的malloc
。超载operator new/delete
本身就是一个主题,但并不像乍一看那么困难(尽管您的示例还不够),您可以在这个优秀的常见问题解答 https://stackoverflow.com/q/7194127/743214.
不幸的是,您必须为具有任何需要非标准对齐的成员的每种类型执行此操作,在您的情况下Sphere
and Vector3
。但是,为了使其变得更容易,您可以做的就是为这些运算符创建一个具有适当重载的空基类,然后从该基类派生所有必要的类。
大多数人有时容易忘记的是标准分配器std::alocator
使用全局的operator new
对于所有内存分配,因此您的类型无法与标准容器(以及std::vector<Vector3>
这种用例并不罕见)。您需要做的是创建自己的标准一致分配器并使用它。但为了方便和安全,实际上更好的是专注于std::allocator
对于您的类型(也许只是从您的自定义分配器派生它),以便始终使用它,并且您不需要每次使用时都关心使用正确的分配器std::vector
。不幸的是,在这种情况下,您必须再次针对每种对齐类型专门化它,但是一个小的邪恶宏可以帮助解决这个问题。
此外,您还必须注意使用全局的其他事情operator new/delete
而不是您自定义的,例如std::get_temporary_buffer
and std::return_temporary_buffer
,并在必要时照顾这些人。
不幸的是,我认为还没有更好的方法来解决这些问题,除非您使用的平台本身就与 16 对齐并了解这一点。或者你可能只是让全局超载operator new/delete
始终将每个内存块对齐到 16 字节,并且无需关心包含 SSE 成员的每个类的对齐,但我不知道这种方法的含义。在最坏的情况下,它只会导致内存浪费,但话又说回来,您通常不会在 C++ 中动态分配小对象(尽管std::list
and std::map
可能对此有不同的看法)。
总结一下:
使用以下方法来注意静态内存的正确对齐__declspec(align(16))
,但前提是它还没有被任何成员照顾(通常是这种情况)。
超载operator new/delete
对于每种类型都有一个具有非标准对齐要求的成员。
制作一个自定义的符合标准的分配器以在对齐类型的标准容器中使用,或者更好的是,专门化std::allocator
对于每个对齐类型。
最后一些一般性建议。通常,在执行许多向量运算时,您只能从计算量大的块中的 SSE 中获益。为了简化所有这些对齐问题,特别是照顾包含一个的每个类型的对齐问题Vector3
,这可能是一个好方法,创建一个特殊的 SSE 向量类型,并且仅在冗长的计算中使用它,使用普通的非 SSE 向量来存储和成员变量。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)