IL是什么?
IL全称是Intermediate Language,一种属于通用语言架构和.NET框架的低阶(lowest-level)的人类可读的编程语言。目标是把.NET框架的语言编译成CIL,然后汇编成字节码。CIL类似一个面向对象的汇编语言,并且它是完全基于堆栈的,它运行在虚拟机上(.Net Framework, Mono VM)的语言。
具体过程是:C#或者VB这样遵循CLI规范的高级语言,会先被各自的编译器编译成中间语言:IL(CIL),等到需要真正执行的时候,这些IL会被加载到运行时库,也就是VM中,由VM动态的编译成汇编代码(JIT)然后再执行。
.NET compatible languages compile to asecond platform-neutral language calledCommon Intermediate Language (CIL).
NET兼容语言编译为第二平台-中立语言称为公共中间语言(CIL)。
The platform-specific Common LanguageRuntime (CLR) compiles CIL to machine-readable code that can be executed on thecurrent platform.
特定于平台的公共语言运行时(CLR)将CIL编译成可在当前平台上执行的机器可读的代码。
有了IL和VM的概念我们就不难发现,这两者并没有对应的VM虚拟机,Unity中VM只有一个:Mono VM,也就是说Boo和Unity Script是被各自的编译器编译成遵循CLI规范的IL,然后再由Mono VM解释执行。
本质上说,到了IL这一层级,它是由哪门高级语言创建的已经不是那么重要了,你可以用C#,VB,Boo,Unity Script甚至C++,只要有相应的编译器能够将其编译成IL都行。
IL2CPP,IL2CPPVM
IL2CPP:把IL中间语言转换成CPP文件。使用IL2CPP构建项目时,Unity会在创建本机二进制文件之前将脚本和程序集内的IL代码转换为C++。
IL2CPP 后端将 MSIL(Microsoft 中间语言)代码(例如,脚本中的 C# 代码)转换为 C++ 代码,然后使用 C++ 代码创建本机二进制文件(例如,.exe、.apk 或 .xap)以用于您选择的平台。
IL2CPP出现原因:
1.Mono VM在各个平台移植,维护非常耗时,有时甚至不可能完成
2.Mono版本授权受限
3.提高运行效率
使用Mono的时候,脚本的编译运行如下图所示:
3大脚本被编译成IL,在游戏运行的时候,IL和项目里其他第三方兼容的DLL一起,放入Mono VM虚拟机,由虚拟机解析成机器码,并且执行。
IL2CPP做的改变由下图红色部分标明:
在得到中间语言IL后,使用IL2CPP将他们重新变回C++代码,然后再由各个平台的C++编译器直接编译成能执行的原生汇编代码。
几点注意:
1.将IL变回CPP的目的除了CPP的执行效率快以外,另一个很重要的原因是可以利用现成的在各个平台的C++编译器对代码执行编译期优化,这样可以进一步减小最终游戏的尺寸并提高游戏运行速度。
2. 由于动态语言的特性,他们多半无需程序员太多关心内存管理,所有的内存分配和回收都由一个叫做GC(Garbage Collector)的组件完成。虽然通过IL2CPP以后代码变成了静态的C++,但是内存管理这块还是遵循C#的方式,这也是为什么最后还要有一个 IL2CPP VM的原因:它负责提供诸如GC管理,线程创建这类的服务性工作。但是由于去除了IL加载和动态解析的工作,使得IL2CPP VM可以做的很小,并且使得游戏载入时间缩短。
3.由于C++是一门静态语言,这就意味着我们不能使用动态语言的那些酷炫特性。运行时生 成代码并执行肯定是不可能了。这就是Unity里面提到的所谓AOT(Ahead Of Time)编译而非JIT(Just In Time)编译。其实很多平台出于安全的考虑是不允许JIT的,大家最熟悉的有iOS平台,在Console游戏机上,不管是微软的Xbox360, XboxOne,还是Sony的PS3,PS4,PSV,没有一个是允许JIT的。使用了IL2CPP,就完全是AOT方式了,如果原来使用了动态特性的 代码肯定会编译失败。这些代码在编译iOS平台的时候天生也会失败,所以如果你是为iOS开发的游戏代码,就不用担心了。因此就这点而言,我们开发上几乎 不会感到什么问题。
有了IL2CPP,程序尺寸可以相对缩小,运行速度可以提高。
IL2CPP 的工作原理
当您使用 IL2CPP 开始构建时,Unity 会自动执行以下步骤:
- Roslyn C# 编译器将应用程序的 C# 代码和任何所需的包代码编译为 .NET DLL(托管程序集)。
- Unity 应用托管字节码剥离。此步骤可以显着减小已构建应用程序的大小。
- IL2CPP 后端将所有托管程序集转换为标准 C++ 代码。
- C++ 编译器使用本地平台编译器编译生成的 C++ 代码和 IL2CPP 的运行时部分。
- Unity 根据您的目标平台创建可执行文件或 DLL。
IL2CPP 和 Mono 都提供了一些有用的选项,您可以使用脚本中的属性对其进行控制。有关更多信息,请参阅平台相关编译。
IL2CPP 使 Unity 能够为特定平台预编译代码。Unity 在此过程结束时生成的二进制文件已经包含目标平台所需的机器代码,而 Mono 必须在执行期间在运行时编译此机器代码。AOT 编译确实增加了构建时间,但它也提高了与目标平台的兼容性并可以提高性能。
两个脚本后端都需要针对您要定位的每个平台进行新构建。例如,要同时支持 Android 和 iOS 平台,您需要构建应用程序两次并生成两个二进制文件。
装配剥离阶段有助于减少最终的二进制大小。Unity 会删除最终构建的应用程序不使用的任何字节码。
配置
生成的 Visual Studio 项目有三种配置:Debug、Release 和 Master:
- Debug 配置会禁用所有优化、保留所有调试信息并且运行速度大幅降低。此配置用于调试游戏。
- Release 配置会启用大多数代码优化,但保持性能分析器为启用状态。此配置用于分析游戏性能。
- Master 配置会禁用性能分析器,用于游戏提交/最终测试。Master 配置的构建时间可能较长,但比 Release 配置快一点。
IL2CPP总结以及我们的建议
IL2CPP是Unity核心进行的很重要的进化之一。就现在来看,好处有以下几点:
1.运行速度加快,游戏安装尺寸减小,内存占用降低
2.除了可以Mono调试C#之外,我们又多了一种选择:Native C++ 源码级调试(不知道你们什么感觉,我对Unity C#调试颇有意见,经常连不上调试器,而且调试过程中常常宕机。换成用原生IDE调试C++代码,就爽很多啦)。
3.可以快速的支持新的平台,当然这点对我们关系不大。
带来的问题:
1.由于原来由Mono VM的IL代码全部变成了CPP,导致项目中多了很多CPP代码,编译时间会显著增加。
2.IL2CPP还有各种Bug,可能会导致原来的代码不能很好的编译运行。需要等待Unity版本迭代。
3.鉴于C++静态语言的特性,我们不能使用诸如System.Reflection.Emit这样的动态代码。(C# ATO方式编译)
参考: