Unity Addressable内存管理(1.18.4文档翻译)


摘自 Memory management | Addressables | 1.16.19 (unity3d.com)

一些混用的词:

Addressables Asset :可寻址资产

 

AssetBundle Memory Overhead

When deciding how to organize your Addressable groups and AssetBundles, you may want to consider the runtime memory usage of each AssetBundle. Many small AssetBundles can give greater granularity for unloading, but can come at the cost of some runtime memory overhead. This section describes the various types of AssetBundle memory overhead.

AssetBundle 内存开销

当决定如何组织管理你的Addressable Groups 和 AssetBundles 时,你可能会考虑到每个Assetbundle在运行时的内存使用。多且小的Assetbundle可以给你更好(小)的粒度用于卸载,但同时也会带来一些运行时的内存开销。以下小节会描述几种不同类型的AssetBundle内存开销。

 

 

Serialized File Overhead

When Unity loads an AssetBundle, it allocates an internal buffer for each serialized file in the AssetBundle. This buffer persists for the lifetime of the AssetBundle. A non-scene AssetBundle contains one serialized file, but a scene AssetBundle can contain up to two serialized files for each scene in the bundle. The buffer sizes are optimized per platform. Switch, Playstation, and Windows RT use 128k buffers. All other platforms use 14k buffers. You can use the Build Layout Report to determine how many serialized files are in an AssetBundle.

Each serialized file also contains a TypeTree for each object type within the file. The TypeTree describes the data layout of each object type and allows you to load objects that are deserialized slightly differently from how they were serialized. All the TypeTrees are loaded when the AssetBundle is loaded and held in memory for the lifetime of the AssetBundle. The memory overhead associated with TypeTrees scales with the number of unique types in the serialized file and the complexity of those types. Although you can choose to ship AssetBundles without TypeTrees, be aware that even engine version patches can slightly change the serialization format and could result in undefined behavior when you use a newer runtime to load assets serialized with an older format; Unity recommends always shipping AssetBundles with TypeTree information, which is the default behavior.

When you put objects of the same type in more than one AssetBundle, the type information for those objects is duplicated in the TypeTree of each AssetBundle. This duplication of type information is more noticeable when you use many small AssetBundles. You can test the impact that TypeTrees have on the size of your AssetBundles by building them with and without TypeTrees disabled and comparing the sizes. If after measuring, you find the duplicate TypeTree memory overhead to be unacceptable, you can avoid it by packing your objects of the same types in the same AssetBundles.

 

序列化文件开销

当Unity加载一个AssetBundle时,会为AssetBundle里的每一个序列化文件分配一个内存缓冲区(加载AssetBundle的时候会产生SerializedFile)。这个缓冲区会持续存在在该AssetBundle的生命周期当中(直到这个AssetBundle被Unload掉)。一个非场景AssetBundle包含一个序列化文件,但场景AssetBundle中每个scene可以最多包含多达2个SerializedFile。这个缓冲区的大小根据不同平台做了优化。Switch,Playstation和Windows RT使用128k的缓冲区,其他的平台使用14k的缓冲区。你可以使用 Build Layout Report  查看AssetBundles里包含了多少个serialized files文件。

每个serialized file中包含了AB包里每个object type的Type Tree。TypeTree 描述了每个object type的数据布局,并允许你使用与序列化方式稍有不同的反序列化方式加载objects。当AssetBundle被加载时,所有的TypeTrees都会被加载,并且在AssetBundle的生命周期中一直被持有。TypeTrees的内存开销与类型数量和这些类型的复杂程度相关。虽然你可以选择不适用TypeTrees的方式打包AssetBundle,但要注意即使引擎的版本补丁也可能对序列化格式有略微的变动,并在你使用新的运行时去序列化旧格式并加载资源的时候导致一些未知的行为。Unity建议打包的时候总是将TypeTree信息一起打包进去,即使用默认配置。

当你将同一类型对象重复的放入到不同的AssetBundles里去时,就会在不同AssetBundle的TypeTree里产生重复的类型信息。当你使用多且小的AssetBundle时,更需要注意这些信息。你可以通过禁用与不禁用TypeTree的方式来测试他们对AssetBundle大小的影响。如果在测试后,你发现这些重复的开销是不可接受的,你可以通过将这些同类型对象放入到同一个AssetBundle的方式来避免这个问题。

 

AssetBundle Object

The AssetBundle object itself has two main sources of runtime memory overhead: the table of contents, and the preload table. While the size of an AssetBundle on disk is not the same as its size at runtime, you can use the disk size to approximate the memory overhead. This information is located in the Build Layout Report.

The table of contents is a map within the bundle that allows you to look up each explicitly included asset by name. It scales linearly with the number of assets and the length of the string names by which they are mapped.

The preload table is a list of all the objects that a loadable asset references. This data is needed so Unity can load all those referenced objects when you load an asset from the AssetBundle. For example, a prefab would have a preload entry for each component as well as any other assets it may reference (materials, textures, etc). Each preload entry is 64 bits and can reference objects in other AssetBundles.

As an example, consider a situation in which you are adding two Assets to an AssetBundle ( Prefab A and Prefab B) and both of these prefabs reference a third prefab (Prefab C), which is large and contains several components and references to other assets. This AssetBundle has two preload tables, one for and one for . Those tables contain entries for all the objects of their respective prefab, but also entries for all the objects in and any objects referenced by . Thus the information required to load ends up duplicated in both and . This will happen whether or not is explicitly added to an AssetBundle.

Depending on how you organize your assets, the preload tables in AssetBundles could become quite large and contain many duplicate entries. This is especially true if you had several loadable assets that all reference a complex asset, such as in the situation above. If you determine that the memory overhead from the preload table is a problem, you can structure your loadable assets so that they have fewer complex loading dependencies.PrefabC

 

资产堆对象

AssetBundle 对象主要有两方面的运行时内存开销:1、内容表; 2、预加载表。虽然在磁盘上的大小和在运行时的大小不同,但你可以通过磁盘大小大致的估算实际内存开销。该信息位于 Build Layout Report

内容表是一个位于bundle里的,允许你通过名字精确查询每个被包含资产的Map。随着资产的数量,以及在Map里名字长度的大小,它的大小呈线性变化。

预加载表是一个可加载资产引用的所有对象的列表。这是一个必要的数据,Unity可以根据个列表加载出这个AssetBundle里所有引用到的对象。例如,一个prefab会有为提供给每个component和其他引用到的资源(类似materials, textures)的预加载条目。每个预加载条目为64bits,并且可以引用其他AssetBundle里的对象。

打个比方,在某种情况下,你将两个Assets(Prefab A 和 Prefab B)加入到一个AssetBundle里,并且这些prefab都引用了第三个prefab(Prefab C),并且这个prefab很大,还包含了好几种component和对其他Asset的引用。这个AssetBundle有两个preload table,一个给Prefab A,一个给Prefab B。这些table包含了各自预制体里引用对象的条目,但同时也包含了Prefab C与任何Prefab C里所引用对象的条目。因此加载Prefab C的所需信息就会重复的出现在Prefab A和Prefab B里。无论Prefab C是否被添加到一个AssetBundle里,这种情况都会出现。

取决于你如何管理组织你的Assets,AssetBundle里的preload tables可能会变得非常大,并且包含了许多重复信息,尤其当你的一些可加载assets引用了复杂的asset时,就像是上例情况中的Prefab C,情况更为明显。如果你已查明preload table的内存开销是有问题的,你可以再构建你的可加载Assets以减少一些复杂的引用。

 

AssetBundle dependencies

Loading an Addressable Asset loads all the AssetBundle dependencies and keeps them loaded until you call Addressables.Release on the handle returned from the loading method.

AssetBundle dependencies are created when an asset in one AssetBundle references an asset in another AssetBundle. An example of this is a material referencing a texture. The dependencies of all these AssetBundles can be thought of as a dependency graph. During the catalog generation stage of the build process, Addressables walks this graph to calculate all the AssetBundles that must be loaded for each Addressable Asset. Because dependencies are calculated at the AssetBundle level, all Addressable Assets within a single AssetBundle have the same dependencies. Adding an Addressable Asset that has an external reference (references an object in another AssetBundle) to an AssetBundle adds that AssetBundle as a dependency for all the other Addressable Assets in the AssetBundle.

For Example:

BundleA contains Addressable Assets RootAsset1 and RootAsset2RootAsset2 references DependencyAsset3, which is contained in BundleB. Even though RootAsset1 has no reference to BundleBBundleB is still a dependency of RootAsset1 because RootAsset1 is in BundleA, which has a reference on BundleB.

Prior to 1.13.0, the dependency graph was not as thorough as it is now. In the example above, RootAsset1 would not have had a dependency on BundleB. This previous behavior resulted in references breaking when an AssetBundle being referenced by another AssetBundle was unloaded and reloaded. This fix may result in additional data remaining in memory if the dependency graph is complex enough.

 

资产堆依赖关系

加载一个Addressable Asset也会加载所有的AssetBundle以来,并且持有他们直到你调用了Addressables.Release去释放通过加载方法返回的handle句柄(或者实例)。

当一个AssetBundle引用了其他AssetBundle里的资源的时候就会产生AssetBundle依赖。一个类似的例子就是material引用了texture。这些AssetBundles的依赖关系可以被视为一张依赖图。在构建过程的目录生成阶段,Addressables遍历这张图表以计算每个Addressable Asset所需要加载的Assetbundle。由于依赖性是在AssetBundle级别计算的,因此单个AssetBundle中的所有可寻址资产都具有相同的依赖性。将具有外部引用(引用另一个AssetBundle中的对象)的可寻址资产添加到AssetBundle会将该Asset Bundle添加为AssetBundle中所有其他可寻址资产的依赖项。

例子:

BundleA 包含了可寻址资产 RootAsset1和RootAsset2。RootAsset2引用了DependencyAsset3,其被包含在BundleB。即使RootAsset1并没有对BundleB的引用,BundleB仍是RootAsset1的引用,因为RootAsset1在BundleA中,并且BundleA对BundleB有引用。

在1.13.0之前,依赖关系图并不像现在这么彻底。在上面的示例中,RootAsset1并不会依赖BundleB。先前的方式会导致在卸载并重新加载另一个AssetBundle引用的AssetBundle时,引用中断。但该修复可能会引起额外不必要的数据保留在内存中,如果依赖图过于复杂的话。

 

Duplicate dependencies

When exploring memory management and dependency graphs, it's important to discuss duplicated content. There are two mechanisms by which an asset can be built into an AssetBundle: explicit and implicit. If you mark an asset as Addressable, it is explicitly put into an AssetBundle. That is the only AssetBundle it is in.

Example: A material has a direct dependency on a texture, and both assets are marked as Addressable in separate AssetBundles BundleM and BundleT respectively. BundleT contains the texture. BundleM contains the material and lists BundleT as a dependency.

If any dependencies are not explicitly included, then they are implicitly pulled in.

Example: A material has a direct dependency on a texture, and only the material is marked as Addressable in BundleM. During build, the texture, because it is not explicitly included elsewhere, is pulled into BundleM when the material is.

This implicit dependency inclusion can lead to duplication.

Example: Two materials, matA and matB, are Addressable and both have direct dependencies on the same texture. If matA and matB are built into the same AssetBundle, then the texture is pulled implicitly in once. If matA and matB are built into separate AssetBundles, then the texture is pulled implicitly into each of those AssetBundles. At runtime, the engine has no record that these textures came from the same source asset, and are each loaded as they are needed by their respective materials.

 

重复依赖

在探索内存管理和依赖关系图时,讨论重复内容很重要。有两种机制能使得你的Asset会被构建到AssetBundle中:显示(机制)和隐式(机制)。如果你将一个Asset标记为Addressable,这会将Asset显示的放入AssetBundle中。那是Asset将会唯一存在的AssetBundle。

例如:一个Material明确依赖于一个Texture,并且两个Asset分别在不同的AssetBundle BundleM和BundleT里分别被标记为Addressable资源。BundleT包含了Texture,BundleM包含了Material并将BundleT列为依赖项。

例如:一个Material明确依赖于一个Texture,并且只有Material被标记为Addressable于BundleM里。当构建时,Texture由于没有被显示的标记属于哪里,便会被放入到BundleM里(Material所属的AssetBundle)。

再例如:两个Material,matA和matB,都被标记为Addressable,并且都对同一个Texture有依赖。如果matA和matB被构建到同一个AssetBundle,这个texture会被隐式的构建入AssetBundle里一次。如果matA和matB被构建到不同的AssetBundles里,这个texture会被隐式的放入这两个Material各自的AssetBundle中。在运行过程当中,引擎没有记录这些纹理来自同一个源Asset,并且每个都需根据各自材质的需要进行加载。

 

SpriteAtlas dependencies

SpriteAtlases complicate the dependency calculation a bit, and merit a more thorough set of examples.

Addressable Sprite Example 1:

Three textures exist and are marked as Addressable in three separate groups. Each texture builds to about 500KB. During the build, they are built into three separate AssetBundles, each AssetBundle only containing the given sprite meta data and texture. Each AssetBundle is about 500KB and none of these AssetBundles have dependencies.

Addressable Sprite Example 2:

The three textures in Example 1 are put into a SpriteAtlas. That atlas is not Addressable. One of the AssetBundles generated contains that atlas texture and is about 1500KB. The other two AssetBundles only contain Sprite metadata (a few KB), and list the atlas AssetBundle as a dependency. Which AssetBundle contains the texture is deterministic in that it is the same through rebuilds, but is not something that can be set by the user. This is the key portion that goes against the standard duplication of dependencies. The sprites are dependent on the SpriteAtlas texture to load, and yet that texture is not built into all three AssetBundles, but is instead built only into one.

Addressable Sprite Example 3:

The SpriteAtlas from Example 2 is marked as Addressable in its own AssetBundle. At this point there are four AssetBundles created. If you are using a 2020.x or newer version of Unity, this builds as you would expect. The three AssetBundles with the sprites are each only a few KB and have a dependency on this fourth SpriteAtlas AssetBundle, which is be about 1500KB. If you are using 2019.x or older, the texture itself may end up elsewhere. The three sprite AssetBundles still depend on the SpriteAtlas AssetBundle. However, the SpriteAtlas AssetBundle may only contain meta data, and the texture may be in one of the other sprite AssetBundles.

Addressable Prefab With Sprite Dependency Example 1:

Instead of three Addressable textures, there are three Addressable sprite prefabs. Each prefab depends on its own sprite (about 500KB). Building the three prefabs separately results, as expected, in three AssetBundles of about 500KB each.

Addressable Prefab With Sprite Dependency Example 2

Taking the prefabs and textures from the previous example, all three textures are added to a SpriteAtlas, and that atlas is not marked as Addressable. In this scenario, the SpriteAtlas texture is duplicated. All three AssetBundles are approximately 1500KB. This is expected based on the general rules about duplication of dependencies, but goes against the behavior seen in "Addressable Sprite Example 2".

Addressable Prefab With Sprite Dependency Example 3

Taking the prefabs, textures, and SpriteAtlas form the above example, the SpriteAtlas is also marked as Addressable. Conforming to the rules of explicit inclusion, the SpriteAtlas texture is included only in the AssetBundle containing the SpriteAtlas. The AssetBundles with prefabs reference this fourth AssetBundle as a dependency.

 

SpriteAtlas依赖项

SpriteAtlas让依赖项计算变得稍微复杂一些,值得我们提供一组更为全面的示例。

Addressable Sprite 示例 1:

存在3个Texture被标记为Addressable,并存在在3个对立的group里。每个纹理构建完大概500KB。在构建过程中,他们会被分别构建到3个不同的AssetBundle里,每个AssetBundle只包含给定的Sprite元数据和纹理。每个AssetBundle大概500KB丙炔这些AssetBundle逗没有依赖项。

Addressable Sprite 示例2:

示例1中的3个Texture被放入一个SpriteAtlas。该图集并没有被标记为Addressable。生成的AssetBundle之一包含了该纹理图集,大小约1500KB。另外两个AssetBundle只包含了Sprite的元数据(几KB),并将图集所在的AssetBundle列为依赖项。包含图集的AssetBundle是确定的,因为重建后它是相同的,但用户无法去设置它。这是违反了标准重复依赖关系的关键部分。(这句建议看原文,翻译的有点问题),Sprite的加载依赖于SpriteAtlas纹理,但该纹理并未构建到三个AssetBundle里,而是只构建到其中一个。

Addressable Sprite 示例3:

示例2中的SpriteAtlas在自己的AssetBundle中被标记为Addressable。此时会有4个AssetBundle被创建。如果你使用Unity 2020.x 或者是更新的版本,这将按照你的预期构建。其中3个包含Sprite的AssetBundle每个都只有几KB并且依赖于第四个包含了图集的AssetBundle这个AssetBundle大约1500KB。如果你使用的是Unity 2019.x或更早的版本,则纹理本身可能会出现在其他地方。但是,SpriteAtlas AssetBundle可能仅包含元数据,并且纹理可能位于其他Sprite AssetBundle的其中一个。

具有Sprite依赖关系的Addressable Prefab示例 1:

除了3个Addressable texture,还有3个Addressable sprite prefabs。每个prefab依赖于自己的sprite(大约500KB)。正如预期的那样,分别构建3个prefab会产生3个大约500KB的AssetBundle。

具有Sprite依赖关系的Addressable Prefab示例 2:

以上述例子中的textures和prefabs为例,将3个texture和合并到一个SpriteAtlas,并且不将这个SpriteAtlas标记为Addressable,在这个情况下,SpriteAtlas纹理将被重复生成,3个AssetBundle都大约为1500KB。这是基于重复依赖的一般规则的预期。但却与Addressable Sprite 示例2中的表现相反。

具有Sprite依赖关系的Addressable Prefab示例 3:

以上述例子中的prefabs、textures和SpriteAtlas为例,将SpriteAtlas也一并标记为Addressable。使其符合显示引用的规则,SpriteAtlas仅包含在包含SpriteAtlas的Asset Bundle中。带有prefabs的AssetBundle将该包含SpriteAtlas的AssetBundle作为依赖项。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM