Unity的未来,是固守Mono,还是拥抱CoreCLR?
Mono vs CoreCLR对于一个C#的初学者,首先要了解的便是.NET和C#的关系。所以这里不再赘述。对于一个Unity的初学者,在使用C#编码的过程中,一定会遇到一些C#新特性不能在项目中使用的情况,这是因为微软官方提供的.NET运行时环境(最新版为 .NET 6 的 CoreCLR)远比Unity集成的Mono强大。由于历史原因,Unity一直未能使用最新的.NET运行时。本文就来细说一下其中的历史,以及Unity未来的发展。
首先,Mono是.NET的开源实现,由Xamarin牵头维护 mono/mono 这个repo。2016年Xamarin被微软收购,将其license从GPL改成了MIT,同时微软也参与到Mono的开发中。在微软的 dotnet/runtime 这个repo中,可以发现mono,但这并不是mono/mono的替代品,而是微软为了方便其他组件开发,将部分代码拷贝过来进行魔改,在需要的时候同步回mono/mono。(出处: Announcement: Consolidating .NET)
然后说一下 CoreCLR。微软改名部绝非浪得虚名,这些年的一系列改名操作把好端端的.NET技术搅成一滩浑水。非常简要的说,过去.NET运行时只能用于Windows平台,名为 .NET Framework,运行时叫CLR,大名鼎鼎的《CLR via C#》就是基于该运行时。后来微软决定将.NET技术开源,并彻底地跨平台(Windows,Linux,MacOS等),顺便大幅提高运行效率并抛弃一些旧组件,重写了一版 .NET Core,运行时叫 CoreCLR。双线开发并不是个好主意,因此再后来微软决定将前者废弃,以 .NET Core相关技术为核心,将.NET技术大一统(桌面开发、移动开发、游戏开发、IoT开发、云开发等等),史称 .NET 5。因此,往后.NET的官方运行时就叫CoreCLR(暂定 )。repo在这里:dotnet/runtime。
大饼是画出来了,但当下移动平台的.NET开发的主流还是Mono。然而Mono对.NET的特性支持通常落后于微软官方的 .NET运行时。这里结合Wiki,对Mono的重要历史加以整理。
2010年9月,Mono2.8发布,带来了新的分代式GC:SGen;支持 C#4;支持 .NET 4.0。2013年7月,Mono3.2发布,SGen取代Boehm成为默认的GC。2015年4月,Mono4.0发布,开始集成 .NET Core;支持 C#6。2017年5月,Mono5.0发布,开启了concurrent SGen;使用Roslyn编译器;支持 C#7。2019年9月,Mono6.4发布,支持 .NET Standard 2.1;支持 .NET 4.8。
过去,Unity 选择 Mono
上文理清了Mono 和CoreCLR的关系,下面说说Unity在这二者间的选择。
早在2008年,Unity就宣布和Mono合作,但后续Mono新版本使用SGen GC取代Boehm GC时,Unity不想再次付许可证费用。直到当下(2021年7月),Unity依然依赖 Boehm GC。这是一种没有分代的(扫描慢)、Mark-Sweep的(会有内存碎片问题)、保守(不能精确地识别垃圾)的GC。
Unity has been and is still relying on the Boehm GC, which is a conservative (stack-root) GC. The link above doesn't go into some details like how managed objects on the stack are collected by the GC, but basically: a conservative GC will scan the entire stack of all managed threads to "pin" memory referenced by it. Because of this blind scan, it can bring false pin, because it can interpret an integer value, as a pointer to a region of the heap memory, while it was really an int in the first place. By doing so, a conservative GC can start to block some objects from being collected (or worse for a moving-generational GC, to relocate objects). Otoh, a precise GC is able to scan precisely stack-roots and report only pointers that actually point to heap memory. In order for a precise GC to work, the (JIT) codegen needs to be GC aware, which is the case for CoreCLR.对于Boehm GC造成的性能问题,Unity官方有一些折中方案。
先尽量分配好所有对象的内存,然后关闭GC,等到合适的时机(如关卡结束),再开启GC;默认开启 incremental 模式分帧处理,注意如果在期间有大量引用关系的改写,分帧处理反而会有大量额外性能损耗(主要来自写屏障)
未来,Unity 会选择 CoreCLR 吗
自2016年微软将Mono的许可证由GPL改为MIT以来,Unity也加入了 .NET Foundation,开始将最新的Mono集成到自己的引擎中。但随着微软构筑开源的大一统解决方案 .NET 5,Unity似乎改变了原先的想法。从官方论坛中可以总结出他们的规划:
首先集成最新的Mono,因为其支持 .NET Core 的BCL;然后将自家的 IL2CPP 也更新(其依赖Mono的输出结果);支持 .NET Standard 2.1(特别是Span,Range),虽然Unity此时依然基于 .NET Framework,但利用最新的Mono可以不依赖 .NET Framework实现;支持 C#8/9,基于前面的工作,这一步并不难;支持 .NET 6(跳过 .NET 5)。但有两大难题:
所有dll必须重新编译; 要修改 UnityEditor 中大量使用 AppDomain进行hot reload的部分(AppDomain在新版.NET中几乎被废弃,出处);目前的替代类 AssemblyLoadContext 并不能提供之前 AppDomain Reload的所有功能。
In general Assembly Load Context is cooperative, and any remaining references (static fields, GC Handles, running threads, etc) will prevent the code from being unloaded.6. 未来可能用CoreCLR替代Mono。Unity大部分代码是C++,C#只有薄薄的一层(但是越来越多的代码在切换到 C#)。在C#运行时切换到CoreCLR后,其访问Managed Object的方式需要彻底改变,因此改动会很大、持续很久,目前没有ETA。参考
总之,Unity团队还有很多高优先级的feature要做,希望Unity越来越好。最新消息可关注官方论坛的讨论:
另外,文中提到的《CLR via C#》虽然内容基于 .NET Framework,但由于大部分内容在 .NET5 中并没有变化,该书凭借其内容深入和广度,依然是 C# 进阶学习的必读经典。 这个话题的官方讨论都在这里:https://forum.unity.com/threads/unity-future-net-development-status.1092205 对,Unity 相关的讨论是在这里,文中倒数第二个链接也提到了 切换到 core clr还是挺麻烦的,core clr本身也还没完善,还没被.net社区完全接受,所以这事情不急。 不是Unity不愿意,而是官方的.net il2cpp这块太拉夸,从完成度来说远远不如Unity现有的方案。虽然Unity的方案确实也存在很多问题,但是人家毕竟是能够商用的完整产品。 gc这块 官方的gc也不如jvm的zgc。虽然我不喜欢jvm平台,但是jvm平台的新的zgc确实不错。 zgc确实是标杆。.NET GC市面上资料不多,而且CoreCLR里的 gc.cpp 单文件3w多行,根本没法看。如果能接受外界PR了,相信会变得更强。
CoreCLR GC 源码: https://github.com/dotnet/runtime/tree/main/src/coreclr/gc
个人推荐微软CLR runtime 组的 Maoni.S 关于GC的公开doc:
https://github.com/Maoni0/mem-doc/blob/master/doc/.NETMemoryPerformanceAnalysis.md
以及MSDN上的公开doc:
https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals
结合《Pro .NET Memory Management》《CLR via C#》应该能把 .NET GC 熟悉的差不多。 微软的vm,你自己嵌入成功了吗,mono好歹能用 什么时候能把EFC用在unity上啊[魔性笑] 写这文章的人对mono近几年的发展一点都不了解