Search Unity

Learn to save memory usage by improving the way you use AssetBundles

, April 9, 2020

Whether your application streams assets from a content delivery network (CDN) or packs them all into one big binary, you’ve probably heard of AssetBundles. An AssetBundle is a file that contains one or more serialized assets (Textures, Meshes, AudioClips, Shaders, etc.) and is loadable at runtime.

AssetBundles can be used directly or through systems like the Unity Addressable Asset System (aka Addressables). The Addressables system is a package that provides a more accessible and supported way to manage Assets within your projects. It is an abstraction on top of AssetBundles. While Addressables minimizes the direct interactions developers have with AssetBundles, it is helpful to understand how the usage of AssetBundles can affect memory usage. For an overview of the Addressables system, please refer to this blog post and this session from Unite Copenhagen 2019.

Developers working on new projects should consider using Addressables rather than working with AssetBundles directly. If you are working on a project with an already established AssetBundles approach, the information here about how AssetBundles affect runtime memory will help you get the best possible results.

Losing memory to the memory cache

When Unity downloads an LZMA AssetBundle using the WWW class (which is now deprecated) or UnityWebRequestAssetBundle (UWR), Unity optimizes the fetching, recompressing, and versioning of AssetBundles using two caches: the memory cache and the disk cache.

AssetBundles loaded into the memory cache consume a large amount of memory. Unless you specifically want to frequently and rapidly access the contents of an AssetBundle, the memory cache is probably not worth the memory cost. Instead, use the disk cache.

If you provide a version or a hash argument to the UnityWebRequestAssetBundle API, Unity stores your AssetBundle data into the disk cache. If you do not provide these arguments, Unity uses the memory cache. Note that Addressables uses the disk cache by default. This behavior can be controlled via the UseAssetBundleCache field.

AssetBundle.LoadFromFile() and AssetBundle.LoadFromFileAsync() always use the memory cache for LZMA AssetBundles. We therefore recommend using the UnityWebRequestAssetBundle API instead. If it is not feasible to use the UnityWebRequestAssetBundle API, you may use AssetBundle.RecompressAssetBundleAsync() to rewrite an LZMA AssetBundle on disk.

Internal testing shows that there is at least an order of magnitude difference in RAM between using the disk cache and using the memory cache. You must weigh the tradeoff in memory cost versus added disk space requirements and Asset instantiation time for your application.

To determine what effect the AssetBundle memory cache may have on your application’s memory usage, use a native profiler (our tool of choice is Xcode’s Allocations Instrument) to examine allocations from the ArchiveStorageConverter class. If this class is using more than 10MB of RAM, you’re probably using the memory cache.

Xcode’s Allocations Instruments showing the amount of memory used by the ArchiveStorageConverter class

Find out what your AssetBundles are actually loading

When building AssetBundles for large projects, do not assume that Unity by default will minimize the amount of duplicated information across them. To identify instances of duplicated data in the generated AssetBundles, you can use the handy AssetBundle Analyzer, written in Python by one of our colleagues in the Consulting & Development group. Used via the command line, the tool extracts information from generated AssetBundles, which is then stored in an SQLite database that features several useful views. You can then query the database using tools such as DB Browser for SQLite. This tool can help you find and resolve any inefficiencies in your build pipeline, whether you created bundles manually or via Addressables.

Analyzing the results from the AssetBundle Analyzer tool using DB Browser for SQLite

Alternatively, check out the AssetBundle Browser tool, which you can download and integrate into your project straight away. Note that this tool provides similar functionality to Addressables, so if you are using Addressables, this tool is not relevant.

The AssetBundle Browser tool allows you to view and edit the configuration of AssetBundles in a given Unity project and provides build functionality. It also provides some neat features, such as informing users about duplicated Assets that are being pulled due to dependencies, such as textures.

The AssetBundle Browser tool warns us about duplications due to dependencies being pulled by more than one bundle

Losing memory to duplicate Assets

When deciding how to organize your Assets into AssetBundles, you need to be careful about dependencies. Regardless of your AssetBundle topology, Unity makes a distinction between Assets that live inside the application binary (in or involving a Resources folder) and those that you need to load from AssetBundles. You can think of these two types of Assets as living in different worlds. It is impossible to create an AssetBundle that has a hard reference to the instance of an Asset inside the Resources folder world. To reference those Assets, Unity instead makes a copy of the Assets that it uses in the AssetBundle world. 

Two Components have a hard reference to the “logo” image. If these Components are serialized into different archives (bundled with the player or included in an AssetBundle), each will include their own copy of the image.

Take for example a game’s logo. The logo may be displayed in the UI of a loading scene while the game starts up. Because this loading screen must be shown before remote Assets are streamed to disk, you might include the logo Asset in the build so that it can be used immediately. 

This same logo is also used on an options panel in the UI, where users can select their language, sound preferences, and other settings. If this UI panel is loaded from an AssetBundle, then that AssetBundle will create its own copy of the logo Asset. 

If both the loading screen and the options panel are loaded at the same time, both copies of the logo Asset will be loaded, which is a duplication that costs memory.

The solution to this problem is to break the hard link between one or both screens. If the logo lives in an AssetBundle, then some amount of streaming needs to occur before you can get a reference to the Asset. If the logo lives in the binary (inside a Resources folder, for example), then the UI panel will need to have a weak reference to the logo Asset and be loaded via an API such as Resources.Load.

A string is used as a weak reference to the logo image. User scripting will need to use the string to load the image at runtime and assign it to the proper component.

User scripting will need to use the string to load the image at runtime and assign it to the proper component.A happy middle ground may be to include the AssetBundle containing the logo Asset inside the application’s StreamingAssets directory. You will still load the Asset from the AssetBundle, but since you are hosting the bundle locally, you will not pay the cost in time required to download the content. 

It is not intuitive to use strings, paths or GUIDs to reference Assets, but you may want to create custom inspectors that enable Unity’s default drag-and-drop reference functionality on your weakly referenced fields. And don’t forget to use Unity’s MemoryProfiler package to identify Assets that are duplicated in Memory. Note that the Addressables system has its own mechanism for checking for duplicates in dependencies (for more information,  see the documentation).

Conclusion

Even though the Addressables system provides an abstraction on top of AssetBundles, knowing how things work under the hood can help you avoid costly performance problems like the ones described in this article.

If you’re currently using Addressables we want to hear from you via this short survey.

We’re planning a roadmap for future entries of this series. Is there any area you’d like us to focus on? Leave a comment to let us know!

14 replies on “Learn to save memory usage by improving the way you use AssetBundles”

I pay a quick visit daily a few websites and blogs to read
articles, but this blog provides feature based articles.

This might be a noob question but I can not find what will be included in the asset bundle. If I use the AssetBundle Browser tool to create a bundle then the only the prefab is bundled, not the FBX that this prefab is dependent on. Am I missing something? I was under the impression that ALL resources would be in the bundle?

Given that modern decompression can be so fast why not have an option for compressed in RAM. In theory you can use less ram and it will be so fast when used that it will probably be indistinguishable from direct RAM access or only a fraction slower?

** For Next Article **

Would be nice for you to demonstrate best practices for the advanced and complex use cases.

1) Adding additional game content to a published build, via addressables, without addressables
2) Updating existing game content on a published build, via addressables, without addressables,
3) How to solve the Pink Shader issue in Editor, via addressables, without addressables.
4) How to organize the assets of an AssetBundle, how to decide which assets should be separate AssetBundles, which should be in the same.
5) How to know in editor that some Assets were changed that are part of AssetBundles, which means that you need to reupload your AssetBundles.

Hello!
I find this post very useful since I am always using AssetBundles to dynamically load scenes in runtime.
But apparently there are conflict informations on which method to use.
This articles states this:

“AssetBundle.LoadFromFile() and AssetBundle.LoadFromFileAsync() always use the memory cache for LZMA AssetBundles. We therefore recommend using the UnityWebRequestAssetBundle API instead.”

But the documentation on the class UnityWebRequestAssetBundle.GetAssetBundle says otherwise:

“Note, that while you can use this API to load Asset Bundle from local storage (using file:// URI or jar:file// on Android), this is not recommended, use AssetBundle.LoadFromFileAsync instead.”

In my case, I am currently downloading the AssetBundles from StreamingAssets using the WebRequest class.
My question is: should I keep using the WebRequest method (as this article recommends), or should I switch to LoadFromFileAsync (as the documentation recommends)?

In short; Asset Bundles are garbage over which you have very little control.

They bundle hierarchy of references without telling you. Even with digging in some poor documentation, you would never figure out what exact type of bundles you need to use without running some profiling. Lot of undocumented thing; such if you try to load a type from a bundle, it loads EVERYTHING from the bundle. Don’t get me started on the stupid gymnastic needed to prevent shader duplication IN EVERY SINGLE BUNDLE! Or how Atlas don’t work with Asset Bundle out of the box.

Here’s some of the issues raised with them: https://forum.unity.com/threads/standard-shader-duplicated-in-asset-bundle-build.593248/#post-5020673

Addressables don’t fix any of the issues with Asset Bundles; it simply hides the issues under a layer of abstraction, making it even harder to figure out why something doesn’t work or eat so much memory.

It looks like AssetBundles and Addressables currently have some limitations. For example it would be nice if there would be exposed LZMA compression and extraction endpoints in a similar manner like dotnet uses for DeflateStream and GZipStream (i.e. by passing byte array or encoded string). This would allow to have generic, non-platform specific compressed assets plus it would give ability to create assets on runtime.

Great post! our teams are heavily using asset bundles already. a few questions:

1. We are manually downloading asset bundles (via WWW and then save them to disk using http://www.bytes), so we are not going through any of the asset bundle “specific” APIs – the disk cache is not used in these cases, correct ?

“When Unity downloads an LZMA AssetBundle using the WWW class (which is now deprecated) or UnityWebRequestAssetBundle (UWR), Unity optimizes the fetching, recompressing, and versioning of AssetBundles using two caches: the memory cache and the disk cache.”

I mean – this information is not relevant for our case at the moment.

2. “AssetBundles loaded into the memory cache consume a large amount of memory. Unless you specifically want to frequently and rapidly access the contents of an AssetBundle, the memory cache is probably not worth the memory cost. Instead, use the disk cache.”

Is there any way to avoid the memory cache then?

3. AssetBundle.LoadFromFile() and AssetBundle.LoadFromFileAsync() always use the memory cache for LZMA AssetBundles. We therefore recommend using the UnityWebRequestAssetBundle API

If all our bundles are using LZMA, will we benefit anything from moving to UnityWebRequestAssetBundle ?

Hey there!

I’ve run by your questions to one of our staff members who provided me with the following response:

1: Correct! The AssetBundleCache will not be used unless a cache version/hash is provided in the download.

2: The naming “memory cache” is rather misleading. This is simply decompression. If you have an LZMA AssetBundle, then it needs to fully decompress in memory as the entire file before any data can be used. If you are loading an LZ4 AssetBundle then it can decompress by chunks that cover the data blocks that you need. Uncompressed, it would not have any need to decompress into memory “cache” at all.

3: There could be some benefit in that it will recompress the AssetBundle. In this regard however, you can manage your own cache and use LoadFromFile. By using AssetBundle.RecompressAssetBundleAsync, using the two runtime enum LZ4/Uncompressed, you can mimic similar behaviour of using our Caching API. By recompressing an AssetBundle you receive from http://WWW.bytes to Uncompressed or LZ4 to manage how much decompress in memory is needed.

The assetbundles methods are very integrated into Unity and sometimes linked to versions of Unity and always by platform for things like Shaders. A better way might be to come up with independent cross-platform compatible assets – Textures (platform independant, convert on load or with copy per platform), Meshes, AudioClips, Shared Material (definition) library instead of shaders. This is less optimal but more flexible

The blog posting keep asking for a captcha but none exists

Perfect timing, I’m working on a live game for mobile right now, Addresables is a treasure! I easily implemented a remote config system for themes in the game, so for every occasion, the team can change UI and feel of the game without sending a forced update on the store.
However, I’m not completely sure if my system is robust and maintainable. More best practices and structural tutorials on addressables would be nice. <3

Comments are closed.