我们如何在Mono中使用Float的速度提高一倍


我的朋友Aras 最近用不同的语言(包括C ++,C#和Unity Burst编译器) 编写了相同的光线跟踪器。 当然,很自然地期望C#会比C ++慢,但是对我来说,Mono的速度比.NET Core慢是很有趣的。

他发表的指标很差:

  • C#(。NET Core):Mac 17.5 Mray / s,
  • C#(Unity,Mono):Mac 4.6 Mray / s,
  • C#(Unity,IL2CPP):Mac 17.1 Mray / s

我决定看看发生了什么,并记录可以改进的地方。

作为此基准测试和研究此问题的结果,我们发现了可以改进的三个方面:

  • 首先,您需要改进默认的Mono设置,因为用户通常不会配置其设置
  • 其次,我们需要积极地向世界介绍Mono中LLVM代码优化的后端
  • 第三,我们改进了一些Mono参数的调整。

该测试的参考点是在计算机上运行的光线跟踪器的结果,并且由于我使用的硬件不同,因此无法比较这些数字。

在我的家用iMac上,用于Mono和.NET Core的结果如下:

工作环境结果,MRay /秒
.NET Core 2.1.4, dotnet run调试版本3.6
.NET Core 2.1.4发行版构建dotnet run -c Release21.7
香草Mono, mono Maths.exe6.6
带有LLVM和float32的香草单声道15.5

在研究此问题的过程中,我们发现了两个问题,在更正之后,获得了以下结果:

工作环境结果,MRay /秒
具有LLVM和float32的单声道15.5
具有LLVM,float32和固定内联的高级Mono29.6

大图:


仅通过应用LLVM和float32,就可以将浮点代码的性能提高近2.3倍。 经过调整(我们将这些实验添加到Mono中),与标准Mono相比,您可以将生产率提高4.4倍-将来版本的Mono中的这些参数将成为默认参数。

在本文中,我将解释我们的发现。

32位和64位Float


Aras在计算的主要部分中使用32位浮点数(在C#中为float类型,在.NET中为System.Single类型)。 在Mono中,我们很早以前就犯了一个错误-所有32位浮点计算都以64位执行,并且数据仍存储在32位区域中。

今天,我的记忆不如从前,而且我不记得我们为什么做出这样的决定。

我只能假设它受到当时趋势和思想的影响。

然后,浮点计算周围会出现一个正向光环,且精度提高了。 例如,即使x是双操作数,Intel x87处理器也使用80位精度进行浮点计算,从而为用户提供了更准确的结果。

当时,这个想法也很有意义,那就是在我以前的一个项目-数值电子表格中-统计功能比Excel中更有效地实现。 因此,许多社区都意识到可以使用精度更高,结果更准确的结果。

在Mono开发的初始阶段,在所有平台上执行的大多数数学运算在输入时只能获得双倍的输入。 C99,Posix和ISO中添加了32位版本,但在fabsf ,它们在整个行业中并不广泛可用(例如, sinfsin的浮动版本, fabsffabs的版本,依此类推)。

简而言之,2000年代初是乐观的时期。

应用程序为增加计算时间付出了沉重的代价,但是Mono主要用于为HTTP页面和某些服务器进程提供服务的桌面Linux应用程序,因此浮点速度并不是我们每天遇到的问题。 它仅在某些科学基准中变得引人注目,并且在2003年很少在.NET上进行开发。

如今,游戏,3D应用程序,图像处理,VR,AR和机器学习已使浮点运算成为一种更常见的数据类型。 麻烦并不孤单,也没有例外。 仅在几个地方,Float不再是代码中使用的友好数据类型。 他们变成了雪崩,无处可藏。 它们很多,它们的传播无法停止。

工作区标志float32


因此,几年前,我们决定增加对使用32位运算执行32位浮点运算的支持,就像在所有其他情况下一样。 我们将工作空间的此功能称为“ float32”。 在Mono中,可以通过在工作环境中添加选项--O=float32来启用它,而在Xamarin应用程序中,可以在项目设置中更改此参数。

我们的移动用户很好地接受了这个新标记,因为基本上移动设备仍然没有太大的功能,并且与提高准确性相比,它们更适合处理速度更快的数据。 我们建议移动用户同时打开LLVM优化编译器和float32标志。

尽管此标志已实施了好几年,但我们并未将其设置为默认标志,以免给用户带来不愉快的惊喜。 但是,我们开始遇到由于标准的64位行为而引起意外的情况,请参见Unity用户提交的错误报告

现在我们将float32使用Mono float32 ,可以在这里跟踪进度: https : //github.com/mono/mono/issues/6985

同时,我回到了朋友阿拉斯的项目中。 他使用了添加到.NET Core的新API。 尽管.NET Core始终将32位浮点运算作为32位浮点执行,但是System.Math API仍会在此过程中执行从floatdouble float转换。 例如,如果需要计算浮点值的正弦函数,则唯一的选择是调用Math.Sin (double) ,并且必须将float转换为double。

为了解决这个问题,.NET Core中添加了一种新的System.MathF类型,其中包含单精度浮点数学运算,现在我们将[System.MathF]移植到Mono上

从此表可以看出,从64位浮点到32位浮点的转换大大提高了性能。

工作环境和选择射线/秒
单声道与System.Math6.6
具有System.Math和-O=float32单声道8.1
单声道与System.MathF6.5
具有System.MathF和-O=float32单声道8.2

也就是说,在此测试中使用float32确实可以提高性能,而MathF效果不大。

LLVM设置


在研究过程中,我们发现尽管Fast JIT Mono编译器具有float32支持,但我们并未将此支持添加到LLVM后端。 这意味着具有LLVM的Mono仍在执行从float到double的昂贵转换。

因此,Zoltan向LLVM代码生成引擎添加了float32支持。

然后他注意到我们的内线对Fast JIT使用与LLVM相同的启发式方法。 使用Fast JIT时,必须在JIT速度和执行速度之间取得平衡,因此我们限制了嵌入式代码的数量,以减少JIT引擎的工作量。

但是,如果您决定在Mono中使用LLVM,则您将尽力争取代码,因此我们相应地更改了设置。 如今,可以使用MONO_INLINELIMIT环境MONO_INLINELIMIT更改此参数,但实际上需要将其写入默认值。

以下是修改后的LLVM设置的结果:

工作环境和选择射线/秒
具有System.Math的Mono --llvm -O=float3216.0
具有System.Math的Mono --llvm -O=float32 ,恒定启发式29.1
Mono与System.MathF --llvm -O=float32 ,恒定启发式29.6

后续步骤


只需很少的努力即可完成所有这些改进。 这些更改是由Slack的定期讨论引起的。 我什至一天晚上设法花了几个小时将System.MathF移植到Mono。

Aras射线跟踪代码已成为理想的研究对象,因为它是自给自足的,是真正的应用程序,而不是综合基准。 我们希望找到其他可用于研究生成的二进制代码的类似软件,并确保将最佳工作数据传递给LLVM,以实现其最佳工作。

我们还在考虑更新LLVM,并使用新添加的优化。

单独的笔记


额外的精度有很好的副作用。 例如,在阅读Godot引擎的池请求时,我看到了关于是否在编译时自定义浮点运算的准确性的积极讨论( https://github.com/godotengine/godot/pull/17134 )。

我问胡安(Juan)为什么对某人来说这是必要的,因为我认为32位浮点运算对于游戏来说已经足够了。

胡安(Juan)解释说,在一般情况下,浮动效果很好,但是如果您“偏离”中心(例如,距游戏中心100公里),则会开始累积计算错误,这可能会导致有趣的图形故障。 您可以使用不同的策略来减少此问题的影响,其中之一就是提高准确性,您必须为此付出代价。

谈话后不久,在我的Twitter提要中,我看到了一篇说明该问题的文章: http : //pharr.org/matt/blog/2018/03/02/rendering-in-camera-space.html

下图显示了该问题。 在这里,我们看到了 pbrt-v3-scenes ** 包中的跑车模型 相机和场景都在原点附近,一切看起来都很棒。


** 森靖靖的 作者 。)

然后,我们将摄像机和场景从原点移到xx,yy和zz处200,000个单位。 可以看出,机器的模型已经变得非常分散。 这完全是由于浮点数缺乏精度。


如果我们从原点再移动5×5×5次,即100万个单位,则模型将开始分解。 机器本身变成了非常粗糙的体素近似,既有趣又可怕。 (基努问了一个问题:《我的世界》之所以那么立方仅仅是因为所有东西都被渲染得离原点很远吗?)


** (我向 森靖俊 Yasutoshi Mori) 道歉, 感谢 我们对他的漂亮模特所做的一切。)

Source: https://habr.com/ru/post/zh-CN432176/


All Articles