Search Unity

Extended Q&A: Optimizing memory and build size with Addressables

April 28, 2023 in Engine & platform | 17 min. read
Extended Q&A: Optimizing memory and build size with Addressables | Hero image
Extended Q&A: Optimizing memory and build size with Addressables | Hero image
Share

Is this article helpful for you?

Thank you for your feedback!

In February, as part of my role as a senior software development consultant for Unity Accelerate Solutions, I led a technical webinar about the Addressables Asset System. During the live session, I demonstrated various profiling tools that you can use to optimize a project’s runtime memory and build size. The webinar ended with a Q&A, and our team received more questions than we had time to answer.

The following is an extension of that closing Q&A, so we can answer more of your questions.


Q: Is the Addressables system needed for light games – like casual, arcade, or puzzle games – if I don’t have memory issues?
A: Maybe not, but it’s good to keep in mind that the Addressables system doesn’t only improve memory performance. Having the ability to choose when you load content can improve loading times. Building content in Addressables enables you to have iterative builds that don’t take as long. For example, if you make a small script change, you may not have to rebuild all of your bundles.

Q: Are loaded assets released when the scene switches?
A: Potentially. Loaded assets from Addressables that are ready to be released because they have a ref count of zero might be unloaded from memory during a scene transition. When transitioning from scenes non-additively, call Resources.UnloadUnusedAssets(). This is expensive on the CPU, but allows you to partially unload AssetBundles.

Q: Do object pooling and Addressables work well together?
A: Yes. You can load your object once from Addressables and then instantiate multiple copies of it to create your pool. When you are done with the pool, destroy all the objects and release the AsyncOperationHandle that was used to load the asset.

Q: Are groups and bundles loaded into memory all at once?
A: Addressables groups are an Editor-only concept. At runtime, you only deal with bundles. Bundles are loaded into memory only when they are needed and only the desired content is loaded.

Example: You have one bundle with 10 characters in it. You ask Addressables to load three characters. The bundle’s metadata and the three characters will be loaded.

Q: If I want to release an asset, do I need to keep the AsyncOperationHandle or the AssetReference?
A: We recommend keeping the handle and using it, since you’re responsible for releasing content when you are done using it.

As an example, members of our team will often go the handle route in order to avoid calling Instantiate/Release directly on the AssetReference.

Q: What are the disadvantages of many small bundles?
A: This documentation lists several disadvantages of too many bundles.

Q: When an asset in a bundle is needed, what overhead do the other assets in the same bundle have? If it’s a remote bundle it must be downloaded, but is there really no memory overhead from unused assets in the bundle?
A: Correct, a remote bundle will be fully downloaded before you can use it.

Unloaded assets in a loaded asset bundle have minimal overhead at runtime. Whenever you load assets from a bundle, you need to load the bundle’s metadata. Part of this metadata includes a table of contents that lists all the assets in the bundle. More assets in a bundle equates to larger metadata.

You can view this memory overhead by taking a capture with the Unity Memory Profiler. In the “All Of Memory” tab, there’s a list of all the “SerializedFile” objects in memory, one for each bundle. These objects are your bundles’ metadata.

Learn more about this metadata in our documentation.

Q: When working in an open-world setting, what bundling strategies can I use to unload individual assets without half unloading a bundle and relying on Resources.UnloadUnusedAssets() to clean it up, without the overhead of having every asset in its own bundle?
A: The key thing to remember is that content should be bundled together if you expect to unload it at the same time. If your game world has “static” content, like trees and rocks for a certain biome that will not be moved by the player, that content should be bundled together. Any “dynamic” content, like items the player can pick up, should be bundled separately.

This blog post and linked GitHub repo covers splitting bundles for an open-world game. It also features a way to deduplicate bundles to reduce the memory overhead of each bundle. Stages 4 and 5 are particularly relevant to open worlds.

Q: When should I leave “AssetBundle CRC” enabled? 
A: The recommended practice is to have this enabled, excluding cached AssetBundles for Remote groups, and disabled for Local groups. The check is only meant to make sure the data wasn’t corrupted on download. There’s almost no reason to do the check for local AssetBundles.

Q: When is it not worth it to use Addressables due to CPU performance concerns when loading and unloading assets?
A: The Addressables system has a positive impact on CPU loading performance due to not needing to load all content up front.

If you don’t use Addressables when loading a scene, you’d have to load all content and references. If you move the content to Addressables, you can choose when to load which content.

For example, say you have an Inventory Manager in a scene that has a reference to 1,000 inventory items. If you don’t use Addressables, you’ll have to load every mesh, texture, audio clip, etc., for all these inventory items. If you wait to load this content, loading the scene will be faster.

Q: Do all dependencies of an addressable asset also need to be Addressables, or is that only necessary if they are shared?
A: Dependencies do not need to be marked addressable. Dependencies will be pulled into Addressables during the build process if necessary.

As an example, if you make a player prefab an addressable, you don’t have to manually mark the player’s mesh, textures, or audio as addressable, too. When the bundle is built, all the dependencies that don’t yet exist in Addressables will be automatically included in the player prefab bundle.

Q: If I forgot to release an asset and change scenes, what happens to this asset?
A: Changing scenes does not inherently interact poorly with handles. But if you load an asset and forget to release its handle, the asset will persist in memory.

Addressables has an internal reference-counting system. Handles are how we interact with this system. Loading an asset increments the reference count, and releasing decrements the reference count.

Creators are responsible for keeping this reference count up to date. The asset will be in memory as long as the reference count is greater than one.

Q: Related to the webinar example, suppose I’m making an open-world game. The boss is present somewhere in the open world. When the player heads to the boss, how do I use Addressables here? Do I send the command to load the sword async, via a trigger, at a certain distance from the enemy, or something else? 
A: It can be a fine line to choose when to load and unload content. You want to be sure the boss is ready when the player needs to see it, but might not want to load it too early when the player is still able to turn around and avoid the boss.

The good thing is that you can iterate on when to load and unload content – you don’t have to get it perfectly optimized on the first try.

To get started, we suggest loading all content for a particular “zone” when the player gets near (e.g., the player approaches a dungeon entrance which causes everything inside the dungeon to load). If this causes unnecessary memory pressure, you can add more fine-grained loading and unloading.

If the sword is not loading soon enough, consider moving the loading trigger to start earlier, improving the load time of the sword assets by using Unity Profiler’s CPU module to see what is being loaded, or using Addressables synchronously to ensure the load is finished.

This documentation includes more details and a code snippet for synchronous Addressables.

Q: If I load an addressable when a scene starts, do I need to have a loading screen for it?
A: Loading from Addressables is typically done in an asynchronous way, like with Addressables.LoadAssetAsync().

There may be some content you don’t want to load before leaving a loading screen. You can collect these AsyncOperationHandles and wait for the necessary ones to complete before leaving.

Q: What is the memory footprint of the addressables metadata at runtime (before loading any of its data)?
A: During Addressables initialization, the catalog file is loaded so that Addressables knows how to map labels and addresses to assets on disk or in remote locations. A larger catalog equates to a larger memory overhead at runtime.

Catalog size can be reduced by stripping unnecessary data, like not including labels or GUIDs in groups that don’t need them, or by reducing the size of existing data. For example, by setting a group’s Internal Asset Naming Mode to GUID instead of filename or full path (which can be longer). You can view the runtime memory size of the catalog in Unity Memory Profiler.

Q: What is the Unity Editor doing in the time it spends building Addressables?
A: A build report log is output in the /Library folder. This log shows each step of the build process. To add additional details to the log, follow this path to select “Use Detailed Build Log”: Enabling Edit > Preferences > Scriptable Build Pipeline > Use Detailed Build Log.

Check out visuals and documentation on how to view the log.

Q: Does Resources.Load() also have a duplication problem?
A: Yes. It can be useful to think of Addressables content and Resources content as different “worlds.” If you have a texture in /Resources, one copy of that texture is included in the Resources file. If bundles in Addressables depend on that texture, each bundle includes an implicit copy of it. You end up with multiple copies of the texture on disk and potentially multiple copies in memory.

To avoid this duplication, move the texture out of /Resources and add it to an Addressables group.

Q: Do you get similar size on disk issues that are resolved by removing duplicate bundles when you don’t use Addressables?
A: Yes. In the webinar and slides we show how deduplicating the two water racing scenes significantly reduced build size.

Q: How can I prevent shader variant duplicates?
A: Shaders can be deduplicated in the same process as any other asset – explicitly declare them in a group.

If an asset is explicitly declared in an Addressables group that is going into your build, that asset will not be duplicated across multiple bundles.

For shaders specifically, it is common practice for projects to use a “Shared shaders” group to contain shaders that you expect to need in memory for the lifespan of your app, and that are shared across many assets.

Q: Do two Unity scenes sharing the same prefab duplicate build size?
A: This depends on if the prefab the scenes depend on has been explicitly included in Addressables, and if the scenes are in the same or different bundles.

See the visual explanation of how duplication occurs in the webinar slides and in this blog post under Stage 4.

The key to remember is that all content going into a bundle needs to be able to access all of its dependencies. If you put a scene into a bundle, all of its dependencies need to either be:

  • Explicitly included somewhere in Addressables
  • Implicitly included in the same bundle

Q: Is it possible to compare duplicates in given groups to prevent having all the game assets packed together into an isolated group?
A: Yes. You can run the built-in deduplication rule and then sort the assets in the Addressables Groups window into better groupings.

Or, a more scalable approach is to write your own Addressables AnalyzeRules, which will appear in the Analyze window. The built-in rules are delivered as C# in the Addressables package and can serve as a baseline.

For example, you may want to find every duplicate across all of your groups that start with “Character-”. Any implicit duplicates can be placed in a “Shared-Character” group.

Q: Are you going to cover remote builds and local paths?
A: We did not cover remote and local paths, which are called “Addressables Profiles” in the webinar. However, we do describe what Addressables Profiles are and how to use them in this documentation.

Q: How does Addressables work with Cloud Content Delivery (CCD)?
A: CCD integration is discussed in this documentation.

Q: Can you please give pointers on best practices to implement low- and high-resolution Addressables variations?
A: You can find an example in the Addressables Sample on GitHub.

Q: What if bundle content is encrypted? Does the UnityDataTool also decrypt the content?
A: No. The data will need to be decrypted before UnityDataTool can analyze the content.

Q: Is it a supported use case to build bundles from one Unity project and load the bundles at runtime from an app built from a different project?
A: Yes. This is covered by using multiple catalogs at the same time.

Q: Are there drawbacks to using InstantiateAsync, or situations where it is better to use LoadAsync + manual Instantiate?
A: It is recommended to use Addressables.LoadAssetAsync() and call Object.Instantiate(). Addressables.InstantiateAsync() has a larger performance cost.

Q: I have a lot of ScriptableObjects with at least 1–2 sprites referenced as variables. If I want to change the sprites to Addressables, do I have to change the references to Addressables one by one, or is there any trick to do this?
A: An Editor script is probably the way to go to convert these references.

You can add the AssetReference fields to your ScriptableObject (and temporarily keep the Sprite fields). Then, you can write an Editor script that iterates through your ScriptableObjects, looks up the Sprite asset in Addressables to find the associated AddressableAssetEntry, and stores the address or creates an AssetReference to be stored on the ScriptableObject.

Lastly, you can remove the direct Sprite references and swap any related code to use the AssetReference.

Q: Can I use addressables for WebGL games? If yes, are there any specific things to look for?
A: Yes, and yes. Two things to note: First, WebGL does not support threading, so don’t use Tasks. Second, caching works differently on WebGL – we’ve seen issues with caching remote AssetBundles before.

Q: If I use Shader.Find(“ShaderName”), is this coming from the build or Addressables?
A: These are coming from the build of the Unity Player, not Addressables. Shader.Find() does not return results from AssetBundles.

Q: How can I organize the Addressables Groups window when I have many similarly-named groups?
A: For organizing the Addressables Groups UI, you can enable Group Hierarchy with Dashes. This will group similarly-named groups together. For example, “Character-person” and “Character-person2” will appear in the UI under the “Character” grouping.

This does not affect how bundles are created. This is only a UI organizational change.

Share your feedback with us in the Addressables forum. Be sure to watch for new technical blogs from other Unity developers as part of the ongoing Tech from the Trenches series.

April 28, 2023 in Engine & platform | 17 min. read

Is this article helpful for you?

Thank you for your feedback!

Related Posts