我的朋友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 Release | 21.7 |
香草Mono, mono Maths.exe | 6.6 |
带有LLVM和float32的香草单声道 | 15.5 |
在研究此问题的过程中,我们发现了两个问题,在更正之后,获得了以下结果:
工作环境 | 结果,MRay /秒 |
---|
具有LLVM和float32的单声道 | 15.5 |
具有LLVM,float32和固定内联的高级Mono | 29.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
,它们在整个行业中并不广泛可用(例如,
sinf
是
sin
的浮动版本,
fabsf
是
fabs
的版本,依此类推)。
简而言之,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仍会在此过程中执行从
float
到
double
float
转换。 例如,如果需要计算浮点值的正弦函数,则唯一的选择是调用
Math.Sin (double)
,并且必须将float转换为double。
为了解决这个问题,.NET Core中添加了一种新的
System.MathF
类型,其中包含单精度浮点数学运算,现在我们将
[System.MathF]
移植
到Mono上 。
从此表可以看出,从64位浮点到32位浮点的转换大大提高了性能。
工作环境和选择 | 射线/秒 |
---|
单声道与System.Math | 6.6 |
具有System.Math和-O=float32 单声道 | 8.1 |
单声道与System.MathF | 6.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=float32 | 16.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) 道歉, 感谢 我们对他的漂亮模特所做的一切。)