Unity_Addressable_Memory management(内存管理)
The Addressables system manages the memory used to load assets and bundles by keeping a reference count of every item it loads.
译:Addressables系统通过跟踪每个加载的项的引用计数来管理用于加载资产和Bundle的内存。
When an Addressable is loaded, the system increments the reference count; when the asset is released, the system decrements the reference count. When the reference count of an Addressable returns to zero, it is eligible to be unloaded. When you explicitly load an Addressable asset, you must also release the asset when you are done with it.
译:当加载Addressable时,系统会增加引用计数;当释放资产时,系统会减少引用计数。当Addressable的引用计数返回到零时,它就可以被卸载。当你显式加载Addressable资产时,你必须在使用完毕后释放资产
The basic rule of thumb to avoid "memory leaks" (assets that remain in memory after they are no longer needed) is to mirror every call to a load function with a call to a release function. You can release an asset with a reference to the asset instance itself or with the result handle returned by the original load operation.
译:避免“内存泄漏”的基本原则是,每个加载函数的调用都要与一个释放函数的调用相对应。您可以使用资产实例本身的引用或原始加载操作返回的结果句柄来释放资产。
Note, however, that released assets are not necessarily unloaded from memory immediately. The memory used by an asset is not freed until the AssetBundle it belongs to is also unloaded. (Released assets can also be unloaded by calling Resources.UnloadUnusedAssets, but that tends to be a slow operation which can cause frame rate hitches.)
译:然而,请注意,释放的资产不一定会立即从内存中卸载。一个资产所使用的内存直到它所属的AssetBundle也被卸载才会被释放。(释放的资产也可以通过调用Resources.UnloadUnusedAssets来卸载,但这往往是一个缓慢的操作,会导致帧率的停顿。)
AssetBundles have their own reference count (the system treats them like Addressables with the assets they contain as dependencies). When you load an asset from a bundle, the bundle's reference count increases and when you release the asset, the bundle reference count decreases. When a bundle's reference count returns to zero, that means none of the assets it contains are still in use and the bundle and all the assets it contains are unloaded from memory.
译:AssetBundle有自己的引用计数(系统将它们视为包含依赖项的Addressables)。当您从Bundle中加载资产时,Bundle的引用计数将增加,当您释放资产时,Bundle的引用计数将减少。当Bundle的引用计数返回到零时,这意味着它包含的所有资产都不再使用,该Bundle及其包含的所有资产都会从内存中卸载。
Use the Event Viewer to monitor your runtime memory management. The viewer shows when assets and their dependencies are loaded and unloaded.
译:使用事件查看器来监视您的运行时内存管理。该查看器显示资产及其依赖项何时被加载和卸载。
Understanding when memory is cleared
An asset no longer being referenced (indicated by the end of a blue section in the Event Viewer) does not necessarily mean that asset was unloaded. A common applicable scenario involves multiple assets in an AssetBundle. For example:
译:资产不再被引用(在事件查看器中的蓝色部分结束)并不一定意味着该资产已被卸载。常见的应用场景包括AssetBundle中的多个资产。例如:
You have three Assets (
tree
,tank
, andcow
) in an AssetBundle (stuff
).译:您在AssetBundle(stuff)中有三个资产(tree,tank和cow)。When
tree
loads, the profiler displays a single ref-count fortree
, and one forstuff
.译:当tree加载时,Profiler显示tree的单个引用计数和stuff的一个引用计数。Later, when
tank
loads, the profiler displays a single ref-count for bothtree
andtank
, and two ref-counts for thestuff
AssetBundle.译:稍后,当tank加载时,Profiler显示tree和tank的单个引用计数以及stuff AssetBundle的两个引用计数。If you release
tree
, it's ref-count becomes zero, and the blue bar goes away.译:如果您释放tree,则其引用计数变为零,蓝色条消失。
In this example, the tree
asset is not actually unloaded at this point. You can load an AssetBundle, or its partial contents, but you cannot partially unload an AssetBundle. No asset in stuff unloads until the AssetBundle itself is completely unloaded. The exception to this rule is the engine interface Resources.UnloadUnusedAssets. Executing this method in the above scenario causes tree
to unload. Because the Addressables system cannot be aware of these events, the profiler graph only reflects the Addressables ref-counts (not exactly what memory holds). Note that if you choose to use Resources.UnloadUnusedAssets, it is a very slow operation, and should only be called on a screen that won't show any hitches (such as a loading screen).
译:在这个例子中,tree资产实际上在这个时候并没有被卸载。你可以加载AssetBundle或它的部分内容,但你不能部分卸载AssetBundle。直到AssetBundle本身完全卸载,stuff中的任何资产都不会卸载。这个规则的例外是引擎接口Resources.UnloadUnusedAssets。在上述情况下执行此方法会导致tree卸载。因为Addressables系统不能意识到这些事件,Profiler图表只反映Addressables引用计数(并不完全反映内存的实际情况)。请注意,如果您选择使用Resources.UnloadUnusedAssets,它是一个非常缓慢的操作,只应该在不会显示任何停顿的屏幕上调用(例如加载屏幕)
Avoiding asset churn
Asset churn is a problem that can arise if you release an object that happens to be the last item in an AssetBundle and then immediately reload either that asset or another asset in the bundle.
译:如果您释放一个恰好是AssetBundle中最后一项的对象,然后立即重新加载该资产或Bundle中的另一个资产,则可能会出现资产变动的问题。
For example, say you have two materials, boat
and plane
that share a texture, cammo
, which has been pulled into its own AssetBundle. Level 1 uses boat
and level 2 uses plane
. As you exit level 1 you release boat
, and immediately load plane
. When you release boat
, Addressables unloads texture cammo
. Then, when you load plane
, Addressables immediately reloads cammo
.
译:例如,假设您有两个材质,boat和plane,共享一个纹理cammo,该纹理已被拉入其自己的AssetBundle中。Level1使用boat,Level2使用plane。当您退出Level1并释放boat时,Addressables卸载纹理cammo。然后,当您加载plane时,Addressables立即重新加载cammo。
You can use the Event Viewer to help detect asset churn by monitoring asset loading and unloading.
译:您可以使用事件查看器来通过监控资产的加载和卸载来帮助检测资产变动。
AssetBundle memory overhead
When you load an AssetBundle, Unity allocates memory to store the bundle's internal data, which is in addition to the memory used for the assets it contains. The main types of internal data for a loaded AssetBundle include:
译:当您加载AssetBundle时,Unity会分配内存来存储该Bundle的内部数据,这是除了用于包含的资产的内存之外的另一部分内存。已加载的AssetBundle的主要内部数据类型包括:
Loading cache: Stores recently accessed pages of an AssetBundle file. Use AssetBundle.memoryBudgetKB to control its size.译:加载缓存:存储AssetBundle文件的最近访问页面。使用AssetBundle.memoryBudgetKB来控制其大小。
TypeTrees: Defines the serialized layout of your objects.译:TypeTrees:定义对象的序列化布局
Table of contents: Lists the assets in a bundle.译:目录表:列出Bundle中的资产。
Preload table: Lists the dependencies of each asset.译:预加载表:列出每个资产的依赖项。
When you organize your Addressable groups and AssetBundles, you typically must make trade-offs between the size and the number of AssetBundles you create and load. On the one hand, fewer, larger bundles can minimize the total memory usage of your AssetBundles. On the other hand, using a larger number of small bundles can minimize the peak memory usage because you can unload assets and AssetBundles more easily.
译:当您组织Addressable组和AssetBundle时,通常必须在创建和加载的AssetBundle的大小和数量之间进行权衡。一方面,较少但更大的Bundle可以最小化AssetBundle的总内存使用量。另一方面,使用更多但更小的Bundle可以最小化峰值内存使用量,因为您可以更轻松地卸载资产和AssetBundle。
While the size of an AssetBundle on disk is not the same as its size at runtime, you can use the disk size as an approximate guide to the memory overhead of the AssetBundles in a build. You can get bundle size and other information you can use to help analyze your AssetBundles from the Build Layout Report.
译:虽然AssetBundle在磁盘上的大小与其运行时大小不同,但您可以使用磁盘大小作为构建中AssetBundle的内存开销的近似指南。您可以从Build Layout Report获取Bundle大小和其他信息,以帮助分析AssetBundle。
The following sections discuss the internal data used by AssetBundles and how you can minimize the amount of memory they require, where possible.
译:以下部分讨论了AssetBundle使用的内部数据以及在可能的情况下如何最小化它们所需的内存量。
TypeTrees
A TypeTree describes the field layout of one of your data types.
译:TypeTree描述了一个数据类型的字段布局。
Each serialized file in an AssetBundle contains a TypeTree for each object type within the file. The TypeTree information allows you to load objects that are deserialized slightly differently from the way they were serialized. TypeTree information is not shared between AssetBundles; each bundle has a complete set of TypeTrees for the objects it contains.
译:AssetBundle中的每个序列化文件都包含文件中每个对象类型的TypeTree。TypeTree信息允许您加载反序列化的对象与序列化时略有不同。TypeTree信息不在AssetBundle之间共享;每个Bundle都有一组包含其中所包含的对象的完整TypeTrees。
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 is proportional to the number of unique types in the serialized file and the complexity of those types.
译:当加载AssetBundle时,所有TypeTrees都会被加载并保存在内存中,直到AssetBundle的生命周期结束。与TypeTrees相关的内存开销与序列化文件中唯一类型的数量和这些类型的复杂性成正比
You can reduce the memory requirements of AssetBundle TypeTrees in the following ways:
译:您可以通过以下方式减少AssetBundle TypeTrees的内存要求
Keep assets of the same types together in the same bundles.译:在同一Bundle中将相同类型的资产放在一起。
Turn off TypeTrees -- turning off TypeTrees makes your AssetBundles smaller by excluding this information from a bundle. However, without TypeTree information, you may encounter serialization errors or undefined behavior when loading older bundles with a newer version of Unity or after making even small script changes in your project.译:关闭TypeTrees - 关闭TypeTrees会使AssetBundle更小,因为它将此信息从Bundle中排除。但是,没有TypeTree信息,当使用较新版本的Unity或在项目中进行即使是小的脚本更改后,加载旧Bundle时可能会遇到序列化错误或未定义行为。
Prefer simpler data types to reduce TypeTree complexity.译:更喜欢简单的数据类型以减少TypeTree的复杂性。
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.
译:您可以通过启用和禁用TypeTrees来测试TypeTrees对AssetBundle大小的影响,并比较它们的大小。
Use BuildAssetBundleOptions.DisableWriteTypeTree to disable TypeTrees in your AssetBundles. Note that not all platforms support TypeTrees and some platforms require TypeTrees (and ignore this setting).
译:使用BuildAssetBundleOptions.DisableWriteTypeTree在AssetBundle中禁用TypeTrees。请注意,并非所有平台都支持TypeTrees,有些平台需要TypeTrees(并忽略此设置)。
If you disable TypeTrees in a project, always rebuild local Addressable groups before building a new player. If you are distributing content remotely, only update content using the same version (including patch number) of Unity that you used to produce your current player and don't make even minor code changes. (When you are juggling multiple player versions, updates, and versions of Unity, you might not find the memory savings from disabling TypeTrees to be worth the trouble.)
译:如果在项目中禁用TypeTrees,请在构建新播放器之前始终重新构建本地Addressable组。如果您正在远程分发内容,请仅使用与生产当前播放器所使用的Unity版本相同的版本(包括补丁号),不要进行即使是微小的代码更改。(当您需要处理多个播放器版本、更新和Unity版本时,您可能不会发现禁用TypeTrees的内存节省值得麻烦。)
Table of contents
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.
译:目录表是Bundle中的一个映射,允许您按名称查找每个明确包含的资产。它与资产的数量和它们映射的字符串名称的长度成线性关系。
The size of your table of contents data is based on the total number of assets. You can minimize the amount of memory dedicated to holding table of content data by minimizing the number of AssetBundles loaded at a given time.
译:您的目录表数据的大小基于资产的总数。通过最小化同时加载的AssetBundle数量,可以将用于保存目录表数据的内存量最小化
Preload table
The preload table is a list of all the other objects that an asset references. Unity uses the preload table to load these referenced objects when you load an asset from the AssetBundle.
译:预加载表是一个列表,其中包含资产引用的所有其他对象。Unity使用预加载表在从AssetBundle加载资产时加载这些引用的对象
For example, a Prefab has a preload entry for each of its components 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.
译:例如,一个Prefab对于其每个组件以及其可能引用的任何其他资产(材质、纹理等)都有一个预加载条目。每个预加载条目为64位,并且可以引用其他AssetBundles中的对象
When an asset references another asset that in turn references other assets, the preload table can become large because it contains the entries needed to load both assets. If two assets both reference a third asset, then the preload tables of both contain entries to load the third asset (whether or not the referenced asset is Addressable or in the same AssetBundle).
译:当一个资产引用另一个资产,而另一个资产又引用其他资产时,预加载表可能会变得很大,因为它包含加载两个资产所需的条目。如果两个资产都引用第三个资产,则两者的预加载表都包含用于加载第三个资产的条目(无论是否将引用的资产添加到AssetBundle中)
As an example, consider a situation in which you have two assets in an AssetBundle (PrefabA and PrefabB) and both of these prefabs reference a third prefab (PrefabC), which is large and contains several components and references to other assets. This AssetBundle contains two preload tables, one for PrefabA and one for PrefabB. Those tables contain entries for all the objects of their respective prefab, but also entries for all the objects in PrefabC and any objects referenced by PrefabC. Thus the information required to load PrefabC ends up duplicated in both PrefabA and PrefabB. This happens whether or not PrefabC is explicitly added to an AssetBundle.
译:例如,假设您在一个AssetBundle中有两个资产(PrefabA和PrefabB),这些预制品都引用第三个预制品(PrefabC),它很大,包含许多组件和引用其他资产。该AssetBundle包含两个预加载表,一个为PrefabA,一个为PrefabB。这些表包含其各自预制的所有对象的条目,以及PrefabC中的所有对象和PrefabC引用的任何对象的条目。因此,加载PrefabC所需的信息最终在PrefabA和PrefabB中重复。这种情况发生在是否将PrefabC显式添加到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 have several loadable assets that all reference a complex asset, such as PrefabC 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.
译:根据您组织资产的方式,AssetBundles中的预加载表可能会变得非常大,并包含许多重复条目。如果您确定预加载表的内存开销是一个问题,则可以构造可加载的资产,使它们具有更少的复杂加载依赖项
Memory implications of loading AssetBundle dependencies
Loading an Addressable asset also loads all of the AssetBundles containing its dependencies. An AssetBundle dependency occurs when an asset in one bundle references an asset in another bundle. An example of this is a material referencing a texture. For more information see Asset and AssetBundle dependencies.
译:加载一个Addressable资产也会加载包含其依赖项的所有AssetBundles。当一个Bundle中的资产引用另一个Bundle中的资产时,就会发生AssetBundle依赖关系。例如,一个材质引用一个纹理。有关更多信息,请参见Asset and AssetBundle dependencies。
Addressables calculates dependencies between bundles at the bundle level. If one asset references an object in another bundle, then the entire bundle has a dependency on that bundle. This means that even if you load an asset in the first bundle that has no dependencies of its own, the second AssetBundle is still loaded into memory.
译:Addressables在Bundle级别计算Bundle之间的依赖关系。如果一个资产引用另一个Bundle中的对象,则整个Bundle都有一个依赖关系。这意味着即使您加载第一个Bundle中没有自己依赖关系的资产,第二个AssetBundle仍会加载到内存中。
For Example:
BundleA
contains Addressable Assets RootAsset1
and RootAsset2
. RootAsset2
references DependencyAsset3
, which is contained in BundleB
. Even though RootAsset1
has no reference to BundleB
, BundleB
is still a dependency of RootAsset1
because RootAsset1
is in BundleA
, which has a reference on BundleB
.
译:BundleA包含Addressable Assets RootAsset1和RootAsset2。RootAsset2引用DependencyAsset3,该资产包含在BundleB中。即使RootAsset1没有引用BundleB,BundleB仍然是RootAsset1的依赖项,因为RootAsset1在BundleA中,而BundleA引用了BundleB
NOTE
Prior to Addressables 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.
译:在Addressables 1.13.0之前,依赖关系图不如现在那样彻底。在上面的示例中,RootAsset1不会依赖于BundleB。这种先前的行为会导致当一个AssetBundle被另一个AssetBundle引用时被卸载和重新加载时引用中断。如果依赖关系图足够复杂,则此修复可能导致额外的数据保留在内存中
To avoid loading more bundles than are required, you should strive to keep the dependencies between AssetBundles as simple as possible. There are several diagnostic tools included with Addressables that can be used to accomplish this:
译:为避免加载比所需更多的Bundle,您应该尽可能简化AssetBundle之间的依赖关系。Addressables包括几个诊断工具可用于实现此目的
Analyze Tool 分析工具
Build Layout Report 构建布局报告