尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

01-10-运行时-JIT-R2R-NativeAOT与IL2CPP-部署模型与动态能力边界

01-10-运行时-JIT-R2R-NativeAOT与IL2CPP-部署模型与动态能力边界 JIT、R2R、NativeAOT 与 IL2CPP部署模型和动态能力边界系列C# 与常用数据结构源码剖析 · 运行时底层剖析版本边界C# 12、.NET 8CoreCLR/NativeAOT 源码观察固定为dotnet/runtime v8.0.0Unity 边界Unity 2022.3 LTS 的 IL2CPP具体行为还受补丁版本、平台、Managed Stripping Level 与原生工具链影响一、先纠正分类R2R、NativeAOT 与 IL2CPP 不是三级开关“JIT 或 AOT”常被画成一条从动态到静态的直线实际部署模型至少包含四组彼此独立的问题程序集是否保留 IL 与完整元数据目标方法在何时、由哪个编译器生成原生代码运行时是否还带 JIT能否加载新的托管代码发布时是否做闭世界分析、裁剪和原生链接。因此本文讨论的三种方案不能混为一谈模型构建产物与运行时运行时 JIT典型闭世界假设CoreCLR JITIL 由 CoreCLR 按需编译有否可加载新程序集ReadyToRunR2R程序集含预编译代码仍在 CoreCLR 上运行有通常仍保留兜底与重编译能力否.NET 8 NativeAOTILCompiler 生成平台原生程序使用 NativeAOT runtime无是发布时决定代码与元数据集合Unity 2022.3 IL2CPPIL 转换为 C再由平台 C 工具链编译链接无是结合 Unity linker 与 IL2CPP 分析R2R 是 CoreCLR 的部署优化不是“完整 AOT 的轻量版”NativeAOT 也不是“R2R 再关掉 JIT”IL2CPP 更不是 NativeAOT 的 C 皮肤。它们共享 C# 和部分 BCL 语义但加载器、泛型实现、GC 集成、互操作、裁剪工具与诊断方式不同。本文采用四种证据标签公共契约API 文档、语言或 CLI 规范保证的行为固定实现观察针对.NET 8 / dotnet/runtime v8.0.0或 Unity 2022.3 的实现结构化摘要/伪代码解释机制不是可复制的源码实验结论只对记录过 RID、CPU、构建参数和运行时的样本成立。二、JIT 的价值不是“永远更快”而是可以晚做决定2.1 分层编译与动态 PGOCoreCLR 不必在第一次调用时就付出最高优化成本。分层编译可以先提供较快生成的代码再把热点方法重新 JIT 为更优化的版本动态 PGO 能收集实际分支、类型和调用信息辅助内联、去虚拟化等决策。概念流程如下结构化摘要不代表每个方法都经历所有阶段 load IL and metadata - obtain initial executable code from JIT, or from an eligible ReadyToRun body - count/sample execution - select hot methods - JIT an optimized version, optionally using profile data - redirect later calls where runtime policy permits具体方法可能因预编译、配置、方法属性、OSR、泛型形态或调用频率而跳过某些步骤。不能把 Tier 阈值写成固定调用次数也不能从“启用了 tiering”推出所有方法最终都有 Tier 1。2.2 运行时类型与硬件信息JIT 可以利用进程实际加载的类型与 CPU 能力。例如对虚调用生成带类型守卫的快速路径对循环使用目标 CPU 支持的 SIMD 指令或在Avx2.IsSupported等分支下编译相应 intrinsic。但 AOT 并非只能面向“最低公分母”。AOT 编译器可以以指定 instruction set 为构建基线同时生成多个实现并在运行时分派保留显式IsSupported分支由操作系统或原生工具链完成某些平台分发。区别在于决策时间和代码体积而不是“AOT 绝对不能用 AVX2”。跨机器发布必须明确最低 CPU 要求针对部署机发布则可能换取更激进指令但降低可移植性。2.3 动态生成代码是单独一项能力JIT 可以把运行时产生的新 IL 转为机器码因此DynamicMethod、Reflection.Emit或表达式树编译器能有代码生成路径。它们并不是普通反射的同义词读取typeof(Order).GetProperties()不需要生成新机器码创建一个从未存在过的方法体才需要。这一区分贯穿全文没有 JIT 不等于没有反射保留反射元数据也不等于能够生成新代码。三、ReadyToRun预编译入口与 JIT 共存3.1 R2R 解决什么在 .NET 8 中设置PublishReadyToRuntrue会让 SDK/crossgen2 为目标 RID 生成包含 ReadyToRun 原生代码的程序集。运行时若确认版本、架构、依赖与 fixup 条件满足可以直接使用这些代码减少启动阶段的 JIT 工作。dotnet publish -c Release -r linux-x64 \ -p:PublishReadyToRuntrue \ --self-contained falseR2R 图像通常还保留 IL因为 CoreCLR 可能需要 JIT某方法没有可用的预编译体、泛型实例化需要运行时处理、预编译假设不成立或者 tiering 决定为热点方法生成优化版本。精确行为由 .NET 8 运行时策略与发布选项决定。3.2 R2R 与 tiering/PGO 不是互斥关系“用了 R2R 就没有 Tier 1/PGO”是错误结论。R2R 代码可承担初始代码角色热点仍可能由 JIT 替换动态 PGO 的收益主要体现在依据本次进程 profile 生成的 JIT 优化代码。反过来禁用 tiered compilation、设置环境变量、方法从未变热或 runtime 策略不同都会改变结果。R2R 还可结合构建期 profile 与 composite image 等部署技术但它们分别影响预编译覆盖、跨程序集优化、映像大小和构建时间。不能只看到PublishReadyToRun属性就承诺固定启动提升。3.3 R2R 没有消除动态能力因为应用仍运行在完整 CoreCLR 上普通反射、运行时程序集加载和动态代码能力通常与该 CoreCLR 部署一致而不是由 R2R 格式一刀切禁止。RuntimeFeature.IsDynamicCodeSupported/IsDynamicCodeCompiled应在实际进程检查。代价同样需要测量R2R 映像常比纯 IL 大跨模块调用可能经 fixup/indirection构建时间增加预编译体不一定等于稳态最佳代码。服务、CLI 和桌面程序对启动、磁盘、内存页共享与长时间吞吐的权重不同。四、NativeAOT闭世界原生程序不是“所有反射都失效”4.1 发布管线与边界.NET 8 NativeAOT 的概念管线是C# 12 source - Roslyn emits IL metadata - trim/closed-world analysis discovers reachable program - ILCompiler/RyuJIT AOT code generation - native linker combines app, runtime and native dependencies - platform executable/library without a runtime JIT实际工具阶段会交错并包含依赖扫描、元数据管理、generic lookup 和平台链接。固定 tag 可从src/coreclr/tools/aot、ILCompiler、dependency analysis 以及 NativeAOT runtime 相关目录追踪这不是对公共 ABI 的承诺。典型发布命令需要目标 RID 与本机/交叉编译工具链dotnet publish -c Release -r osx-arm64 \ -p:PublishAottrueNativeAOT 没有运行时 JIT也不支持把任意新托管程序集加载后现编译执行。它适合依赖集合可在发布时确定的程序。框架与库支持程度应以 .NET 8 NativeAOT compatibility 文档和构建警告为准不按“这是 C# 语法”推断可用性。4.2 反射的核心问题是静态分析可见性以下代码对人很直观对裁剪器却可能不够static object Create(string assemblyQualifiedName) { Type type Type.GetType(assemblyQualifiedName, throwOnError: true)!; return Activator.CreateInstance(type)!; }字符串可来自配置、网络或插件目录。构建期无法知道目标类型自然无法证明哪些构造器、方法和元数据要保留。NativeAOT 并非规定Type.GetProperties或Activator.CreateInstance一律失败当类型、成员、元数据与所需原生代码都在闭世界中可用时许多反射操作可以工作。风险来自“运行时选择超出构建期所见集合”。应首先让类型流可分析using System.Diagnostics.CodeAnalysis; static object Create( [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type) { return Activator.CreateInstance(type)!; }DynamicallyAccessedMembers是对数据流的要求传入的Type必须保留相应成员。若调用者无法证明会收到 trim/AOT 分析警告。它不是运行时“注册表”也不自动枚举未知配置值。4.3 警告注解分别表达两种危险[RequiresUnreferencedCode( The member set is selected from external configuration.)] static void DiscoverByName(string name) { /* reflection */ } [RequiresDynamicCode( This operation may need to generate executable code at runtime.)] static Delegate BuildFastInvoker() { /* code generation */ }RequiresUnreferencedCode裁剪后所需成员可能不在调用者需要承担分析无法证明的风险RequiresDynamicCode操作可能依赖运行时代码生成在 NativeAOT 等环境不可满足两者可同时存在因为元数据保留和机器代码生成是两个问题。这些属性主要向工具和调用者传递约束不是 catch 后继续运行的能力开关。应用应清理发布警告库应把注解传播到真正无法静态保证的公共入口。4.4DynamicDependency与 descriptor 只能精确保留已知目标当分析器无法看见一个已知的反射依赖可在贴近反射入口处使用DynamicDependencyusing System.Diagnostics.CodeAnalysis; static class PluginBootstrap { [DynamicDependency( DynamicallyAccessedMemberTypes.PublicConstructors, typeof(BuiltInPlugin))] public static object CreateBuiltIn() Activator.CreateInstance(typeof(BuiltInPlugin))!; } sealed class BuiltInPlugin { public BuiltInPlugin() { } }也可通过 linker descriptor 为已知程序集/类型/成员声明保留策略。descriptor 的 schema 与 MSBuild 接入必须使用目标 SDK 文档核对。它用于保留分析原本可能删除的实体不是描述任意闭合泛型机器码的通用 DSL。特别要区分历史 .NET Native 工具链的rd.xml、现代 ILLink descriptor、NativeAOT 配置和 Unitylink.xml并非同一种文件。本文不提供不存在的genericargument注册语法。保留ListMyType相关元数据也不等于凭空为所有反射组合生成所有原生代码。五、泛型共享、特化与运行时决定类型5.1 JIT 也会共享泛型代码“JIT 为每个T生成完整独立代码”并不准确。CoreCLR 通常可在多个引用类型实例化之间共享 canonical code因为引用的机器表示和许多操作一致值类型因大小、GC 布局和运算不同常需要特化。共享代码通过 generic dictionary/context 获得类型相关信息。这是实现策略不是简单的“引用都是固定多少字节”公共契约。方法约束、静态抽象成员、值类型布局、调用方式与优化都可能改变代码形态。5.2 NativeAOT 的闭世界不等于穷举笛卡尔积NativeAOT 分析可达实例化并结合 exact/canonical sharing、runtime-determined type 与泛型查找生成代码。它不会天真地为每个泛型定义乘上所有类型也不会承诺任何反射构造的组合都存在。static object MakeClosed(Type elementType) { Type closed typeof(List).MakeGenericType(elementType); return Activator.CreateInstance(closed)!; }若elementType来自无界外部输入构建期无法建立可靠闭包。某些组合可能因共享代码与元数据恰好可用而工作另一些可能产生 AOT 警告或运行时失败这正是“在开发机试过一个类型”不能证明模型成立的原因。更可靠的做法是显式限定支持集static object CreateList(string kind) kind switch { enemy new ListEnemy(), item new ListItem(), _ throw new NotSupportedException(kind) };这种工厂既让依赖分析可见也把外部协议的合法类型集合写入代码。类型很多时用 Source Generator 从声明式清单生成工厂与注册表而不是手写“永不调用的预热函数”。5.3 泛型虚方法尤其需要矩阵测试反射调用闭合泛型、泛型虚方法、值类型嵌套泛型和接口分派会同时涉及代码发现与 dispatch。NativeAOT 和 IL2CPP 都有自己的共享/特化策略不能从 CoreCLR JIT 的__Canon结论外推。测试必须覆盖真实形态例如IHandlerT.HandleU的实际T/U组合而不是只实例化一个ListT就宣布泛型已“注册”。构建日志中的 AOT/trim 警告应视为设计反馈不要全局 suppress。六、动态能力用 capability 检测不靠平台名称猜测.NET 8 提供运行时能力信息using System; using System.Runtime.CompilerServices; Console.WriteLine($Dynamic code supported: {RuntimeFeature.IsDynamicCodeSupported}); Console.WriteLine($Dynamic code compiled: {RuntimeFeature.IsDynamicCodeCompiled});IsDynamicCodeSupported表示运行时是否支持动态代码IsDynamicCodeCompiled区分动态代码是否会编译成本机代码等情形。它们适合在库中选择“生成代码”或“预生成/解释”路径但不能替代 API 自身的注解与目标平台测试。6.1 Reflection.Emit 与动态程序集NativeAOT 没有 JIT所以依赖DynamicMethod、AssemblyBuilder、ILGenerator 后即时执行的设计不成立。R2R 仍在 CoreCLR 上通常可继续使用这些能力。Unity IL2CPP 是否暴露某个 API、是 stub、抛异常还是受平台限制要按 Unity 2022.3 的 API profile 与 Player 验证不能只看 Editor Mono 成功。6.2dynamic不是一个布尔格子C#dynamic经 DLR binder/call site 解析。某些简单操作在 AOT 目标可能有可用路径某些 binder 行为会需要运行时代码生成、未保留成员或不受支持的反射组合。因此不能笼统写“NativeAOT 下dynamic全部不支持”也不能因为一个加法样例成功就认为插件式动态分派安全。跨 AOT 的库应优先使用接口、泛型约束、显式 union/模式匹配或源生成 dispatch确实使用dynamic时保留分析警告并对每个发布目标运行测试。6.3 表达式树可解释不一定必须发射 IL表达式树有两件事构造/检查树以及把树变成可调用 delegate。后者可以走动态编译也可以走解释器using System.Linq.Expressions; ExpressionFuncint, int expression value value * 2 1; Funcint, int operation RuntimeFeature.IsDynamicCodeSupported ? expression.Compile() : expression.Compile(preferInterpretation: true); Console.WriteLine(operation(20));在无动态代码环境应显式选择/验证解释路径。具体.Compile()重载是否自动回退、哪些表达式节点受解释器支持、性能与异常行为都以 .NET 8 API 注解和目标运行时为准。解释避免生成机器码但通常有逐节点调度成本热路径可改用源生成委托或手写代码。七、Source Generator把开放世界选择变成构建产物Source Generator 在 Roslyn 编译期间读取语法、语义和附加文件生成普通 C#随后与应用一起接受 trimming/AOT 分析。它能把原来依赖运行时枚举的工作提前为序列化模型生成读写器与元数据上下文为依赖注入生成服务工厂和生命周期调用为 RPC/消息路由生成 ID 到强类型 handler 的 switch为对象映射、命令分发和状态机生成直接访问代码为有限插件集生成 manifest 与构造函数表。源生成不是自动正确。生成器必须包含所有业务类型增量输入要声明完整生成代码也可能触发 trim/AOT 警告。它不能让发布后才下载的未知托管程序集突然成为 NativeAOT 机器码。一个适合闭世界的注册表形状如下internal static partial class GeneratedHandlers { public static IMessageHandler Create(int id) id switch { 1 new LoginHandler(), 2 new MoveHandler(), _ throw new NotSupportedException($Unknown message id: {id}) }; }相比反射扫描全部程序集这种代码可被分析、可搜索、失败边界明确还能在生成阶段检查重复 ID。八、序列化、DI 与插件三个最容易踩 AOT 的系统8.1 序列化反射序列化器常按运行时类型枚举构造器、属性和泛型 converter。裁剪可能移除这些成员NativeAOT 也可能无法满足动态 accessor 生成。以System.Text.Json为例优先采用源生成上下文并显式声明根模型using System.Text.Json.Serialization; [JsonSerializable(typeof(SaveGame))] [JsonSerializable(typeof(ListItemData))] internal partial class GameJsonContext : JsonSerializerContext { }多态模型还要显式定义派生类型集合与未知类型策略。只保留 DTO 类型不保证第三方 converter、构造器访问和所有嵌套闭合泛型都可用。必须用最终 NativeAOT/IL2CPP Player 跑 round-trip、版本兼容和损坏输入测试。8.2 依赖注入运行时扫描程序集并通过表达式树/Emit 构造 activation delegate 的容器在 AOT 下可能遇到两类问题扫描成员被裁剪以及快速激活器需要动态代码。解决顺序应是使用容器官方 AOT/source-gen 模式显式注册服务把反射入口正确注解最后才考虑小范围保留。构建通过不等于生命周期正确。源生成 DI 仍要测试 scope、循环依赖、开放泛型、装饰器和异常清理。8.3 插件CoreCLR JIT 应用可以用AssemblyLoadContext加载兼容程序集并按需 JIT。NativeAOT 应用不能把任意后来出现的托管 DLL 当作同等插件执行需要在发布时包含插件、把插件放到进程外、使用脚本/解释器或设计稳定原生 ABI。Unity 的 AssetBundle 可承载资源和构建时已知的MonoBehaviour/类型引用但它不是为 IL2CPP Player 添加新 C# 机器代码的通道。热更新框架引入自己的解释器、补充元数据或代码生成模型应独立评估许可证、平台规则、性能和安全性不能称为 IL2CPP 原生支持的 JIT。九、Unity 2022.3 IL2CPP保留、生成、共享是三件事9.1 构建链不是“一类对应一个 C 类”Unity 2022.3 的概念构建链可写为C# assemblies - managed code stripping / dependency analysis - IL2CPP conversion and metadata/code generation - generated C IL2CPP runtime native plugins - platform C compiler and linker - Player生成物可能包含共享泛型方法、类型/方法元数据表、invoker、adjustor thunk、interop wrapper 和 runtime support。优化后还会被原生编译器合并、内联或删除因而“一 C# 类生成一 C 类”既不准确也不能用于估算包体。9.2link.xml主要控制 stripping 保留Unity linker 根据根和静态分析移除不可达托管代码。[Preserve]、link.xml以及 Unity 提供的其他保留机制可告诉 linker 保留指定程序集、类型或成员。它们主要解决“被裁掉”问题不是“让 IL2CPP 为任意反射闭合泛型自动生成机器码”的注册语言。linker assembly fullnameAssembly-CSharp type fullnameGame.Serialization.SaveGame preserveall / /assembly /linker这段只展示普通类型保留意图真实项目应最小化范围并按 Unity 2022.3 schema 验证。preserveall大面积使用会增加包体也可能掩盖依赖分析缺口。不存在可普遍依赖的genericargument语法来注册所有泛型代码。9.3 IL2CPP 泛型共享不是 CoreCLR 规则的复制IL2CPP 会根据构建期可见使用和自身 generic sharing 策略生成/共享代码。引用类型、值类型、布局、约束、虚调用和平台可影响是否共享Unity 版本也会演进。反射创建的闭合泛型尤其需要最终 Player 测试。可采用显式强类型入口或构建期生成注册表让实例化可见但不要把永不执行的“预热”方法当正式契约linker 仍可能删除它分析器也不保证按人的猜测保留所有下游形态。若必须用保留机制应同时验证 stripping report、生成日志与运行时用例。9.4 不给 IL2CPP 固定 GC 贴标签“IL2CPP 使用固定分代 GC”是错误写法。Unity 的内存管理取决于版本、平台、Player 设置与后端集成CoreCLR 的 SOH/LOH/POH、代际晋升和环境变量结论不能直接移植。本文只把 IL2CPP 定义为脚本 AOT/转换部署模型不用它推导某种 GC 算法。十、异常、包体、启动与稳态性能10.1 异常仍是语言/runtime 能力NativeAOT 与 IL2CPP 并非“没有异常”。try/catch/finally、托管异常与 stack unwinding 由各自 runtime 和原生平台支持差异可能出现在互操作边界、调试符号、stack trace 可读性、裁剪后的反射信息和平台限制。AOT 兼容错误也不是统一的MissingMethodException。可能在构建时报告分析警告/错误在原生链接时报缺少符号在运行时抛NotSupportedException、反射相关异常或业务异常。测试应断言行为和诊断而不是只捕获一个异常继续。10.2 四个指标不能合成“更快”指标JIT/ILR2RNativeAOTIL2CPP构建时间通常较低增加 crossgen2 工作增加闭世界分析与原生链接C 生成与平台编译可能显著包体IL/运行时部署方式决定预编译代码常增大程序集裁剪可减少托管闭包但原生代码/元数据仍占空间受 stripping、generic code、C 链接与引擎模块影响启动可能支付 JIT可减少部分启动 JIT无运行时 JIT无运行时 JIT稳态tiering/动态 PGO 可优化热点热点可重新 JIT依赖静态 profile、AOT 优化和硬件策略依赖 IL2CPP、原生编译器及生成形态这里没有固定胜者。R2R 可能增加磁盘与工作集NativeAOT 裁剪可能被大量反射保留抵消IL2CPP 值类型泛型组合可能增加生成代码JIT 的启动成本也可能被长生命周期服务摊薄。比较时至少测可执行与依赖总大小、冷/热启动、首请求、峰值 RSS、关键路径 p50/p95/p99、分配/GC、稳态吞吐和构建耗时。不同 RID 的产物不能只比较主 executable 文件。十一、可运行能力探针与发布实验下面探针验证动态能力、基础反射、闭合泛型和表达式解释。它不会替代完整兼容性测试但能揭示“开发环境 JIT 成功、发布产物失败”的差异。!-- AotProbe.csproj -- Project SdkMicrosoft.NET.Sdk PropertyGroup OutputTypeExe/OutputType TargetFrameworknet8.0/TargetFramework LangVersion12/LangVersion Nullableenable/Nullable EnableTrimAnalyzertrue/EnableTrimAnalyzer EnableAotAnalyzertrue/EnableAotAnalyzer /PropertyGroup /Projectusing System; using System.Collections.Generic; using System.Linq.Expressions; using System.Runtime.CompilerServices; Console.WriteLine($Framework: {System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription}); Console.WriteLine($RID: {System.Runtime.InteropServices.RuntimeInformation.RuntimeIdentifier}); Console.WriteLine($DynamicSupported{RuntimeFeature.IsDynamicCodeSupported}); Console.WriteLine($DynamicCompiled{RuntimeFeature.IsDynamicCodeCompiled}); Type model typeof(ProbeModel); Console.WriteLine($Ctor{model.GetConstructor(Type.EmptyTypes) is not null}); Console.WriteLine($Properties{model.GetProperties().Length}); object knownClosedGeneric new ListProbeModel(); Console.WriteLine(knownClosedGeneric.GetType()); ExpressionFuncint, int tree x checked(x * 2); Funcint, int operation RuntimeFeature.IsDynamicCodeSupported ? tree.Compile() : tree.Compile(preferInterpretation: true); Console.WriteLine(operation(21)); public sealed class ProbeModel { public int Id { get; set; } }以目标机器实际 RID 分别发布不要照抄示例 RID 到另一平台# 普通 framework-dependent JIT dotnet publish -c Release -r osx-arm64 --self-contained false # ReadyToRun CoreCLR dotnet publish -c Release -r osx-arm64 --self-contained false \ -p:PublishReadyToRuntrue # NativeAOT dotnet publish -c Release -r osx-arm64 \ -p:PublishAottrue \ -p:PublishTrimmedtrue裁剪没有作为项目全局发布属性启用这样前两条命令仍是可解释的 CoreCLR JIT/R2R 对照NativeAOT 命令单独启用 AOT/裁剪闭世界发布。PublishAot在 .NET 8 SDK 中本身会带入 NativeAOT 所需的裁剪行为这里显式写出PublishTrimmedtrue只是让实验变量可见。若还要比较“CoreCLR trimming”本身应增加一组独立的 self-contained trimmed 发布而不要把它混入普通 JIT 基线。实验步骤保存每次完整构建日志先消除 trim/AOT 警告不用NoWarn批量隐藏在干净目录运行发布产物不用dotnet run代替比较能力输出、产物清单、冷启动、内存和稳态增加一个字符串选择类型的反射用例观察分析器能否证明增加序列化、DI、泛型虚方法与本地库调用等真实路径对 R2R 用诊断工具确认哪些方法使用预编译体、哪些被 tiered JIT对 NativeAOT 保留 map/符号信息定位包体和异常栈。Unity 需另建矩阵不能拿上述 CoreCLR/NativeAOT 探针替代维度至少记录Unity2022.3.x 的完整补丁号、IL2CPP package/Editor 来源构建Development/Release、Script Debugging、代码剥离级别平台Editor 与目标 PlayerWindows/macOS/iOS/Android/WebGL 等实际目标原生架构、C compiler/linker、LTO/符号设置RuntimeAPI Compatibility、异常支持设置、GC/增量 GC 相关 Player SettingsBurstBurst 版本、AOT 设置、安全检查、Job/非 Job 路径在 Player 中跑相同业务 fixture序列化 round-trip、反射工厂、所有泛型组合、异常栈、原生回调和热路径。保留 stripping report、IL2CPP 构建日志与设备 Profiler capture。十二、设计与审查清单发布 JIT/R2R/NativeAOT/IL2CPP 共用库前逐项回答我们讨论的是公共 API 契约还是v8.0.0/Unity 2022.3 的实现R2R 产物是否仍允许 CoreCLR JIT 兜底与热点重编译动态路径需要“元数据保留”还是需要“生成新机器代码”抑或两者都要所有RequiresUnreferencedCode、RequiresDynamicCode和分析器警告是否已解释反射Type数据流能否用DynamicallyAccessedMembers表达DynamicDependency/descriptor 是否只保留最小已知成员而非把整个程序集变成根是否误把rd.xml、ILLink descriptor 与 Unitylink.xml当成同一语法运行时闭合泛型集合是否有限且在构建图中可见泛型虚方法组合是否实测Expression、dynamic、Emit 是否有 source-gen、解释器或显式 fallback序列化与 DI 是否启用其官方 AOT/source-generator 路径插件是在发布时已知、进程外、解释执行还是误以为 AOT 能加载未知 DLLIL2CPP 的保留与代码生成是否被错误混同是否出现“一类一 C 类”或固定 GC 推断包体是否统计全部依赖性能是否同时覆盖冷启动与稳态分位数测试是否运行最终 NativeAOT binary 或 IL2CPP Player而不只是 Editor/JIT真正的分界不只是“编译发生得早还是晚”而是哪些决定必须在发布时封闭哪些决定可以留到进程运行后。R2R 用预编译减少部分早期工作同时保留 CoreCLR 的动态后路NativeAOT 用闭世界换取无 JIT 的原生产物IL2CPP 把 Unity 托管程序转入平台 C 构建链。理解反射元数据、泛型代码、动态生成和裁剪的不同责任才能同时守住可部署性、包体与性能。下一篇Mono 与 CoreCLR运行时谱系、代码生成和 GC 边界
返回列表