Unity构建详解(11)——代码剥离

2024-04-29 08:52

本文主要是介绍Unity构建详解(11)——代码剥离,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

【前言】

我们知道代码在程序启动时全量加载到内存中的,一个库内的全部代码有些是不需要使用的,遵循减少内存的一个目标:内存中仅存在需要的数据,如果可以将这些不需要的代码去除,就可以减少内存,这就是代码剥离。

对于大型的项目而言,代码剥离可以减少几十M的常驻内存。

在Unity中,被剥离的是托管代码,并不是全部代码。使用 IL2CPP 编译时,托管代码剥离还可以减少构建时间,因为需要转换为 C++ 并进行编译的代码减少。托管代码剥离将从托管程序集中删除代码,托管程序集如下:

  • 游戏逻辑的C# 脚本构建的程序集
  • 插件中的程序集
  • NET 框架中的程序集,例如 mscorlib.dll 和 System.dll
  • Unity引擎程序集,包括构成 Unity 引擎的托管程序集,如 UnityEngine.Core.dll。

注意,程序集是C#中的概念。

【启动代码剥离】

使用项目的 Player Settings 中的 Managed Stripping Level 选项来控制 Unity 删除未使用代码的激进程度,有五个选项

  • Disabled:Unity不做任何的代码裁剪。这个配置只在我们使用Mono的时候可以选择,并且是默认配置。当选择 IL2CPP 脚本后端时,由于其对构建时间的影响,Disabled 选项不可用。托管代码越多意味着 IL2CPP 要生成的 C++ 代码越多,也意味着需要编译的 C++ 代码越多。
  • Minimal:Unity只会检测UnityEngine和.NET的代码。并不会移除任何用户代码。这个配置最不可能会导致意料之外的运行时表现,此配置多用于可用性优先级高于包体积的产品中。此配置是IL2CPP的默认配置
  • Low:Unity搜索部分用户程序集以及所有的UnityEngine和.NET代码。此设置应用一组规则,删除一些未使用的代码,但最大限度地减少出现意外后果的可能性,例如使用反射的运行时代码的行为变化。
  • Medium:Unity 部分地搜索所有程序集以查找无法访问的代码。此设置应用一组规则,该规则去除更多类型的代码模式,以减少生成大小
  • High:Unity对所有的程序集进行最广泛的检测,Unity 优先考虑缩小代码的大小,而不是代码的稳定性,并且尽可能多地删除代码

找到所有没有使用的托管代码是一件困难的事情,如果误删了代码,很可能导致运行时报错甚至崩溃。不同选项实际是在缩小代码大小和代码稳定性之间的权衡。

【保留和删除特定代码】

通常来说,如果程序不能自动解决某一问题,那么会留出接口和配置项供人为指导。Unity提供了根注释(Root Annotations)的方式来控制代码剥离时保留或删除特定代码。根注释强制Unity Linker把代码元素当作根元素,有两种方式进行根注释:

  • Preserve 特性 : 直接在源代码中标记要保留的元素
  • link.xml 文件:在文件中声明如何保留程序集中的元素

为什么是这两种方式,UnityLinker.exe是个可执行程序,程序提供配置功能,一般来说要有个配置文件,配置文件中的数据以什么样的形式来组织,一般来说不会闲着没事蛋疼或者炫技自己搞一个新的格式,都用已有的格式,这就是XML格式。

至于特性,这是因为UnityLinker会根据传入的dll路径,去加载dll分析,恰好可以利用C#特性来标记。

【使用Preserve特性标记】

该特性可以用于:

  • 程序集Assembly:将保留程序集中的所有类,在程序集中的任意命名空间前声明即可。
  • 类型Type:将保留该类及其默认构造器
  • 方法Method:将保留该方法及方法涉及的所有类
  • 属性Property:将保留该属性及属性涉及的所有类,对应的getter 方法以及 setter 方法
  • 字段Field:将保留该字段及其涉及的类
  • 事件Event:将保留事件及其涉及的所有类、对应的add 方法以及 remove 方法、
  • 委托Delegate:将保留委托类型及其涉及的所有类

【使用XML文件标记】

示例如下:

<linker><!--Preserve types and members in an assembly--><assembly fullname="Assembly1"><!--Preserve an entire type--><type fullname="Assembly1.A" preserve="all"/><!--No "preserve" attribute and no members specified means preserve all members--><type fullname="Assembly1.B"/><!--Preserve all fields on a type--><type fullname="Assembly1.C" preserve="fields"/><!--Preserve all fields on a type--><type fullname="Assembly1.D" preserve="methods"/><!--Preserve the type only--><type fullname="Assembly1.E" preserve="nothing"/><!--Preserving only specific members of a type--><type fullname="Assembly1.F"><!--Fields--><field signature="System.Int32 field1" /><!--Preserve a field by name rather than signature--><field name="field2" /><!--Methods--><method signature="System.Void Method1()" /><!--Preserve a method with parameters--><method signature="System.Void Method2(System.Int32,System.String)" /><!--Preserve a method by name rather than signature--><method name="Method3" /><!--Properties--><!--Preserve a property, it's backing field (if present), getter, and setter methods--><property signature="System.Int32 Property1" /><property signature="System.Int32 Property2" accessors="all" /><!--Preserve a property, it's backing field (if present), and getter method--><property signature="System.Int32 Property3" accessors="get" /><!--Preserve a property, it's backing field (if present), and setter method--><property signature="System.Int32 Property4" accessors="set" /><!--Preserve a property by name rather than signature--><property name="Property5" /><!--Events--><!--Preserve an event, it's backing field (if present), add, and remove methods--><event signature="System.EventHandler Event1" /><!--Preserve an event by name rather than signature--><event name="Event2" /></type><!--Examples with generics--><type fullname="Assembly1.G`1"><!--Preserve a field with generics in the signature--><field signature="System.Collections.Generic.List`1&lt;System.Int32&gt; field1" /><field signature="System.Collections.Generic.List`1&lt;T&gt; field2" /><!--Preserve a method with generics in the signature--><method signature="System.Void Method1(System.Collections.Generic.List`1&lt;System.Int32&gt;)" /><!--Preserve an event with generics in the signature--><event signature="System.EventHandler`1&lt;System.EventArgs&gt; Event1" /></type><!--Preserve a nested type--><type fullname="Assembly1.H/Nested" preserve="all"/><!--Preserve all fields of a type if the type is used.  If the type is not used it will be removed--><type fullname="Assembly1.I" preserve="fields" required="0"/><!--Preserve all methods of a type if the type is used.  If the type is not used it will be removed--><type fullname="Assembly1.J" preserve="methods" required="0"/><!--Preserve all types in a namespace--><type fullname="Assembly1.SomeNamespace*" /><!--Preserve all types with a common prefix in their name--><type fullname="Prefix*" /></assembly><!--Preserve an entire assembly--><assembly fullname="Assembly2" preserve="all"/><!--No "preserve" attribute and no types specified means preserve all--><assembly fullname="Assembly3"/><!--Fully qualified assembly name--><assembly fullname="Assembly4, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"><type fullname="Assembly4.Foo" preserve="all"/></assembly><!--Force an assembly to be processed for roots but don’t explicitly preserve anything in particular.  Useful when the assembly is not referenced.--><assembly fullname="Assembly5" preserve="nothing"/></linker>

这些不用我们手动修改XML文件,可以在代码中修改,一般类是默认保留所有,即preserve="all"。在Addressable中提供了LinkXmlGenerator类来修改XML文件,其基本将涉及到游戏资源的相关逻辑需要的类型囊括进去了。随后继承IUnityLinkerProcessor,返回生成的link.xml文件路径给UnityLinker.exe,可以返回多个link.xml,其内部会合并处理的。

【UnityLinker工作原理】

C#代码经过编译后生成的程序集中的代码是中间语言代码,C# IL Linker可以对所有程序集进行静态分析,找出所有未被使用的程序集、类型、方法、属性、字段等,并删除他们以减少应用程序的大小。

UnityLinker基于IL Linker做了自定义处理,由于是自己做的,也会加上引擎代码的剥离,但这部分不公开。

Unity编辑器会创建一个程序集列表,其中包含 Unity 项目中的任何场景中使用的类型,并将此列表传递给 Unity 链接器。UnityLinker会分析所有的程序集,找到所有根元素,基于IL的分析,可以找到所有被根元素使用的类型、方法、属性等,并标记。没有被标记的将会从程序集中删除。

【参考】

托管代码剥离 - Unity 手册

关于优化的二三事:用Unity 2020 LTS更好地管理代码剥离 | Unity Blog

这篇关于Unity构建详解(11)——代码剥离的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/945642

相关文章

PHP轻松处理千万行数据的方法详解

《PHP轻松处理千万行数据的方法详解》说到处理大数据集,PHP通常不是第一个想到的语言,但如果你曾经需要处理数百万行数据而不让服务器崩溃或内存耗尽,你就会知道PHP用对了工具有多强大,下面小编就... 目录问题的本质php 中的数据流处理:为什么必不可少生成器:内存高效的迭代方式流量控制:避免系统过载一次性

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

MySQL的JDBC编程详解

《MySQL的JDBC编程详解》:本文主要介绍MySQL的JDBC编程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、前置知识1. 引入依赖2. 认识 url二、JDBC 操作流程1. JDBC 的写操作2. JDBC 的读操作总结前言本文介绍了mysq

Python实现Excel批量样式修改器(附完整代码)

《Python实现Excel批量样式修改器(附完整代码)》这篇文章主要为大家详细介绍了如何使用Python实现一个Excel批量样式修改器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录前言功能特性核心功能界面特性系统要求安装说明使用指南基本操作流程高级功能技术实现核心技术栈关键函

Redis 的 SUBSCRIBE命令详解

《Redis的SUBSCRIBE命令详解》Redis的SUBSCRIBE命令用于订阅一个或多个频道,以便接收发送到这些频道的消息,本文给大家介绍Redis的SUBSCRIBE命令,感兴趣的朋友跟随... 目录基本语法工作原理示例消息格式相关命令python 示例Redis 的 SUBSCRIBE 命令用于订

使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解

《使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解》本文详细介绍了如何使用Python通过ncmdump工具批量将.ncm音频转换为.mp3的步骤,包括安装、配置ffmpeg环... 目录1. 前言2. 安装 ncmdump3. 实现 .ncm 转 .mp34. 执行过程5. 执行结

Python中 try / except / else / finally 异常处理方法详解

《Python中try/except/else/finally异常处理方法详解》:本文主要介绍Python中try/except/else/finally异常处理方法的相关资料,涵... 目录1. 基本结构2. 各部分的作用tryexceptelsefinally3. 执行流程总结4. 常见用法(1)多个e

SpringBoot日志级别与日志分组详解

《SpringBoot日志级别与日志分组详解》文章介绍了日志级别(ALL至OFF)及其作用,说明SpringBoot默认日志级别为INFO,可通过application.properties调整全局或... 目录日志级别1、级别内容2、调整日志级别调整默认日志级别调整指定类的日志级别项目开发过程中,利用日志

Java中的抽象类与abstract 关键字使用详解

《Java中的抽象类与abstract关键字使用详解》:本文主要介绍Java中的抽象类与abstract关键字使用详解,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、抽象类的概念二、使用 abstract2.1 修饰类 => 抽象类2.2 修饰方法 => 抽象方法,没有