SSE,内在,和对齐 [英] SSE, intrinsics, and alignment

查看:621
本文介绍了SSE,内在,和对齐的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经写了使用了大量的SSE编译器内在函数的三维矢量类。一切工作正常,直到我开始instatiate具有三维矢量作为新成员的类。我在释放模式,但不是在调试模式和其他方式经历了奇怪的崩溃。

所以,我读了一些文章,想我需要拥有一致的三维矢量类的实例类16个字节太。于是我就加入 _MM_ALIGN16 __ declspec(对齐(16))在班前,像这样:

  _MM_ALIGN16结构球
{
    // ....

    Vector3类型点;
    浮动范围
};
 

这似乎在首先要解决的问题。但是,改变一些$ C C我的程序$后开始再次崩溃以奇怪的方式。我在网上搜索一些和发现了一个博客文章。我试了一下作者,恩斯特热,确实解决问题和它的作品对我来说太。我增加了新的和delete操作符来我的班是这样的:

  _MM_ALIGN16结构球
{
    // ....

    无效*运营商新的(无符号整数大小)
     {返回_mm_malloc(尺寸,16); }

    无效的operator delete(无效* P)
     {_mm_free(对); }

    Vector3类型点;
    浮动范围
};
 

安永提到,这个形式给出可能是有问题的为好,但他只是链接到它不没有解释为什么它可能会有问题存在了一个论坛。

所以,我的问题是:

  1. 有什么问题,确定经营者?

  2. 为什么不加入 _MM_ALIGN16 类定义就够了吗?

  3. 什么是处理对齐问题,上证所内部函数未来的最好方法是什么?

解决方案

首先,你必须关心两种类型的内存分配的:

  • 静态分配。对于自动变量被正确对齐,你的类型需要一个合适的定位标准(例如 __ declspec(对齐(16)) __属性__((排列(16 ))),或 _MM_ALIGN16 )。但幸运的是,你只需要这个,如果按类型的成员给出的对齐要求(如果有的话)是不够的。所以,你不需要为你,因为你的 Vector3类型已正确对齐。如果你的 Vector3类型包含一个 __ M128 成员(这是pretty的可能,否则,我会建议这样做),那么你甚至都不需要它 Vector3类型。所以,你通常不会有乱用编译器的具体调整的属性。

  • 动态分配。这么多的容易的部分。的问题是,C ++使用,在最低水平,一个相当键入不可知内存分配功能用于分配任何动态存储器。这不仅保证了适当的调整对所有标准类型,这可能正好是16个字节,但不能保证。

    有关此赔偿你必须重载内建运营商新/删除来实现自己的内存分配和使用油烟机,而不是好老下对齐分配函数的malloc 。重载运营商新/删除是对自己的一个话题,但并不难,因为它似乎在第一(虽然你的例子是不够的),你可以阅读它在这个优秀的常见问题解答问题

    不幸的是,你必须这样做对每种类型有任何成员需要非标准的调整,你的情况这两种 Vector3类型。但是,你能做些什么,使之更容易一点就是做适当的过载空基类的运营商,然后就得出所有neccessary类从这个基类。

    大多数人有时往往忘记的是,该标准分配器的std :: alocator 使用全局运营商新的所有的内存分配,让你的类型将无法与标准集装箱的工作(和的std ::矢量< Vector3类型> 不是罕见的用例)。什么,你需要做的就是让自己的标准符合性分配和使用。但是,为了方便和安全这实际上是最好只专注的std ::分配器为你的类型(也许只是派生它形成你的自定义分配器),因此它总是用你不需要关心使用正确的分配器每次使用的std ::矢量时间。不幸的是在这种情况下,你必须再次专门为它对准每一个类型,但小恶宏有助于这一点。

    此外,你必须留意使用全局其他事情的operator new /删除,而不是你自定义的,如的std :: get_temporary_buffer 的std :: return_temporary_buffer ,并照顾那些如果neccessary。

不幸的是,还没有一个更好的方法对这些问题,我想,除非你是能够自然地对齐到16 一个平台,并了解这个。或者,你可能只是超载全球运营商新/删除来始终对准每个存储块为16个字节,且无照顾每个对准和包含SSE每一个类成员,但我不知道这种方法的影响。在最坏的情况下,它应该只是导致浪费内存,但话又说回来,你通常不会动态分配在C中的小物件++(尽管的std ::列表的std ::地图可能不同的角度思考这一点)。

所以,总结一下:

  • 关爱的使用像 __ declspec静态存储器正确对齐(对齐(16)),但只有当它尚未被任何成员的照顾,这通常是这样的。

  • 超载运营商新/删除每个具有非标准对齐要求成员每一种类型。

  • 请一个cunstom符合标准的分配器在排列类型的标准容器使用,或者更好的,专门的std ::分配器为每一个对准类型


最后一些一般性的建议。通常你只有利润的形式执行许多向量运算时,上证所在计算重块。为了简化这一切的对齐问题,关爱每一个路线和每个包含 Vector3类型类型的特别问题,这可能是一个很好的形式给出作出特别SSE矢量型只有使用冗长的计算这里面,使用普通的非SSE向量进行存储和成员变量。

I've written a 3D vector class using a lot of SSE compiler intrinsics. Everything worked fine until I started to instatiate classes having the 3D vector as a member with new. I experienced odd crashes in release mode but not in debug mode and the other way around.

So I read some articles and figured I need to align the classes owning an instance of the 3D vector class to 16 bytes too. So I just added _MM_ALIGN16 (__declspec(align(16)) in front of the classes like so:

_MM_ALIGN16 struct Sphere
{
    // ....

    Vector3 point;
    float radius
};

That seemed to solve the issue at first. But after changing some code my program started to crash in odd ways again. I searched the web some more and found a blog article. I tried what the author, Ernst Hot, did to solve the problem and it works for me too. I added new and delete operators to my classes like this:

_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
};

Ernst mentions that this aproach could be problematic as well, but he just links to a forum which does not exist anymore without explaining why it could be problematic.

So my questions are:

  1. What's the problem with defining the operators?

  2. Why isn't adding _MM_ALIGN16 to the class definition enough?

  3. What's the best way to handle the alignment issues coming with SSE intrinsics?

解决方案

First of all you have to care for two types of memory allocation:

  • Static allocation. For automatic variables to be properly aligned, your type needs a proper alignment specification (e.g. __declspec(align(16)), __attribute__((aligned(16))), or your _MM_ALIGN16). But fortunately you only need this if the alignment requirements given by the type's members (if any) are not sufficient. So you don't need this for you Sphere, given that your Vector3 is already aligned properly. And if your Vector3 contains an __m128 member (which is pretty likely, otherwise I would suggest to do so), then you don't even need it for Vector3. So you usually don't have to mess with compiler specific alignment attributes.

  • Dynamic allocation. So much for the easy part. The problem is, that C++ uses, on the lowest level, a rather type-agnostic memory allocation function for allocating any dynamic memory. This only guarantees proper alignment for all standard types, which might happen to be 16 bytes but isn't guaranteed to.

    For this to compensate you have to overload the builtin operator new/delete to implement your own memory allocation and use an aligned allocation function under the hood instead of good old malloc. Overloading operator new/delete is a topic on its own, but isn't that difficult as it might seem at first (though your example is not enough) and you can read about it in this excellent FAQ question.

    Unfortunately you have to do this for each type that has any member needing non-standard alignment, in your case both Sphere and Vector3. But what you can do to make it a bit easier is just make an empty base class with proper overloads for those operators and then just derive all neccessary classes from this base class.

    What most people sometimes tend to forget is that the standard allocator std::alocator uses the global operator new for all memory allocation, so your types won't work with standard containers (and a std::vector<Vector3> isn't that rare a use case). What you need to do is make your own standard conformant allocator and use this. But for convenience and safety it is actually better to just specialize std::allocator for your type (maybe just deriving it form your custom allocator) so that it is always used and you don't need to care for using the proper allocator each time you use a std::vector. Unfortunately in this case you have to again specialize it for each aligned type, but a small evil macro helps with that.

    Additionally you have to look out for other things using the global operator new/delete instead of your custom one, like std::get_temporary_buffer and std::return_temporary_buffer, and care for those if neccessary.

Unfortunately there isn't yet a much better approach to those problems, I think, unless you are on a platform that natively aligns to 16 and know about this. Or you might just overload the global operator new/delete to always align each memory block to 16 bytes and be free of caring for the alignment of each and every single class containing an SSE member, but I don't know about the implications of this approach. In the worst case it should just result in wasting memory, but then again you usually don't allocate small objects dynamically in C++ (though std::list and std::map might think differently about this).

So to sum up:

  • Care for proper alignment of static memory using things like __declspec(align(16)), but only if it is not already cared for by any member, which is usually the case.

  • Overload operator new/delete for each and every type having a member with non-standard alignment requirements.

  • Make a cunstom standard-conformant allocator to use in standard containers of aligned types, or better yet, specialize std::allocator for each and every aligned type.


Finally some general advice. Often you only profit form SSE in computation-heavy blocks when performing many vector operations. To simplify all this alignment problems, especially the problems of caring for the alignment of each and every type containing a Vector3, it might be a good aproach to make a special SSE vector type and only use this inside of lengthy computations, using a normal non-SSE vector for storage and member variables.

这篇关于SSE,内在,和对齐的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆