近期,有不少Unity开发者咨询有关Asset Bundle和旧有资源(Resources)系统的问题:为何Asset Bundle加载Asset时消耗的内存要比旧有资源多。

首先说明一下,事实并非如此。或者说从长远看,如果用好Asset Bundle中旧有资源系统Resources不具备的新特性,Asset Bundle的内存消耗会小的多。如果您不熟悉Asset Bundle,可以参考Unity手册和Asset Bundle与资源指南。

详细问题
根据我们收到的一些Bug报告,基本上都在说同一件事情:当从一个Asset Bundle加载某个Asset时,内存使用量会突增几兆,但在使用资源时却没发生这样的情况。重现这些Bug时,我们看到的结果也非常相似:启动时内存正常,载入Asset后内存激增,并且不会回落到原来的水平。

094635szoyaj0duogysj00.png
Asset Bundle的内存使用情况

094640e420b27fmg37gal4.png
Resources的内存使用情况
下面我们就通过内存系统及其关联关系、数据保存方式、内存使用量数值的含义和内存使用效率几个方面,为大家剖析一下这个问题的原理,帮助您更好地理解。注意:本文使用的Unity版本为Unity 5.5.0f3。

问题剖析

内存系统及关联
首先来了解提供这些数字的系统,以及它们之间的关联。

Unity的原生内存系统会使用多种大小在1MB到32MB(平均1MB到4MB)之间固定大小的块内存分配器。具体大小根据分配的工作类型而定,例如主线程还是后台线程;或者根据当前运行的平台。

保留总量(Reserved Total)是操作系统分配的所有块的总量,已用总量(Used Total)是其中正由Unity使用的内存量。每个区域标签、FMOD、Porfiler等等,表示系统列出的相应分配器或大概的外部内存。您可从内存分析器手册页面了解有关区域标签的信息。在手册页面之外还有一些方面需要注意:在撰写本篇博文时(Unity 5.5.0f3),已用和保留的总数并不包括FMOD的值。我们已在处理这个问题。

总系统内存使用量(Total System Memory Usage)是由平台系统提供的虚拟内存大小,不支持此功能的平台会显示为0。最后,已用总数并不包括对象的头部或字节对齐,而保留总量包括这些部分。因此,要对比Asset Bundle和资源间的内存使用量,我们会主要关注已用总量和保留总量中的Unity区域标签。

数据保存方式
另外,了解Asset Bundle以及资源数据在磁盘上的保存方式对于理解分析器的原理也十分必要。

资源和Asset Bundle在底层数据结构上非常相似,它们都有一个用于存放每个对象序列化数据的文件,一些用于高效异步加载(纹理、音频等)的额外资源文件,以及一个包含序列化对象Asset文件路径映射表。Asset Bundle将这些文件都打包在一个压缩包中,映射表则储存在Asset Bundle对象的序列化数据中。资源将其映射表保存在一个名为ResourceManager的全局单例中,其他文件则散落在磁盘上。此外,与资源系统不同的是,Asset可以分散在不同的Asset Bundle中,因此可以通过仅加载数据子集来最大限度地减少内存使用量。

094640ebv3ub1ensbhu3ba.png
Asset Bundles

094640gcvdcgw2w2wvjldu.png
资源

内存使用量数值含义
了解Unity的内存与文件系统之后,我们再详细解释下这些内存使用量数值的意义。

第一个突出的问题是Unity中用于Asset Bundle的保留区域增长了10MB,而使用资源系统时则没有。什么原因呢?这主要是因为前面提及的块分配器。就这个特定的内存使用率测试而言,我们使用的是采用异步加载API协程的AsyncBundleLoader.cs行为。值得注意的是这种组合实际上使用了不同的分配器,而且这些分配器在此时其实尚未被使用。

所以,10MB的增量来源于两个分配器初始化内存块的动作,其中一个分配器需要为新对象分配更多内存,所以它分配了一个4MB的内存块。两个新分配器中,一个为Asset Bundle异步加载分配了一个2MB的内存块,另一个则为类型树(后面再详细介绍类型树)分配了一个4MB的内存块。这些内存块的大小是专为同时加载多个Asset与Asset Bundle而优化的。例如,您可以同时从4-5个Asset Bundle中加载对象,而无须为Asset Bundle异步加载或需要新块的类型树创建新的分配器。当然,这具体还是要根据Asset Bundle的大小,采取的压缩方式,以及这些包中所使用的唯一脚本类型的数量而定。

在这些分配的块中,用于类型树的4MB内存块,仅在从Asset Bundle加载对象时使用。操作完成后这个块应当会恢复。但是,由于示例中构建协程的方式,导致AssetBundleRequest对象一直处于作用域中,没有被垃圾回收器清除;而用于Asset Bundle异步加载的2MB内存块,它是读取Asset Bundle档案时的缓冲区,在没有包的内部引用后会被释放。最后的4MB内存块的使用者是负责我们所有对象存储的主分配器,因此不会被释放。

通常在一个项目中,对象的创建/删除非常频繁,我们会使用内存池来重用内存而非将其释放回分配器。观察最终卸载后保留区域的Unity数值时,您会发现使用Asset Bundle(64.1MB)与使用资源系统(63.3MB)的差异很小,仅与分配器获得新块的顺序有关。

内存使用效率
我们一直都在讨论保留内存,那Asset Bundle与资源之间的保留内存实际使用效率差别又有多大呢?

这个问题非常简单,因为已使用中的Unity区域已经告诉了我们答案。使用Asset Bundle时,占用了保留内存中的21.7MB,而使用资源时稍多,大概在22.2MB。此外,在卸载时,这个内存数值分别下降为20.7MB和21.2MB。所以,很显然Asset Bundles是内存利用效率方面的赢家。

您可能已经注意到,Asset Bundle的使用量在卸载后要比其启动时更大(4.4MB)。这是因为前面提到的内存池的关系,因此如果您重新载入Asset Bundle与Asset,它将会回到21.7MB。而对于资源来说,启动与卸载时的内存差异来源于舍入误差。对于Asset Bundle的块分配可以减少,但是要牺牲性能以及向下兼容性。正如上面提到的,为了满足加载对象所需的内存量,所以必须要分配4MB大小的内存块。剩下的6MB中,2MB用于了异步加载API。

因此,为了防止块分配,只要牺牲FPS使用同步API即可。最后的4MB是源于类型树的分配,正如前面提到的,我们会讲的更细一点。这个系统会在Asset Bundle里储存在资源系统中没有的额外数据,这些数据使Asset Bundle可以兼容更多版本的Unity,并使诸如FormerlySerializedAs这样的序列化特性正常工作。这使您可以在升级到更新的Unity版本后依然能使用相同的Asset Bundle,或仅需修改少量代码,而非重新构建它们,导致用户因为您所做的更改必须重新下载整个Asset Bundle。向BuildPipeline.BuildAssetBundlesAPI传入BuildAssetBundleOptions.DisableWriteTypeTree选项,可以禁止写入这个额外数据。

094640ixrmr8rn3metye3m.png
无类型树Asset Bundle同步加载
您可以用Asset Bundles 1, Resources 0试试。如果您想生成自己的数据,相关脚本已经上传到了 Github Gist,您可点击【阅读原文】下载。目前它设置为每类创建100个:纹理、Monobehavior、预制件、一个固定的随机种子,所以您在自己机器上每次运行都会生成同样的输出(但可能会和别人的不一样)。请确保您的Asset Bundle项目中没有意外包含了一个有内容的Resources文件夹,否则您的内存数值将会比预想高出两倍。试试将每类资源的数量提高至300或甚至500个进行测试。

总结
不知上述内容是否对您项目的内存管理带来一些启发呢?如果您有任何疑问或者建议,请留言。或来到Unite 2017 Shanghai大会现场,与Unity技术专家们进行深入讨论。

更多Unity相关技术文章
Unity WebGL内存详解
Unity编辑器中使用GitHub管理项目
Draw Call未被批处理,在Unity 5.6中如何查找原因
Unity开发2.5D游戏的精灵层级顺序设置
Unity 5的光照魅力

Unity官方活动
Unite 2017 Shanghai即将于5月开幕,Keynote主题演讲和四大技术专场会为您带来全新的Unity技术盛宴。66折技术通票售票期仅剩11天,请访问大会官网(http://unite2017.csdn.net/)了解更多大会信息,并可免费抢注Keynote主题演讲门票!

095728k55d735q9t059ll9.png
原文链接:https://blogs.unity3d.com/cn/2017/04/12/asset-bundles-vs-resources-a-memory-showdown/
原文作者:Ryan Caltabiano
感谢Unity官方翻译组对本文的贡献(如何加入翻译组)
转载请注明来源:Unity官方中文社区(forum.china.unity3d.com)。请勿私自更改任何版权说明信息。 Asset Bundles, Resources, 内存锐亚教育

锐亚教育 锐亚科技 unity unity教程