Unity热更技术对比(Lua、ILRuntime、HybridCLR)
热更技术原理:app+脚本解释器+脚本代码,动态执行最新代码,实现热更。
解释器:
- Lua技术 = Lua解释器+Lua脚本;
- C# = C#解释器+c#脚本
Unity的热更方案:
- Lua解决方案(如ToLua,xLua等):内置Lua虚拟机+UnityEngine与C#框架的接口导出+Lua代码。
- C#解决方案(ILRuntime):内置.net字节码解释器(虚拟机)+解释执行.net字节码。
- 内置虚拟机(lua的虚拟机或者IL虚拟机,均基于栈实现):自己解释执行的一个运行环境,在里面解析执行指令(lua虚拟机的指令/IL语句)
- 这些热更新方案的VM(虚拟机)与IL2CPP是独立的,意味着它们的元数据系统是不想通的,所以无法直接继承MonoBehaviour(IL2CPP级别的数据对象),导致了以下问题:要封装一层;跨域访问;接口导出;不符合标准的Unity开发。
HybridCLR(huatuo)方案:基于IL2CPP
- 在Unity的il2cpp里面添加一个可以装载.net字节码,解释执行.net的字节码的功能。
- IL2CPP runtime环境(IL2CPP VM)编写一个解释器,解释执行IL代码指令+使用的是AOT的数据内存对象;
- IL2CPP 数据内存+代码逻辑指令(二进制机器指令)
- IL2CPP_huatuo 数据内存+代码逻辑指令(二进制机器指令)+ IL代码指令解释执行
HybridCLR技术实现:
- MetadataCache::LoadAssemblyFromBytes(C#层调用Assembly.Load时触发)时加载并注册interpreter Assembly。
- IL2CPP运行过程中延迟初始化类型相关元数据,其中的关键为正确设置了MethodInfo元数据中methodPointer指针。
- IL2CPP运行时通过methodPointer或者methodInvoke指针,再经过桥接函数跳转,最终执行了Interpreter::Execute函数。
- Execute函数在第一次执行某Interpreter函数时触发HiTransform::Transform操作,将原始IL指令翻译为HybridCLR的寄存器指令。
- 执行该函数对应的HybridCLR寄存器指令,实现执行最新的代码效果。
huatuo热更可以随意继承使用GameObject,MonoBehvaviour数据对象,而不用像其它热更方案一样去封装类型传递。
在AOT编译时已经把类型编译进去,解释执行时IL运行时的new Obj和AOT里的new Obj是同一个数据对象。
脚本类与AOT类在同一个运行时内,可以随意写继承、反射、多线程(volatile、ThreadStatic、Task、async)之类的代码。不需要额外写任何特殊代码、没有代码生成,也没有什么特殊限制。
HybridCLR优势:
- 直接使用AOT项目中的内存对象,内存占用、跨域都没问题,性能好、内存占用少;
- 直接unity+c#开发,不用内置虚拟机,不用弄多一个热更项目;
- 使用普通的Unity模式开发,不用特殊开发模式。
HybridCLR扩充了il2cpp的代码,使它由纯AOT runtime变成‘AOT+Interpreter’ 混合runtime,进而原生支持动态加载assembly,使得基于il2cpp backend打包的游戏不仅能在Android平台,也能在IOS、Consoles等限制了JIT的平台上高效地以AOT+interpreter混合模式执行,从底层彻底支持了热更新。
HybridCLR是原生的c#热更新方案。通俗地说,il2cpp相当于mono的aot模块,HybridCLR相当于mono的interpreter模块,两者合一成为完整mono。HybridCLR使得il2cpp变成一个全功能的runtime,原生(即通过System.Reflection.Assembly.Load)支持动态加载dll,从而支持ios平台的热更新。
正因为HybridCLR是原生runtime级别实现,热更新部分的类型与主工程AOT部分类型是完全等价并且无缝统一的。可以随意调用、继承、反射、多线程,不需要生成代码或者写适配器。
其他热更新方案则是独立vm,与il2cpp的关系本质上相当于mono中嵌入lua的关系。因此类型系统不统一,为了让热更新类型能够继承AOT部分类型,需要写适配器,并且解释器中的类型不能为主工程的类型系统所识别。特性不完整、开发麻烦、运行效率低下。
huatuo技术原理:
- Unity打包运行为:AOT(本地机器指令执行)+IL2CPP VM(提供基础服务支撑,如GC,线程创建等)。
- Unity项目运行:数据内存对象+AOT代码机器指令
- HybridCLR项目运行:数据内存对象+AOT代码机器指令+Interpreter IL指令解释执行
方案对比:
一、可热更范围对比
1、Lua方案:
- 使用Lua开发的脚本都可以热更,C#的脚本不能热更。更改C#的脚本要重新导出类型后重新出包。
2、ILRuntime方案:
- 由Unity内置.net字节码解释器,分两个项目,热更项目里的c#代码可以热更,主框架里的c#代码无法热更。
3、HybridCLR:
- 在IL2CPP VM中内置一个.net字节码解释器,会把.net里的数据对象映射到native的数据对象,所以全部的c#代码都可以热更。如果需要热更就装载到IL2CPP VM解释执行,不需要热更就使用原来的AOT的代码。理论上可以更新任何代码。
二、新旧版本性能对比
1、HybridCLR:
- 新版本重新出包时,可以把需要之前热更的dll直接加入到AOT模式去执行;新版本native性能会比解释执行性能要好。
2、其它方案:
- 每个版本都是解释执行,效率不会有提升。
三、解释执行时性能对比
1、HybridCLR:
- 在解释执行时IL字节码时,先把字节码对象内存映射成native内存块,所以执行IL字节码时能直接访问native内存对象。
2、其它方案:
- 由于是内置了虚拟机,所以热更代码的内存是运行在虚拟机域的内存对象,在访问native c#对象时,要做一层转化和用函数包起来访问,会增加内存占用(多了对象头和内存对齐)。
HybridCLR兼容性:
- 支持2019.4.x、2020.3.x、2021.3.x系列LTS版本
- 支持所有il2cpp支持的平台。目前测试支持 PC(Win32和Win64)、macOS(x86、x64、Arm64)、Android(armv7、armv8)、iOS(64bit)、NS(64bit)、WebGL(有少量bug)平台,剩余平台有待测试。
- 测试过大量游戏常见库,未发现跟il2cpp原生兼容但使用HybridCLR后不兼容性的库。只要能在il2cpp backend下工作的库都可以在HybridCLR下正常工作。甚至那些与il2cpp因为AOT问题不兼容的库,现在因为HybridCLR对il2cpp的能力扩充,反而可以正常运行了。