Asset load times: Cache vs Download

Hi, Is it normal for loading from the cache to take almost as long as downloading the assets for the first time?

Also I am seeing strange results when running the same code in a Windows build. Downloading can take a few minutes, and during this time PC freezes (frame rate drops to 1). However, loading from cache is very fast, taking only 0.6 seconds.

When I add *yield return null;* inside the for loops, the download becomes smooth and only takes 6 seconds, but then loading from cache also takes the same 6 seconds.

Am I doing something wrong? and what can be done to speed-up loading times in Web?

Link to the web build, in case you want to test it yourself:
https://skillwarz.com/webtest/bundles/

Here’s a simplified version of the script I’m using:

    private IEnumerator Load()
    {
        for (int i = 0; i < spriteReferences.Length; i++)
        {
            int index = i;
            LoadAssets<Sprite>(spriteReferences[i], index, 3);
        }

        for (int i = 0; i < meshReferences.Length; i++)
        {
            int index = i;
            LoadAssets<Mesh>(meshReferences[i], index, 2);
        }

        for (int i = 0; i < animationReferences.Length; i++)
        {
            int index = i;
            LoadAssets<AnimationClip>(animationReferences[i], index, 1);
        }

        for (int i = 0; i < materialReferences.Length; i++)
        {
            int index = i;
            LoadAssets<Material>(materialReferences[i], index, 0);
        }

        while (assetsDownloaded < totalAssets)
        {
            yield return null;
        }

        //...
    }

    private void LoadAssets<T>(AssetReference assetRef, int index, int tips) where T : UnityEngine.Object
    {
        var handle = assetRef.LoadAssetAsync<T>();

        handle.Completed += (h) =>
        {
            if (h.Status == AsyncOperationStatus.Succeeded)
            {
                switch(tips)
                {
                    case 0:
                        materialMap[index] = h.Result as Material;
            
                        if (materialMap.Count == materialReferences.Length)
                        {

                        }
                        
                        break;
                    case 1:
                        animationMap[index] = h.Result as AnimationClip;

                        if (animationMap.Count == animationReferences.Length)
                        {
 
                        }
                        
                        break;
                    case 2:
                        meshMap[index] = h.Result as Mesh;

                        if (meshMap.Count == meshReferences.Length)
                        {

                        }
                        break;
                    case 3: 
                        spritesMap[index] = h.Result as Sprite;
                        
                        if (spritesMap.Count == spriteReferences.Length)
                        {

                        }
                        break;                      
                }
            }
            else
            {
                Debug.LogError($"Failed to load asset: {assetRef.RuntimeKey} - Status: {h.Status}");
            }
        };
    }
1 Like

If I’m not mistaken you will always load from cache when the cache is enabled. The first time you download an asset, it will download it to the cache first, but the load into the game (into RAM) will still happen from the cache.

By default when you download bundles into the cache, Unity will decompress and recompress them into LZ4 format. So this could account for slower loading speeds, as well as of course the time it takes to download the assets (which I am guessing are stored on a remote CDN).

Without the yield return null that loop will just keep running until the assets are integrated into the game, which will block the main thread and stall your game.

The one thing I don’t understand is why non first time loads only take .6 seconds without the yield return null, and 6 seconds with it. I wouldn’t imagine that it would have any bearing on the load time; it should only affect whether the main thread is blocked or not during the loading process.

Also, it’s sort of surprising to me that it works at all without the yield return null, as I would have assumed that Unity needs the main thread to complete the integration of assets step. But perhaps my understanding of this is wrong.

Hi!

How large are the asset bundles that you are downloading? And what does the “Network” tab say in the Chrome Dev Tools?
How long does the download of the file take and how long to load it from cache?

Hi, link is still working:
https://skillwarz.com/webtest/bundles/

WebGL folder is 133 MB

I just looked at the Network tab in the Chrome Dev Tools and it looks like you have a lot of very tiny Asset Bundles. Some of them under 10 kB. I think this introduces a lot of overhead since it creates a lot of web requests and async loading operations.
This isn’t really optimal since both very large or very small (but many) AssetBundles can create bottlenecks. From the Addressables documentation:

  • Runtime performance: performance bottlenecks can occur if your bundles become very large, or alternatively if you have a very large number of bundles.

Some web requests stay 6 seconds in the waiting queue because there are so many parallel requests.
Per default, a asset bundle loaded from the cache is revalidated using the if-modified-since request header. The server will return a 304 with an empty body if the asset bundle is still up to date. For tiny files this revalidation can take just as much time as downloading it because of the overhead of the request.

I would recommend to combine the smallest asset bundles so your bundles are at least a few MB in size(you will have to do some trial an error to find the sweet spot).
You can also try to change the cache policy so that the revalidation is skipped and the bundles are always loaded from cache: Unity - Manual: Cache behavior in Web

// In your index.html add cacheControl to the config object
var config = {
   // ...
   cacheControl: function (url) {
     // Caching enabled for .data and .bundle files.
     if (url.match(/\.data/) || url.match(/\.bundle/)) {
         // This is the default behaviour
         // return "must-revalidate";

         // Load file from cache without revalidation.
         return "immutable";
     }

     // Disable explicit caching for all other files.
     // Note: the default browser cache may cache them anyway.
     return "no-store";
   },
   // ...
}