如何避免 SSE 管道刷新?

Posted

技术标签:

【中文标题】如何避免 SSE 管道刷新?【英文标题】:How to avoid SSE pipeline flush? 【发布时间】:2015-07-09 15:43:28 【问题描述】:

我在 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无用。

【问题讨论】:

@JerryCao1985 您可以通过编辑直接在问题中提及这一点,而不是将其添加为评论。 如果您认真对待 SIMD 优化,那么您需要准备好完全重构您的代码,以便您可以完全在 SIMD 中均匀地处理大块数据。正如您在上面的示例中已经看到的那样,尝试以特别的方式将 SIMD 应用于现有代码库通常是次优的。 IOW:你不能一边吃蛋糕一边吃。 它绝对大型游戏引擎的一个选项,是的,SIMD 入侵并试图“污染”你的所有代码。随它吧。只需从一开始就牢记这一点来编写代码,就可以了。 fast-dot-product-using-sse-avx-intrinsics. 【参考方案1】:

似乎在这里使用 union 是一件坏事。只要编译器看到__m128 与某些东西统一,它就无法理解何时更新值,从而导致过多的内存操作。

MSVC 在这种情况下并不是性能最差的编译器。只需检查the code generated by GCC 5.1.0,它的运行速度比我机器上由 MSVC2013 生成的代码(with 寄存器溢出)慢 12 倍,比最佳代码慢 20 倍以上。

有趣的是,大多数编译器只有在您真正使用xyz 成员来访问您的数据时才开始做傻事。例如,MSVC2013 只有在计算后通过标量成员读取寄存器时才会溢出寄存器(我想确保这些成员是实际的)。如果您使用_mm_setr_ps 设置初始值而不是直接将它们写入成员,那么上面看到的 GCC 的可怕行为就会消失。

在这种情况下最好避免联合。看来 OP 也做出了同样的决定(见current Vector3fv code)。使访问单个坐标变得更加困难具有良好的“心理”性能效果:一个人在编写标量代码之前会三思而后行。您可以使用提取/插入内在函数(使编译器生成这些指令)或简单的指针算法(使编译器选择某种方式)轻松编写 setter/getter:

float getX() const  return ((float*)&data)[0]; 

当我删除 union 并简单地使用 __m128 时,生成的代码在所有编译器上都会变得更好。但是,MSVC2013 仍然有不必要的移动:每个算术运算一次无用的寄存器移动。我想这是编译器的内联算法效率低下。您可以通过将所有函数声明为 __vectorcall 来删除 MSVC2013 中的这些移动。请注意,使用这个新的调用约定还可以让您避免寄存器溢出,以防您的 simd 函数根本没有内联。

【讨论】:

以上是关于如何避免 SSE 管道刷新?的主要内容,如果未能解决你的问题,请参考以下文章

如何避免页面刷新按钮事件

如何刷新图形以避免黑屏?

php 如何避免刷新页面重复插入数据到数据库

如何避免在引导模式中刷新页面?

Firestore + Vue:刷新页面时,如何避免数据显示延迟?

如何避免在asp.net中的按钮单击事件后页面刷新