我在 SSE 上遇到了一个非常微妙的问题。情况是这样的,我想用 SSE 优化我的光线追踪器,这样我就可以基本了解如何使用 SSE 提高性能。
我想从这个函数开始。
Vector3f Add( const Vector3f& v0 , Vector3f& v1 );
(实际上,我首先尝试优化 CrossProduct,为简单起见,此处显示了添加,我知道这不是我的光线追踪器的瓶颈。)
这是结构体定义的一部分:
struct Vector3f
{ union { struct{ float x ; float y ; float z; float reserved; }; __m128 data; };
问题是这个声明会刷新 SSE 寄存器,编译器不够智能,无法保留这些 sse 寄存器以供进一步使用。 并通过以下声明避免刷新。
__m128 Add( __m128 v0_data, __m128 v1_data );
我可以在这种情况下采用这种方式,但是对于包含四个 __m128 数据的 Matrix 来说,这将是丑陋的设计。你不能让运算符(operator)在 Vector3f 本身上工作,而是在它的数据上工作,:(。
最令人不安的是,您将不得不到处更改更高级别的代码以适应更改。而这种通过 SSE 进行优化的方式对于像大型游戏引擎这样的大型项目来说绝对不是一个选择,你将在它工作之前更改大量代码。
如果不避免 SSE 寄存器刷新,它的功率将被那些使 SSE 变得无用的无用刷新命令耗尽,我猜。
最佳答案
看来union用在这里是个坏东西。只要编译器看到 __m128
与某些东西统一,它就无法理解何时更新值,从而导致过多的内存操作。
在这种情况下,MSVC 并不是性能最差的编译器。只需检查 the code generated by GCC 5.1.0 ,它比我机器上 MSVC2013 生成的代码( 寄存器溢出)慢 12 倍,比最佳代码慢 20 多倍。
有趣的是,大多数编译器只有在您真正使用 x
、y
、z
成员访问您的数据时才开始做傻事。例如,只有当您在计算后通过标量成员读取寄存器时,MSVC2013 才会溢出寄存器(我想确保这些成员是真实的)。如果您使用 _mm_setr_ps
设置初始值而不是将它们直接写入成员中,那么上述 GCC 的可怕行为就会消失。
在这种情况下最好避免 union 。 OP 似乎做出了同样的决定(参见 current Vector3fv code )。使访问单个坐标变得更难具有良好的“心理”性能效果:一个人在编写标量代码之前会三思而后行。您可以使用提取/插入内在函数(这使编译器生成这些指令)或使用简单的指针算法(这使编译器选择某种方式)轻松编写 setter/getter:
float getX() const { return ((float*)&data)[0]; }
当我删除 union 并简单地使用 __m128
时,生成的代码在所有编译器上都会变得更好。然而,MSVC2013 仍然有不必要的移动:每次算术运算一个无用的寄存器移动。我想这是编译器内联算法的低效率。您可以通过将所有函数声明为 __vectorcall 来删除 MSVC2013 中的这些移动.请注意,如果您的 simd 函数根本没有内联,使用这个新的调用约定还可以避免寄存器溢出。
关于c++ - 如何避免 SSE 管道冲洗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31322279/