跳到主要内容

对App内存影响

接入hybridclr后对App运行时内存的影响主要由以下几部分构成:

  • 动态堆内存
    • (增加)维护桥接函数映射占用的内存
    • (增加)解释器线程数据栈和帧栈内存
    • (增加)指令优化模块(只在商业化版本上有)占用的内存
    • (增加)DHE(只在旗舰化版本上有)相关额外占用的元数据内存
    • 减少)优化libil2cpp元数据内存管理(只在商业化版本上有)。显著减少了的元数据内存开销
  • 静态Code段内存
    • (增加)桥接函数MehtodBridge.cpp编译后增加了二进制代码大小
    • (增加)DHE代码注入所引入的额外代码膨胀导致增加了二进制代码大小(只在旗舰化版本上有
    • 减少)将AOT程序集转为普通解释执行的程序集后(不含DHE程序集,因为DHE程序集也需要编译到AOT中),减少了二进制代码大小
危险

增加的静态Code段内存大小虽然会显示在App的总内存中,但它并不代表代码的真实内存占用。静态Code段代码属于按需加载,实际上占用内存需要查看实际占用的物理内存(RSS)数据。

我们构建了测试项目测试hybridclr对包体的实际影响。

测试

我们在Unity 2021版本测试了构建的Android Armv8平台apk的内存。

测试工程的AOT部分完整包含了以下框架和库:

我们统计了构建完apk后AOT模块dll的总大小:共12.0M。

测试工程的热更新部分代码以下部分构成:

  • 单元测试工程代码
  • Luban生成的配置代码

编译后的HotUpdate.dll为1216k。

桥接函数MethodBridge.cpp大小为15088K。

我们对比了以下几种情况的内存占用:

  • NotHybridCLR-NotHotUpdateCode 未接入HybridCLR,未包含HotUpdate代码
  • NotHybridCLR-HotUpdateCode 未接入HybridCLR,包含HotUpdate代码
  • HybridCLR社区版-NotHotUpdateCode 接入HybridCLR社区版,不包含HotUpdate代码,正常生成桥接函数文件
  • HybridCLR社区版-HotUpdateCode 接入HybridCLR社区版,包含HotUpdate代码,正常生成桥接函数文件
  • HybridCLR专业版-NotHotUpdateCode 接入HybridCLR专业版,不包含HotUpdate代码,正常生成桥接函数文件
  • HybridCLR专业版-HotUpdateCode 接入HybridCLR专业版,包含HotUpdate代码,正常生成桥接函数文件
  • HybridCLR旗舰版-NotHotUpdateCode 接入HybridCLR旗舰版,不包含HotUpdate代码,正常生成桥接函数
  • HybridCLR旗舰版-HotUpdateCode-LoadOriginalDifferentialHybridAssembly 接入HybridCLR旗舰版,包含HotUpdate代码,正常生成桥接函数,HotUpdate程序集未改动,使用RuntimeApi::LoadOriginalDifferentialHybridAssembly加载HotUpdate
  • HybridCLR旗舰版-HotUpdateCode-LoadDifferentialHybridAssembly 接入HybridCLR旗舰版,包含HotUpdate代码,正常生成桥接函数,HotUpdate程序集未改动,使用RuntimeApi::LoadDifferentialHybridAssembly加载HotUpdate

测试结果如下:

构建方式App堆内存(K)
NotHybridCLR-NotHotUpdateCode51343
NotHybridCLR-HotUpdateCode59400
HybridCLR社区版-NotHotUpdateCode53592
HybridCLR社区版-HotUpdateCode65695
HybridCLR专业版-NotHotUpdateCode50380
HybridCLR专业版-HotUpdateCode62235
HybridCLR旗舰版-NotHotUpdateCode52531
HybridCLR旗舰版-HotUpdateCode-LoadOriginalDifferentialHybridAssembly61276
HybridCLR旗舰版-HotUpdateCode-LoadDifferentialHybridAssembly65655

根据以上测试项目,我们大约可以得出以下结论:

  • (增加)桥接函数大约占用 {桥接函数文件MethodBridge.cpp文件大小} * 0.1 大小的堆内存
  • (增加)每个执行过热更新代码的线程大约会占用1.2M内存
  • (增加)指令优化模块(只有商业化版本有)占用了约为700K
  • (增加)DHE(只有旗舰版本有)相关额外占用的元数据内存。大约为 {AOT程序集总大小+DHE程序集总大小} * 0.12大小的内存
  • (增加)桥接函数MehtodBridge.cpp编译后增加了二进制代码大小。大约为 {MethodBridge.cpp文件大小} * 0.3大小
  • (增加)DHE(只有旗舰版本有)代码注入所引入的额外代码导致增加了二进制代码大小。大约为 {DHE程序集大小} * 0.86大小
  • 减少)优化libil2cpp元数据内存管理(只有商业化版本有),减少了10-25%的元数据内存开销
  • 减少)将AOT程序集转为普通解释执行的程序集后(不含DHE程序集,因为DHE程序集也需要编译到AOT中),减少了二进制代码大小。大约为{热更新程序集大小} * 5.2大小

总结

社区版

  • 新增堆内存大约为 {MethodBridge.cpp大小} * 0.1 + 1.2M * {执行过解释代码的线程数} + {热更新程序集大小} * 2.2
  • 增加代码段内存约为 {MethodBridge.cpp大小} * 0.3 - {热更新程序集大小} * 5.2
提示

一般来说,接入社区版本后二进制代码会减少,即增加的代码段内存一般为负值

专业版

  • 新增堆内存大约为 {MethodBridge.cpp大小} * 0.1 + 0.7M(指令优化模块内存) + 1.2M * {执行过解释代码的线程数} + {热更新程序集大小} * 1.6 - {所有程序集大小} * 0.2
  • 增加代码段内存约为{MethodBridge.cpp大小} * 0.3 - {热更新程序集大小} * 5.2
提示

一般来说,接入专业版本后二进制代码会减少,即增加的代码段内存一般为负值

旗舰版

  • 新增堆内存大约为 {MethodBridge.cpp大小} * 0.1 + 0.7M(指令优化模块内存) + 1.2M * {执行过解释代码的线程数} + {热更新程序集大小} * 3(存疑,感觉明显高估) + {AOT程序集总大小+DHE程序集总大小} * 0.12 - {所有程序集大小} * 0.2
  • 增加代码段内存约为 {DHE程序集大小} * 0.86 + {MethodBridge.cpp大小} * 0.3
危险

由于示例项目规模不大,测试结果跟真实项目未必相符, {热更新程序集大小} * 3 这部分内存明显高估,请以实际项目为准。