January 20 2020

Unity Addressables: Compression Benchmark


Today I’ll show you how to select the best Unity Addressable compression method for your asset bundles.

In this post you’ll learn:

  • How to reduce your asset bundle sizes
  • How to pay less to your greedy CDN
  • Should you use Uncompressed, LZ4 or LZMA?

So if you want to make the most of Unity addressable assets, you’ll find this post useful.

Let’s get started.

Some time ago, I had the goal to optimize the memory usage of my game.

I had just too much content to fit in RAM.

So the natural way to go was the addressables route.

After putting some great effort into learning the system, I got a rock-solid system up and running. The result was a 50% memory usage reduction. Not bad.

But I was just barely scratching the surface of Addressables. There was so many features I was missing out.

So I went through all Addressable settings I could find.

One instantly caught my attention: the asset bundle compression mode.

Interesting, this will prove to be useful, I said to myself.

There I clearly saw the opportunities Unity gave me. Unity gave me a powerful tool I could use to work on two crucial variables: storage size and CPU performance.

The three compression modes Unity offers you are: Uncompressed, LZ4 and LZMA.

I was intrigued. I really wanted to see what difference each mode did in real-life.

However, at the same time, I couldn’t stop asking myself… is paying attention to this really worth my time?

Of course, the lack of Addressables documentation from Unity’s side was to be expected. Busy developers, busy times. So as it often happens with Addressables, I couldn’t count on them.

But in spite of the time investment, here’s the deal: I decided I would make the research worth. I decided to make a benchmark to understand the real-world implications of Unity addressables compression modes.

And today I’ll share with you the results of my investigation and my thoughts on the subject.

Chapter 1: What Is Addressables?

The default way of working with assets in Unity is by using direct references your content. Direct references load all assets automatically for you into memory. This is comfortable, but often leads to an explosion of memory consumption. And that, my friend, we want to fight.

Unity Addressables is the piece of technology game developers urgently needed to use indirect references.

Indirect references let you decide when to load and unload your content into and out of your memory. This gives youan immense control over memory.

When we use Addressables, we aim to optimize memory usage.

But that’s not all. Optimizing memory has many secondary benefits such as reduced loading times, less crashes and better user reviews, i.e. more cash to your pocket.

With addressables we organize all our content around asset groups…

And addressable groups are data buckets that contain our assets in a very specific way.

In every supermarket you’ll see tons of assets: (warm) soda drinks, bread, juicy fruit, etc… These assets are packaged differently. You don’t want to pour some cola into a paper bag you put bread into. Each packaging is well suited for certain types of goods.

Well, Coca-cola, fanta, water, bread, rice are your assets. And the different packaging you put your assets in are your groups. Groups dictate the way you store and deliver your assets, including compression settings (a looser or a tighter cardboard box).

Assigning assets to groups is easy as you can see below.

Assigning Addressable Assets to a Group

To learn more about addressables, check my Addressables Tutorial (Learn the Basics).

Chapter 2: What Is Addressables Compression?

Your game might have thousands and thousands of assets. And as we saw, different content type require different packaging (groups).

Compression is one of the group parameters that will determine how efficiently Unity stores and loads your assets.

You can choose between the compression modes below:

  • Uncompressed
  • LZ4
  • LZMA
The question is then: how can you make the most of compression in your game?

When you assign assets to a group, you are effectively packing these assets together like in a ZIP file.

And as you might recall with WinZIP and its family, you have several compression settings.

The strongest compression levels tend to save more data, but they also steal more resources to compress and decompress.

Therefore, you and I must always seek to hit the balance between storage space savings and performance cost.

Just like with WinZIP, you have to apply the same principle to Unity addressables compression settings.

So, which compression modes do you have?

Uncompressed means: you store the asset data as it is. Theoretically, this requires no further processing and just I/O bandwidth.

LZ4 is the second compression mode you have. I’ts an ultra-fast compression algorithm that achieves pretty good ratios for its performance cost. No wonder this is the default setting for new addressable groups.

But sometimes we’re not happy with our Seat and want a Tesla instead. We’re willing to toss a good handful of cash into what seems a more powerful alternative, no matter the cost. Here’s where LZMA serves you.

LZMA is a (theoretically) more potent Unity addressables compression algorithm. With LZMA we can potientally achieve greater compression ratios by throwing more hardware into the task.

Wondering where to find these compression modes? Have a look below, that’s how I usually set my video groups.

Group settings for Addressable Videos

As you might know, most videos are heavily compressed with H264 already. So in general you don’t have to re-apply compression on top of them.

But let’s be honest, videos is just a single piece of the puzzle.

What’s the crack with the rest of the content types?

And more importantly… I want numbers!

Chapter 3: Benchmarking Addressables Compression

If you’re reading this, it’s probably because you want to make informed decisions in the future.

I’ll help you do that in this chapter… with numbers.

I took some time to measure the impact of addressable group compression levels on two metrics:

  • Resulting asset bundle storage size
  • Time-to-load

I did this test with different content types and several formats to include more information:

  • Textures: RGB32, ETC2, ASTC
  • Music: PCM, ADPC, Vorbis
  • Videos: H.264
  • Text: Epic Lorem Ipsum

Benchmark Setup

It’s time to get some juicy numbers. They often reveal the truth the writer wants them to reveal.

To do the benchmark I imported public domain assets into a new Unity project for Android.

Each asset will go into its own addressable group and therefore into its own asset bundle.

For a better insight on the numbers, I duplicated the assets to apply specific settings, such as different texture compression algorithms and asset bundle compression modes.

See below the asset combinations I used for this benchmark.

Unity Addressables: Group Compression
public class AddressablesGenericContentLoader : MonoBehaviour
{
    [SerializeField] private AssetReference[] assetReferences = null;
    private const float WaitTime = .1f;
    private float _lastTimeMs;
    private string _lastObjectName;
    
    private IEnumerator Start()
    {
        yield return Addressables.InitializeAsync();
        const int repetitions = 5;
        for (var i = 0; i < assetReferences.Length; i++)
        {
            var totalMs = 0f;
            for (var repetition = 0; repetition < repetitions; repetition++)
            {
                yield return new WaitForSeconds(WaitTime + 1f);
                yield return TestLoad<UnityEngine.Object>(assetReferences[i]);
                totalMs += _lastTimeMs;
            }
            Debug.Log($"[TheGamedevGuru] \"{_lastObjectName}\" {totalMs/repetitions}ms");
        }
        Debug.Log($"[TheGamedevGuru] Finished");
    }
    
    private IEnumerator TestLoad<TObject>(AssetReference assetReference)
        where TObject : UnityEngine.Object
    {
        GC.Collect();
        yield return Resources.UnloadUnusedAssets();
        var stopwatch = new Stopwatch();
        stopwatch.Start();
        var loadOperation = assetReference.LoadAssetAsync<TObject>();
        yield return loadOperation;
        stopwatch.Stop();
        _lastObjectName = loadOperation.Result.name;
        _lastTimeMs = stopwatch.ElapsedMilliseconds;
        yield return new WaitForSeconds(WaitTime);
        assetReference.ReleaseAsset();
    }
}

That’s the way I defined the addressable groups (asset bundles) must be created, stored and loaded.

The way I did the benchmark was to create a script that loads all my addressable assets sequentially. The script measures how long it took to load each asset 15 times and computes an average. I did the measurements with probably the most accurate C# API: StopWatch.

Here’s a screenshot of the component inspector and its code.

Unity Addressables: Compression Benchmark-Inspector

Curious to see how this performs in my Mate 20 Pro?

I would.

So let’s skip the line and start putting video addressables compression to the test.

Benchmarking Videos

Here’s the deal.

I have a full-hd H.264 video and I made copies of it to get 3 combinations.

The first group will have no addressables compression applied to it. The second will be compressed with LZ4 and the third with LZMA.

I then built the asset bundles, deployed the benchmark into a Mate 20 Pro and got the numbers…

I won’t make you wait any longer, that’d be cruel. Here are your results.

ModeStorage (KB)Average Loading Time (ms)
Uncompressed49,405287
LZ449,084290
LZMA49,407291

What do you think?

I see no clear winner on any metric.

I clearly expected the storage size not to vary much with further compression, as H.264 is a highly efficient video codec. This is the reason I always recommend my game performance students to turn off video compression.

Regarding loading time, well, it increases with compression as one would expect. However, the difference is not noticeable in this piece of… hardware.

As a side note, Unity plays videos through streaming techniques. That means the video content is not entirely loaded into memory but just a small window. I didn’t test this, but I’d expect to be paying a higher CPU cost if streaming through a compressed asset bundle.

Benchmarking Audio

Audio becomes more interesting. To show bigger differences, I’ll go for longer clips such as music.

In this benchmark I’ll test 3×3 variations: vorbis/pcm/adpcm + uncompressed/LZ4/LZMA. The first compression setting is an audio-specific parameter you set in the AudioClip import settings and this reflects the audio codec to use.

I set all AudioClips set to decompress on load and to load synchronously.

Without further delay, here are the results for you to enjoy.

ModeStorage (KB)Average Loading Time (ms)
PCM – Uncompressed23,893224
PCM – LZ423,890217
PCM – LZMA→ 2 2,266208
ADPCM – Uncompressed6,724328
ADPCM – LZ46,712320
ADPCM – LZMA→ 5,687323
Vorbis – Uncompressed3,828​669
Vorbis – LZ4​3,825671
Vorbis – LZMA3,834672

LZMA has a clear edge on compression ratio with PCM and ADPCM.

However, I do not see significant changes in loading times in my tests.

For this reason, I can definitely recommend you testing LZMA in your project if you’re using PCM or ADPCM.

Benchmarking Textures

With textures I followed a similar benchmarking format as wit audio.

I cloned textures so I could apply different native and addressables compression settings.

This benchmark approach gives me 3×3 combinations: RGBA32/ETC2 8-bit/ASTC6x6 + Uncompressed/LZ4/LZMA.

And here are the results.

ModeStorage (KB)Average Loading Time (ms)
RGB32 – Uncompressed87,386878
RGB32 – LZ468,775880
RGB32 – LZMA→ 38,5671072
ETC2 – Uncompressed21,850246
ETC2 – LZ411,286230
ETC2 – LZMA→ 7,946276
ASTC6x6 – Uncompressed9,733134
ASTC6x6 – LZ49,622130
ASTC6x6 – LZMA→ 8,691176

It looks like we’ve got some tasty differences, don’t you agree?

You can see the biggest differences both in storage and loading times in uncompressed textures (RGB32). As you would expect, compression works best with uncompressed content.

With uncompressed content, Unity addressable compression modes shine brighter than a laser pointing directly at your eye.

LZMA halves the storage requirements of uncompressed textures at the cost of slightly increasing the loading time. Not bad.

LZ4 does a good job at a negligible cost… And this is always welcome. Thanks LZ4.

The gains you get with LZMA are still present with ETC2, which is great. Since ETC2 is a block-based compression algorithm, the file itself can still be compressed as a whole to get noticeable gains.

With the last-generation compression algorithm ASTC you still get some savings, but you start to see diminishing returns.

For textures, I’d suggest you to clearly go for LZ4 unless you’re going for highly efficient compression modes such as ASTC. In the last case, decide whether storage size or CPU time is more important to you and go for it.

Benchmarking Text

The last test I prepared is to create a huge text file.

I filled the file by overclocking an online lorem ipsum generator that froze my browser for a couple of minutes.

The result? A monstrous 30MB text file that no engine should ever dare to load.

Heck, even Visual Code was not happy to load it.

In any case, here’s what you get:

ModeStorage (KB)Average Loading Time (ms)
Uncompressed31,472305
LZ49,766285
LZMA→ 23 !!311

Jeez, did you see that?

I honestly expected huge wins, but LZMA really outperformed my expectations here.

That was not a typo. LZMA reduced 30MB to 23KB.

I won’t make a recommendation here.

Chapter 4: Conclusion

We made it!

Now we have some interesting numbers to judge how practical each compression method is.

Let’s give the three compression methods a fair trial based on these variables:

  • Storage Size
  • Loading Time
  • Content import settings native compression

If you look again at the numbers, you can probably see a pattern.

The more compressed your original content is, the worse returns you’ll get by applying Unity addressable-based compression.

That said, you can’t do wrong by going to LZ4. It’s a pretty fast algorithm that will give you noticeable savings on most content types.

So here’s where I summarize my recommendations, I guess?

Yeah, let’s do that:

  • Videos: uncompressed
  • Textures: LZMA for old-generation formats, LZ4/LZMA for ASTC and newer
  • Audio: LZMA for PCM/ADPCM, uncompressed for Vorbis
  • Text: LZMA
  • Mixed content (prefabs, meshes, etc.): LZ4 (for CPU performance) or LZMA (for storage size reduction)

I encourage you to test and measure my guidelines in your project and share your findings. I’m curious about your results.

If you want to become a game performance expert, subscribe now to my newsletter to receive an experimental free 3-day level-up program with my best content:

  • UI Optimization: learn how to squeeze the most of your UI performance juice
  • Memory Performance: improve your memory management to free up critical resources for your CPU and GPU
  • CPU: gain access to little-known techniques to easily boost the CPU performance of your game

Subscribe now and don’t miss out,

~Ruben

The Gamedev Guru Logo

Performance Labs SL
Paseo de la Castellana 194, Ground Floor B
28046 Madrid, Spain

This website is not sponsored by or affiliated with Facebook, Unity Technologies, Gamedev.net or Gamasutra.

The content you find here is based on my own opinions. Use this information at your own risk.
Some icons provided by Icons8