关于c#:HybridCLR划时代的Unity原生C热更新技术

6次阅读

共计 4650 个字符,预计需要花费 12 分钟才能阅读完成。

HybridCLR 是一个个性残缺、零老本、高性能、低内存的近乎完满的 Unity 全平台原生 C# 热更计划。

HybridCLR 裁减了 IL2CPP 的代码,使它由纯 AOT Runtime 变成“AOT+Interpreter“混合 Runtime,进而原生反对动静加载 Assembly,使得基于 IL2CPP Backend 打包的游戏不仅能在 Android 平台,也能在 iOS、Consoles 等限度了 JIT 的平台上高效地以 AOT+interpreter 混合模式执行。

HybridCLR 开创性地实现了“Differential Hybrid Dll“技术。即能够对 AOT Dll 任意增删改,会智能地让变动或者新增的类和函数以 Interpreter 模式运行,但未改变的类和函数以 AOT 形式运行,让热更新的游戏逻辑的运行性能根本达到原生 AOT 的程度。

HybridCLR(wolong)原作者 walon(focus-creative-games 创始人)将该系列课程放入 UWA 学堂连载更新,现已【收费上线 UWA 学堂】,本文先带大家对 HybridCLR——划时代的 Unity 原生 C# 热更新技术 有个简略的理解,更多内容可返回 UWA 学堂查看。


1. 根底概念

1.1 CLR
CLR 即 Common Language Runtime,中文叫公共语言运行时,是让.NET 程序执行所需的内部服务的汇合,是.NET 平台的外围和最重要的组件,相似于 Java 的 JVM。更具体介绍请看《公共语言运行时 (CLR) 概述》。

1.2 IL2CPP
IL2CPP 是 Unity 开发的跨平台 CLR 解决方案,诞生它的一个要害起因是 Unity 须要跨平台运行。但一些平台如 iOS,这种禁止 JIT 并导致依赖 JIT 的官网 CLR 虚拟机无奈运行,而是必须应用 AOT 技术将 Mananged 程序提前转化为指标平台的动态原生程序后再运行。而 Mono 尽管也反对 AOT,但性能较差以及跨平台反对不佳。

IL2CPP 计划蕴含一套 AOT 运行时以及一套 DLL 到 C ++ 代码及元数据的转换工具,使得原始的 C# 开发的代码最终能在 iOS 这样的平台运行起来。

1.3 IL2CPP 与热更新
很可怜,不像 Mono 有 Hybrid mode execution,可反对动静加载 DLL。IL2CPP 是一个纯动态的 AOT 运行时,不反对运行时加载 DLL,因而不反对热更新。

目前 Unity 平台的支流热更新计划 xLua、ILRuntime 之类都是引入一个第三方 VM(Virtual Machine),在 VM 中解释执行代码,来实现热更新。这里咱们只剖析应用 C# 为开发语言的热更新计划。这些热更新计划的 VM 与 IL2CPP 是独立的,意味着它们的元数据系统是不相通的,在热更新里新增一个类型是无奈被 IL2CPP 所辨认的(例如,通过 System.Activator.CreateInstance 是不可能创立出这个热更新类型的实例),这种看起来像,但实际上又不是的伪 CLR 虚拟机,在与 IL2CPP 这种简单的 CLR 运行时交互时,会产生极大量的兼容性问题,另外还有重大的性能问题。

一个大胆的想法是,是否有可能对 IL2CPP 运行时进行裁减,增加 Interpreter 模块,进而实现 Mono hybrid mode execution 这样机制?这样一来就能彻底反对热更新,并且兼容性极佳。对开发者来说,除了解释模式运行的局部执行得比较慢,其余方面跟规范的运行时没有区别。

对 IL2CPP 加以理解并且三思而行后的答案是——的确是可行的!具体分析参见第二节《对于 HybridCLR 可行性的思维试验》。这个想法诞生了 HybridCLR,Unity 平台第一个反对 iOS 的跨平台原生 C# 热更新计划!


2. 原理

HybridCLR 裁减了 IL2CPP 运行时,将它由 AOT 运行时革新为“AOT + interpreter”双引擎的混合运行时,进而完满反对在 iOS 这种禁止 JIT 的平台上以解释模式无缝地运行动静加载的 DLL。如下图所示:

更具体一些,至多须要实现以下性能:

  • 加载和解析 DLL 元数据
  • 动静注册元数据,其中要害为 Hook 动静函数的执行流到解释器函数
  • 实现一个高效正确的解释器
  • 正确处理 GC 及多线程等运行时机制

3. 个性

3.1 规范运行时个性
近乎残缺实现了 ECMA-335 标准,不反对的个性仅包含:

  • 不反对 Delegate 的 BeginInvoke,EndInvoke。纯正是感觉没必要实现。
  • 不反对 MonoPInvokeCallbackAttribute。意味着你如果同时还接了 Lua,你没法间接将热更新的 C# 函数注册到 Lua 中,但有一个不简单的方法能做到这点。

因为 HybridCLR 极其残缺的实现,让应用 HybridCLR 后的 C# 开发体验跟 Editor 下 Mono 开发简直完全相同(除了调用一些 IL2CPP 没实现的.net framework 函数,非专家级别的开发者难以结构出 HybridCLR 不反对的用例)。

另外,因为是运行时级别的实现,HybridCLR 反对这些个性的同时,不须要你额定生成或者调整任何代码。对于开发者来说,相比 Unity 下原生 C# 开发,零额定的学习和开发成本。

3.2 AOT 相干个性
因为 IL2CPP AOT 模块的存在,IL2CPP 比规范运行时多了一些不存在的机制,因而 HybridCLR 也有一些额定的个性:

  • 反对应用 Interpreter Assembly 替换 AOT Assembly(限度:必须不存在其余 AOT Assembly 对它的间接援用)。
  • 反对补充元数据机制,彻底反对 AOT 泛型,参见《AOT 泛型限度及原理介绍》。
  • 反对 AOT Hotfix,能够修复 AOT 模块的 Bug。
  • 反对任意 C# 函数注册到 Lua 之类的虚拟机,不限于 Static 函数,并且也不须要 MonoPInvokeCallbackAttribute。条件是注册和回调形式须要稍微调整。

3.3 Unity 相干个性

  • 完满反对 Unity 的 Assembly Def 模块机制。
  • 完满反对代码中挂载热更新脚本,无应用场景限度
  • 完满反对资源上挂载热更新脚本,但要求打包工作流有少许调整,参见《MonoBehaviour 相干工作流》。

4. 与其余风行的 C# 热更新计划的区别

从原理来说,HybridCLR 简直将热更新技术做到实践上的极限,与以后所有支流热更新计划不在一个档次。

4.1 实质比拟
HybridCLR 是原生的 C# 热更新计划。艰深地说,IL2CPP 相当于 Mono 的 AOT 模块,HybridCLR 相当于 Mono 的 Interpreter 模块,两者合一成为残缺 Mono。HybridCLR 使得 IL2CPP 变成一个全功能的 Runtime,原生(即通过 System.Reflection.Assembly.Load)反对动静加载 DLL,从而反对 iOS 平台的热更新。

正因为 hHybridCLR 是原生 Runtime 级别实现,热更新局部的类型与主工程 AOT 局部类型是齐全等价并且无缝对立的。能够随便调用、继承、反射或多线程,不须要生成代码或者写适配器。

其余热更新计划则是独立 VM,与 IL2CPP 的关系实质上相当于 Mono 中嵌入 Lua 的关系。因而类型零碎不对立,为了让热更新类型可能继承 AOT 局部类型,须要写适配器,并且解释器中的类型不能为主工程的类型零碎所辨认。个性不残缺、开发麻烦、运行效率低下。

4.2 理论应用体验或者个性比拟

  • HybridCLR 学习和应用老本简直为零。HybridCLR 让 IL2CPP 变成全功能的 Runtime,学习和应用老本简直为零,简直零侵入性。而其余计划则有大量的坑和须要躲避的规定,学习和应用老本较高,须要对原我的项目作大量革新。
  • HybridCLR 能够应用所有 C# 的个性,而其余计划往往有大量的限度。
  • HybridCLR 中能够间接反对应用和继承主工程中的类型,其余计划要写适配器或者生成代码。
  • HybridCLR 中热更新局部元数据与 AOT 元数据无缝对立,像反射代码可能失常工作的,AOT 局部也能够通过规范 Reflection 接口创立出热更新对象。其余计划做不到。
  • HybridCLR 对多线程反对良好。像多线程、ThreadStatic、Async 等等个性都是 HybridCLR 间接反对,其余计划除了 Async 个性外均难以反对。
  • HybridCLR 中 Unity 工作流与原生简直完全相同。HybridCLR 中热更新 MonoBehaviour 能够间接挂载在热更新资源上,并且正确工作。其余计划不行。
  • HybridCLR 兼容性极高。各种第三方库只有在 IL2CPP 下能工作,在 HybridCLR 下也能失常工作。其余计划往往要大量魔改源码。
  • HybridCLR 内存效率极高。HybridCLR 中热更新类型与主工程的 AOT 类型齐全等价,占用一样多的空间。其余计划的等同类型则是假类型,不仅不能被 Runtime 辨认,还多占了数倍空间。
  • HybridCLR 执行效率高。HybridCLR 中热更新局部与主工程 AOT 局部交互属于 IL2CPP 外部交互,效率极高。而其余计划则是独立虚拟机与 IL2CPP 之间的效率,不仅交互麻烦还效率低下。

5. 运行性能

理论性能如实践预计,全面并且大幅胜出以后支流的 xLua、Puerts、ILRuntime 之类的热更新计划。

  • 根底指令(数值计算及条件跳转等指令),因为各个语言之间差距不大,因而胜出不显著。
  • 对象模型指令。因为没有跨语言交互的老本,简直是数倍到数十倍的晋升(如果指令本身耗费特地大,则差距不那么显著)。

性能测试用例来自 ILRuntime 提供的规范测试用例,测试项目来自 Don’t worry 的 github 仓库。

测试结果显示,绝大多数测试用例都有数倍到数十倍的性能差距,差距极其夸大。唯独数值计算跟 xLua 有大量劣势,这是因为以后HybridCLR 未对指令作任何优化,后续优化版本大多数根底指令都将有 100~300% 的性能晋升。


6. 内存

HybridCLR 是运行时级别的实现,因为热更新的脚本,除了执行代码是以解释模式执行,其余形式跟 AOT 局部的类型是完全相同的,包含占用内存。

6.1 对象内存大小比照
Lua 的计算规定略简单,参见《Lua 数据结构和内存占用剖析》。空 Table 占 56 字节,每多一个字段至多多占 32 字节。

ILRuntime 的类型除了 Enum 外对立以 IlTypeInstance 表白,空类型占 72 字节,每多一个字段至多多用 16 字节。如果对象中蕴含援用类型数据,则整体又至多多 24 字节,并且每多一个 Object 字段多 8 字节。


7. 以后稳定性情况

技术评估上目前稳定性处于 Beta 版本。因为 HybridCLR 技术原理的先进性,Bug 实质上不多,稳固得十分快。曾经有一段时间未收到 2020.3.33 版本的 Bug 反馈。

  • 目前 PC、Android、iOS 已跑通所有单元测试,可稳固体验应用。
  • 2022.6.7 第一款应用 HybridCLR 的 Android 和 iOS 双端休闲游戏正式上线。
  • 2022.7 将有至多 2 款重度我的项目和 2 款中度游戏上线。
  • 2022 年预计有几十款中重度我的项目及超过一百款轻度或者独立游戏上线。

曾经有几十个大中型游戏我的项目较完整地接入 HybridCLR,并且其中一些在紧锣密鼓作上线前测试。具体参见收集的一些残缺接入的商业我的项目列表。


8. 总结

HybridCLR 是一个划时代的 Unity 平台 C# 原生热更新技术,它将国内 Unity 开发的技术框架程度进步到新的高度,并粗浅地扭转 Unity 平台的开发生态。


本文内容就介绍到这里啦,更多内容能够返回 UWA 学堂进行浏览。

正文完
 0