Search Unity

Tales from the optimization trenches: Saving memory with Addressables

, março 31, 2021

Efficient streaming of assets in and out of memory is a key element of any quality game. As a consultant on our Professional Services team, I’ve been striving to improve the performance of many customer projects. That’s why I’d like to share some tips on how to leverage the Unity Addressable Asset System to enhance your content loading strategy.

Memory is a scarce resource that you must manage carefully, especially when porting a project to a new platform. Using Addressables can improve runtime memory by introducing weak references to prevent unnecessary assets from being loaded. Weak references mean that you have control over when the referenced asset is loaded into and out of memory; the Addressable System will find all of the necessary dependencies and load them, too.

This blog will cover a number of scenarios and issues you can run into when setting up your project to use Unity Addressable Asset System – and explain how to recognize them and promptly fix them.

Inventory example 


For this series of recommendations, we will work with a simple example that’s set up in the following way:

  • We have an InventoryManager script in the scene with references to our three inventory assets: Sword, Boss Sword, Shield prefabs.
  • These assets are not needed at all times during gameplay.

You can download the project files for this example on my GitHub.

We’re using the preview package Memory Profiler to view memory at runtime. In Unity 2020 LTS, you must first enable preview packages in Project Settings before installing this package from the Package Manager.

If you’re using Unity 2021.1, select the Add package by name option from the additional menu (+) in the Package Manager window. Use the name “com.unity.memoryprofiler”.

Stage 1: Hard references, no Addressables

Let’s start with the most basic implementation and then work our way toward the best approach for setting up our Addressables content. We will simply apply hard references (direct assignment in the inspector, tracked by GUID) to our prefabs in a MonoBehaviour that exists in our scene.

When the scene is loaded, all objects in the scene are also loaded into memory along with their dependencies. This means that every prefab listed in our InventorySystem will reside in memory, along with all the dependencies of those prefabs (textures, meshes, audio, etc.)

As we create a build and take a snapshot with the Memory Profiler, we can see that the textures for our assets are already stored in memory even though none of them are instantiated.

Textures for our items are stored in memory even though they are not yet instantiated.

Problem: There are assets in memory that we do not currently need. In a project with a large number of inventory items, this would result in considerable runtime memory pressure.

Stage 2: Implement Addressables

To avoid loading unwanted assets, we will change our inventory system to use Addressables. Using Asset References instead of direct references prevents these objects from being loaded along with our scene. Let’s move our inventory prefabs to an Addressables Group and change InventorySystem to instantiate and release objects using the Addressables API.

Build the Player and take a snapshot. Notice that none of the assets are in memory yet, which is great because they have not been instantiated.

No inventory item textures appear in memory; only TextMeshPro textures.

Instantiate all the items to see them appear correctly with their assets in memory.

Problem: If we instantiate all of our items and despawn the boss sword, we will still see the boss sword’s texture “BossSword_E ” in memory, even though it isn’t in use. The reason for this is that, while you can partially load asset bundles, it’s impossible to automatically partially unload them. This behavior can become particularly problematic for bundles with many assets in them, such as a single AssetBundle that comprises all of our inventory prefabs. None of the assets in the bundle will unload until the entire AssetBundle is no longer needed, or until we call the costly CPU operation Resources.UnloadUnusedAssets().

The texture BossSword_E remains in memory even after the boss sword has been released.

 

Stage 3: Smaller Bundles

To fix this problem, we must change the way that we organize our AssetBundles. While we currently have a single Addressable Group that packs all of its assets into one AssetBundle, we can instead create an AssetBundle for each prefab. These more granular AssetBundles alleviate the problem of large bundles retaining assets in memory that we no longer need.

Making this change is easy. Select an Addressable Group, followed by Content Packaging & Loading > Advanced Options > Bundle Mode, and go to Inspector to change the Bundle Mode from Pack Together to Pack Separately.

By using Pack Separately to build this Addressable Group, you can create an AssetBundle for each asset in the Addressable Group.

The assets and bundles will look like this:

Now, returning to our original test: Spawning our three items and then despawning the boss sword no longer leaves unnecessary assets in memory. The boss sword textures are now unloaded because the entire bundle is no longer needed.

Problem: If we spawn all three of our items and take a memory capture, duplicate assets will appear in memory. More specifically, this will lead to multiple copies of the textures “Sword_N” and “Sword_D”. How could this happen if we only change the number of bundles?

Stage 4: Fix duplicate assets

To answer this question, let’s consider everything that goes into the three bundles we created. While we only placed three prefab assets into bundles, there are additional assets implicitly pulled into those bundles as dependencies of the prefabs. For example, the sword prefab asset also has mesh, material, and texture assets that need to be included. If these dependencies are not explicitly included elsewhere in Addressables, then they are automatically added to each bundle that needs them.

Our Asset Bundles for the Sword and BossSword contain some of the same dependencies.

Addressables include an analysis window to help diagnose bundle layout. Open Window > Asset Management > Addressables > Analyze and run the rule Bundle Layout Preview. Here, we see that the sword bundle explicitly includes the sword.prefab, but there are many implicit dependencies also pulled into this bundle.

In the same window, run Check Duplicate Bundle Dependencies. This rule highlights the assets included in multiple asset bundles based on our current Addressables layout.

Analysis shows that we have duplicate textures and meshes between our sword bundles, and all three bundles duplicate the same shader.

We can prevent the duplication of these assets in two ways:

  1. Place the Sword, BossSword and Shield prefabs in the same bundle so that they share dependencies, or
  2. Explicitly include the duplicated assets somewhere in Addressables

We want to avoid putting multiple inventory prefabs in the same bundle to stop unwanted assets from persisting in memory. As such, we will add the duplicated assets to their own bundles (Bundle 4 and Bundle 5).

The duplicate textures are explicitly placed in their own bundles.

In addition to analyzing our bundles, the Analyze Rules can automatically fix the offending assets via Fix Selected Rules. Press this button to create a new Addressable Group named “Duplicate Asset Isolation,” which has the four duplicated assets in it. Set this group’s Bundle Mode to Pack Separately to prevent any other assets no longer needed from persisting in memory.

Stage 5: Reduce Asset Bundle metadata size in large projects

Using this AssetBundle strategy can result in problems at scale. For each AssetBundle loaded at a given time, there is memory overhead for AssetBundle metadata. This metadata is likely to consume an unacceptable amount of memory if we scale this current strategy up to hundreds or thousands of inventory items. Read more about AssetBundle metadata in the Addressables docs.

View the current AssetBundle metadata memory cost in the Unity Profiler. Go to the memory module and take a memory snapshot. Look in the category Other > SerializedFile.

1,819 bundles are currently loaded with SerializedFile memory, with a total size of 263 MB.

There is a SerializedFile entry in memory for each loaded AssetBundle. This memory is AssetBundle metadata rather than the actual assets in the bundles. This metadata includes: 

  • Two file read buffers
  • A type tree listing every unique type included in the bundle
  • A table of contents pointing to the assets

Of these three items, file read buffers occupy the most space. These buffers are 64 KB each on PS4, Switch, and Windows RT, and 7 KB on all other platforms. In the above example, 1,819 bundles * 64 KB * 2 buffers = 227 MB just for buffers.

Seeing as the number of buffers scales linearly with the number of AssetBundles, the simple solution to reduce memory is to have fewer bundles loaded at runtime. However, we’ve previously avoided loading large bundles to prevent unwanted assets from persisting in memory. So, how do we reduce the number of bundles while maintaining granularity?

A solid first step would be to group assets together based on their use in the application. If you can make intelligent assumptions based on your application, then you can group assets that you know will always be loaded and unloaded together, such as those group assets based on the gameplay level they are in.

On the other hand, you might be in a situation where you cannot make safe assumptions about when your assets are needed/not needed. If you are creating an open-world game, for example, then you cannot simply group everything from the forest biome into a single asset bundle because your players might grab an item from the forest and carry it between biomes. The entire forest bundle remains in memory because the player still needs one asset from the forest.

Fortunately, there is a way to reduce the number of bundles while maintaining a desired level of granularity. Let’s be smarter about how we deduplicate our bundles.

The built-in deduplication analyze rule that we ran detects all assets that are in multiple bundles and efficiently moves them into a single Addressable Group. By setting that group to Pack Separately, we end up with one asset per bundle. However, there are some duplicated assets we can safely pack together without introducing memory problems. Consider the diagram below:

We know that the textures “Sword_N” and “Sword_D” are dependencies of the same bundles (Bundle 1 and Bundle 2). Because these textures have the same parents, we can safely pack them together without causing memory problems. Both sword textures must always be loaded or unloaded. There is never concern that one of the textures might persist in memory, as there is never a case where we specifically use one texture and not the other.

We can implement this improved deduplication logic in our own Addressables Analyze Rule. We will work from the existing CheckForDupeDependencies.cs rule. You can see the full implementation code in the Inventory System example.

In this simple project, we merely reduced the total number of bundles from seven to five. But imagine a scenario where your application has hundreds, thousands, or even more duplicate assets in Addressables. While working with Unknown Worlds Entertainment on a Professional Services engagement for their game Subnautica, the project initially had a total of 8,718 bundles after using the built-in deduplication analyze rule. We reduced this to 5,199 bundles after applying the custom rule to group deduplicated assets based on their bundle parents. You can learn more about our work with the team in this case story.

That is a 40% reduction in the number of bundles, while still having the same content in them and maintaining the same level of granularity. This 40% reduction in the number of bundles similarly reduced the size of SerializedFile at runtime by 40% (from 311 MB to 184 MB).

Conclusion

Using Addressables can significantly reduce memory consumption. You can get further memory reduction by organizing your AssetBundles to suit your use case. After all, built-in analyze rules are conservative in order to fit all applications. Writing your own analyze rules can automate bundle layout and optimize it for your application. To catch memory problems, continue to profile often and check the Analyze window to see what assets are explicitly and implicitly included in your bundles.

Check out the Addressable Asset System documentation for more best practices, a guide to help you get started, and expanded API documentation. 

If you’d like to get more hands-on help to learn how to improve your content management with the Addressable Asset System, contact Sales about a professional training course, Manage Content with the Addressable Asset System. We’re currently offering this training through scheduled live public sessions, though it will be available on-demand as well. 

22 replies on “Tales from the optimization trenches: Saving memory with Addressables”

I swear I’m losing my mind here because a lot of you are both misinterpreting this post dramatically and also convinced that Addressables aren’t one of the better solutions for content memory management in ages across multiple engines. I’d argue that Addressables are a far more important and versatile addition to the engine than the entire data oriented tech stack, while a lot of you are treating it like it’s Enlighten 2.0 or something.

Somebody even said that other game engines have solved this problem, but as somebody who has worked in engines going back to Ogre3D, absolutely not. Content memory management is not a solved problem in any engine aside from in-house ones with specific pipelines often targeting specific games. Unity is not an in-house engine, it is a generalist engine that needs to work with everything from games that run on mobile hardware all the way up to next-gen consoles. Because of this, solutions for content memory management need to be robust and cover a wide variety of use cases. This seems to be a sticking point for a lot of people here.

One person suggested using the resources folder, which is absolutely baffling to me because Unity has been doing everything it can to push people away from the resources folder because it’s slow, dramatically more cumbersome to use than interfacing with Addressables at the C# level, and has a hard size limit of 4gb, rendering it useless for a massive amount of games, even lots of mobile titles.

Another person said that in-editor references have to be replaced, this is COMPLETELY untrue. This is such a misrepresentation of how things work that I’m wondering if the argument was made in good faith in the first place. Yes, you have to replace references if you’re trying to access Addressable content, but I’ll blow your mind with this one neat tip:

Don’t use Addressables for that stuff then. You don’t have to. You’re free. Addressables might not be the best option for your UI or similar things that benefit from direct links like that! So don’t use them! Just like with ECS, this doesn’t have to be and, in fact, is not a total replacement for your entire workflow. What Addressables ARE for is opening up further optimization strategies for people working on games where memory limits and asset streaming are major considerations and a fair few games just looking for improved performance.

What Addressables are NOT are the nightmare that was the old AssetBundle system that was not only more cumbersome to use, suffered even more interface problems, were only modestly faster than the resources folder by default and not even remotely as fast as Addressables when optimized well. I could make the argument that Addressables are the only good core-engine feature Unity has added in ages.

Good to hear a sane voice in the comments!

I always feel that a lot of loud voices in the Unity community tend to not understand how varied much of the professional work done with Unity is. The constant nagging over “why is this feature so difficult” starts to get old. I wish people would read or learn a bit more before posting.

Some of the comments in this post would benefit a whole lot by reading the last part of this chapter were they mention the scale of assets used in Subnautica.

Thank you Unity team for this blog post. Please continue with them!

All of this could be solved with late/lazy loading.

References in inspector having a tick box for lazy/late load ticked aren’t immediately brought into memory, instead they require a call to be loaded, and provide an optional callback once they are.

Much like aSync scenes, only not so convoluted.

No, it really couldn’t, lazy loading like that comes with a whole host of usability and tracking issues that add multiple layers of complexity to any reasonable memory management. A tick box? Cool, great. How do you do it from code? How do you handle lists of things that may need to be loaded at different times under different contexts? How many systems are you making yourself that Addressables currently already has?

“how do you solve… etc” for late/lazy loading?

Follow the tried and tested, true and efficient model of ARC.

Added benefit – warnings that are exact.

EXTRA GAIN: super easy memory clean up options.

Massive PLUS: In editor notification of overlaps and contradictions in efforts to streamline memory

The switch in the Inspector Editor is merely a surfacing of access to this, such that a non-programmer can gain the benefits of optimising memory usage, without needing to read a blog post like the above.

ANY system should be designed for end users, to be as easy to use as possible. The above blog post demonstrates this is something Unity’s not good at.

“Another person said that in-editor references have to be replaced, this is COMPLETELY untrue.” … “Yes, you have to replace references if you’re trying to access Addressable content, but I’ll blow your mind with this one neat tip:” … “Don’t use Addressables for that stuff then.”

So….you meant to say my claim is COMPLETELY true. Sorry your feature has big problems. Doesn’t make it any nicer to use.

Behind the scenes the system has a lot of great stuff. The user facing interface needs a complete overhaul. Unity has a history of refitting flawing systems, like UNET, so hopefully they get the message here.

Yeah maybe it’s better than OGRE and asset bundles, but that doesn’t make it good. The basic interface shouldn’t be any more difficult than manifests of assets that are commutative between editor, addressable, and script.

Not being a one size fit all solution is not a problem at all, let alone a “big problem.”

And I didn’t say I ONLY used Ogre. I’ve also used UDK, UE4, Lumberyard, CryEngine (go here if you want to see a real disaster), and even Gamebryo. Addressables is a far more usable asset memory management solution than anything they over and covers more use cases.

Addressables are a half baked solution – the worst user facing system I’ve seen from unity – even worse than UNET. They are also basically required if you want to have any reasonable control over your game’s memory.

The interface is totally ham fisted – to use it you will have to change the way your program interacts with basic gameobects and prefabs. Simple in editor references have to be replaced to use this system, and there is no built in way to map between asset references and monobehaviours.

Just take a look at the forums to see the pain that awaits. Other game engines have solved this problem, at least passably, for years – this solution is just a bear to work with. Good luck.

It’s not. It’s covering a variety of use cases that will change depending on the size of your game and availability of memory as a resource, and what the best practices in those situations is. This is an in-depth post about the various features of a toolchain, not Intro to Addressables. The third sentence using the term “content loading strategy” should be a giveaway.

This is an intermediate level subject being taught like one.

Addressables may not be the right tool for everyone, but if you decide it’s a solution you’d like to adopt then I strongly encourage you to share any questions or challenges you encounter on our Addressables forums: https://forum.unity.com/forums/addressables.156/

The community and are developers are quite active in that space, and many questions or challenges you face may have already been solved. :)

Funny how the super simple example here would be best served by just using the Resources folder and 2 line of code, instead of using an overengineered and over-complicated solution like addressables.

I like how in a simple blog post on how to solve a fairly simple problem (load asset A or B depending on some factors), you have a multi step blog post, and some of the steps are about solving problems that are inherent to using addressables.

Thanks Unity, because when I want to solve a problem, I actually want to solve 10 problems.

Addressables : Turn your 1 problem into 10 problems.

…what?

The resources folder is not only a terrible solution when dealing with any meaningfully sized project, but it’s also exceptionally slow. Have you actually spent any amount of time using addressables, or are you just complaining about a post?

The answers to your question are a google search away. Nick is a prolific user and recorder of his experiences with unity, over a very long time, and in great depth. He and his dev partner have been huge helpers and providers of profound insights into unity usage.

Deeds: why would I google some random name attached to a comment? Also this does not answer my question about addressable use specifically, which does not match any of the experiences I’ve had with addressables in ages.

niko: the most recent post in that thread is from nearly a year ago

“why should you use google?”

Because nobody else is going to answer your questions when they’re phrased in such a charming manner.

Don’t get it twisted. I asked why I should google a random person’s name attached to a comment, something that people don’t tend to do in general, especially when Nick’s response doesn’t actually address anything other than a perceived system-level overcomplexity based on a blog post being misinterpreted as a an Intro course and not best practices for a variety of situations

I have heard similar things from other experienced developers.

The amount of steps detailed in this article makes it seem that addressables increase complexity by a whole lot.

Except this is covering multiple use cases and even explains the contexts in which you might need to exercise certain practices. This is one of the first good blog posts here in absolute ages because it’s actually covering best use case scenarios and resource management.

Because when you teach/showcase something, you start with the most complex situation… But then you’re just a horrible teacher.
Your argument is not really useful here.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *