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