3. IL2CPP & Mono

在iOS和Android上,可以通过Player Settings里面选择Mono或者IL2CPP作为脚本后端。如果需要改变脚本后端,到Player Settings窗口(具体的菜单:Edit > Project Settings > Player), 向下滚动到Other Settings区域,然后从下拉菜单中选择Mono或者IL2CPP。

注意:使用2017.3的版本,选择IL2CPP脚本后端或者Mono后端都可以。然而,WebGL和UWP只支持IL2CPP。iOS在快速开发阶段仍然支持Mono脚本后端,但是不能再向Apple提交Mono(32位)的应用。

不同脚本后端的优缺点

每个脚本后端都有自己的优点和不足,当选择脚本后端的时候,这些因素需要被考量:

IL2CPP

  • 和Mono相比,代码生成被大力改进
  • 可以从C++代码中能够从上到下进行调试
  • 可以开启Engine code stripping选项来减少代码大小
  • 打包的过程要比Mono时间长
  • 只支持预编译(Ahead of Time AOT)

Mono

  • 打包过程比IL2CPP快上不少
  • 因为即时编译(Just In Time compilation JIT)能够支持更多的托管代码库
  • 支持运行过程中代码执行
  • 必须搭载托管的程序集(通过mono- 或者 .net- 产生的.dll文件)

小提示:在开发阶段和最后发行阶段使用IL2CPP后端。如果在迭代阶段发现使用IL2CPP太慢,暂时切换到Mono脚本后端提高迭代速度。

注意:Player Settings中的默认目标架构是针对发行的编译进行优化的。在开发阶段使用默认选项会增加编译的时间因为Unity会针对选择的每个目标平台进行编译:

  • Android的Player Settings中默认目标架构是armv7 和 x86 with the IL2CPP 和 Mono脚本后端。
  • iOS的Player Settings中默认目标架构是armv7 和 IL2CPP脚本后端的arm64。

Unity中的代码剥离

代码大小对硬盘空间和运行内存有着直接的影响。所以对于Unity而言,从代码库中移除用不上的代码非常重要。Unity会在构建过程中自动剥离代码,在两个不同层面进行:

  • 托管代码的剥离
  • 原生代码的剥离

托管代码剥离

Unity在方法层面对托管代码进行剥离。如果想要改变剥离层面,可以在Player Settings窗口,向下拖动到Other Settings部分,定位到Stripping Level,选择Strip Assemblies

UnityLinker会通过中间语言(IL, Intermediate Language)移除掉没有使用的类型(包括类,结构体等)。即使你使用了某个类型,UnityLinker也会移除掉这个类型中没有使用的方法。

注意: 尽管这个功能在使用Mono脚本后端编译的时候是可选的,当使用IL2CPP脚本后端编译的时候永远开启。

原生代码剥离

Unity默认开启PlayerSettings中的Strip Engine Code选项并且开启原生代码剥离。开启Strip Engine Code可以移除原生的Unity引擎代码中未使用到的模块和类。如果禁用Strip Engine Code则会保留原生Unity引擎代码中所有的模块和类型。

注意::对于目前公开可获取的所有平台,原生代码剥离只在iOS、WebGL和Android平台上被支持。

从Unity 2017.3版本开始支持Android平台的原生代码剥离;而在之前的版本,Unity的运行时作为提前链接的.so库,这样的话Unity就不能够进行剥离。在2017.3版本之后,Android的运行时是作为静态的引擎代码库,支持进行原生代码剥离。最后的链接过程发生的构建(Build)阶段,对构建的时间会有略微的影响。

Unity 模块剥离

注意:WebGL是目前唯一支持剥离未使用到的Unity模块的平台。

Unity作最大的努力尝试移除掉所有未被使用的Unity模块。意味着,只要在构建过程中的场景中使用或者脚本引用到了任何Unity模块中的组件,这个模块的代码就不会被移除掉。Unity目前不会剥离关键模块,如Camera,AssetBundle,Halo等,但是在未来的发行版本中,这些也会被剥离掉。

在WebGL平台上从一个空项目中剥离模块代码

移除掉模块代码能够节省大量的内存。例如,Unity模块中最大的一个就是物理模块,包含差不多5MB的压缩后的ASM.js代码。如果你从空项目中移除掉这个模块,最后打包的体积会从17MB降到15MB。

C#代码剥离

Unity Linker在基本的标记-清除算法上工作,类似于垃圾回收。Unity Linker会从单次Build过程中构建每个程序集中包含的类型和方法的映射。UnityLinker会将部分类型和方法标记为为“根”,然后遍历类型和方法之间的依赖关系图。

例如,当一个类型的方法调用了另外一个类型中的方法,UnityLinker就会标记被调用的方法为“使用中”。一旦UnityLinker标记了所有根的依赖,系统就会重新组织程序集,移除掉没有被使用的方法或者整个类型。

场景,资源,程序集和AssetBundle中的根

如果类在场景或者Resources中直接引用,UnityLinker会将这些内置类作为根。类似地,UnityLinker也会将用户程序集中使用的所有类和方法作为根。

如果你在场景中或者包括在Resources中的Asset中使用了来自其他程序集的类型或者方法,Unity也会将这些作为根。

使用 link.xml文件来标记额外的类型和方法作为根。如果你的工程使用了AssetBundle,可以使用BuildPlayerOption.assetBundleManifestPath来标记额外的类型和方法作为根。

用户程序集

用户程序集指的是在Assets目录下Unity从松散的代码中生成的程序集。Unity会将大部分的代码放在Assembly-CSharp.dll中;然而,Unity会将放在/Assets/Standard Assets/或者/Assets/Plugins中的代码放在Assembly-CSharp-firstpass.dll,这个DLL也被认为是用户程序集。

如果代码库中很大一部分的类型和方法没有用到,你可以通过将稳定的代码移动到提前编译的程序集中,这样UnityLinker可以剥离这些代码中没有没使用到的类型和方法,这样可以减少二进制包体和节约打包时间。使用Assembly Definition Files参考将稳定的代码迁移到预编译的程序集中。

通用共享

对于引用类型而言,IL2CPP对应生成的C++代码可以使用引用类型在生成的IL2CPP代码中共享。然而,IL2PP并不会共享值类型,因为IL2CPP需要针对每个类型独立生成代码。这样就会导致代码体积增大。

通常来讲,这样不会导致很大的性能差异,但是这取决于特定的情况和需要被优化的具体情况。类通常位于堆上,而结构体则通常位于栈上(有一些特殊情况,比如协程的情况)。对于内存性能和使用而言,使用空引用会导致其他的问题。你必须使用值类型来拷贝函数参数来改变性能。对于额外的信息,可以参阅如下的博客 blog post。需要指出,Integer或者Enum类型目前是没有被共享的。

程序集定义文件

Assembly Definition Files允许你自定义托管程序集并且将脚本指定给某个程序集(以文件夹为单位)。

这样做可以导致更快的迭代速度,因为Unity只需要构建那些被改动过脚本的程序集即可。

注意:尽管多个程序集能够保证模块性,但是同时也增加了应用的大小和运行内存。测试显示每个程序集会增加4kB的运行内存。

构建报告

Build Report目前是包含在Unity内部的一个API,目前还没有UI部分的支持。构建一个工程会产生一个buildreport文件,通过这个文件你可以发现那些部分被剥离并且为什么会从最后的执行文件中剥离。

剥离相关的信息查看步骤:

  1. 构建工程
  2. 保持编辑器运行
  3. 连接上http://files.unity3d.com/build-report/.

构建报告工具会连接上正在运行的编辑器,下载并且呈现出构建日志的分解。

也可以通过binary2text工具查看Library/LatestBuild.buildreport的数据。Binary2text的目录:
Mac : Unity.app/Contents/Tools
Windows : Unity/Editor/Data/Tools
构建报告在Unity 5.5之后的版本中获取·。

你可能感兴趣的:(3. IL2CPP & Mono)