我在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来说,这将是一个丑陋的设计。并且您无法在Vector3f本身上运行操作,但是在其数据上:(。
最令人不安的是,您必须在任何地方更改更高级别的代码以适应变化。而这种通过SSE优化的方式对于像大型游戏引擎这样大的东西来说绝对没有选择,你会在它运行之前改变大量的代码。
在没有避免SSE寄存器刷新的情况下,我认为它的功率将被无用的刷新命令耗尽,这使得SSE无用。
答案 0 :(得分:1)
在这里使用联盟似乎是件坏事。只要编译器看到__m128
与某些东西统一,就会在理解何时更新值时出现问题,从而导致过多的内存操作。
在这种情况下,MSVC不是性能最差的编译器。只需检查the code generated by GCC 5.1.0,它的工作速度比我的机器上的MSVC2013( 寄存器溢出)生成的代码慢12倍,比最佳代码慢20倍。
有趣的是,只有当您真正使用x
,y
,z
成员访问您的数据时,大多数编译器才会开始做愚蠢的事情。例如,MSVC2013只有在计算后通过标量成员读取它们时才会溢出寄存器(我想确保这些成员是实际的)。如果您使用_mm_setr_ps
设置初始值而不是直接将其写入成员,那么上面看到的GCC的可怕行为就会消失。
在这种情况下最好避免使用工会。看来OP已经做出同样的决定(见current Vector3fv code)。使访问单个坐标变得更加困难具有良好的“心理”表现效果:一个人在编写标量代码之前会三思而后行。您可以使用extract / insert内在函数(使编译器生成这些指令)或使用简单的指针算法(使编译器选择某种方式)轻松编写setter / getter:
float getX() const { return ((float*)&data)[0]; }
当我删除union并只使用__m128
时,生成的代码在所有编译器上变得更好。但是,MSVC2013仍然有不必要的移动:每个算术运算一个无用的寄存器移动。我想这是编译器内联算法的低效率。您可以通过将所有函数声明为__vectorcall来删除MSVC2013中的这些移动。请注意,使用这个新的调用约定还可以避免寄存器溢出,以防你的simd函数完全没有内联。