Excessive memory usage by shader variants

Hello!

When doing some memory “profiling” in our project that uses Babylon I found that Chrome is reporting a lot of memory being used to store strings that seem to represent PBR shaders.
I know that Babylon creates a lot of shader variants with the PBR shaders, so I quickly tested some artificial cases to see how bad it can get, and it wasn’t hard to get into the hundreds of megabytes of strings, which seems to be mostly these strings.

I then tried tracking down where shader source code is stored in strings, and found a couple of places:

  • The keys to the _compiledEffects map on the Engine
  • In Effect class:
  • _fragmentSourceCode
  • _vertexSourceCode
  • fragmentSourceCodeBeforeMigration
  • vertexSourceCodeBeforeMigration

I made a Playground example that creates an artificial, but not unrealistic, case for us, where we basically just automatically cycle through a bunch of light configurations to generate shader variants. It also estimates the worst-case memory usage of the variables listed above, which it prints out into the console every 4 seconds.

Screenshot with Chrome’s memory profiler showing the shader code

Now in that specific example, I reach about 16MB of strings after not too long (counted from those variables, Chrome reports more strings being used). It’s not too much, but still noticeable on mobile devices. But an important thing to note is that we usually have several variants of PBR materials (regardless of lights) due to being sourced from different GLTFs, meaning the number multiplies quite fast:

  • In an artificial test case we have, we reach upwards of 400MB of these strings (again, Chrome reports slightly more, but there’s other data as well of course). They’re spread over 277 shader variants.
  • In a realistic scenario, we easily reach 100MB of these strings (87 variants), which is a big portion of our budget on some mobile devices. However, it’s not unreasonable for some of our user journeys to double or quadruple that number if they play around with the light configuration.

If you have any issues with my method of measuring this, please let me know! I don’t know the inner workings of Babylon, and I’m not that used to the web environment either!

I wanted to write this just to make sure you were aware of it, and maybe you can point us in the right direction to alleviate some of these issues:

  • Is there a way to purge the cache of unused shaders somehow?
  • Is there a way to discard the source code copies, and maybe use a hash for the _compiledEffects array, to reduce the memory footprint?

Or if you have any other ways to otherwise reduce this memory footprint, I’m all ears :slight_smile:

If the answer is no to those questions, then we might look at creating a PR (or if you want to do it) if you think it sounds like a good idea.

Thanks in advance,
Tore

2 Likes

@sebavan @Evgeni_Popov @Deltakosh Thoughts?

One thing I can totally think of is exposing a function to wipe the cache. That will force the system to recompile if the same shader is encountered again

Other idea that I like is to write a smarter hash function so the key is far smaller but I have no immediate idea on that front

With a hash we may have the risk to return the wrong shader so we must ensure it is really unique.
@rapid.tore.levenstam tell me if you want me to provide the purge function

We can also have an option in the engine to NOT store the source code. This is is quite easy to do and I think it will solve your issue here

@sebavan / @Evgeni_Popov : on my phone right now but I believe we should not store the code by default. I do not think it will be a breaking change as it is internal ( and we could have an engine option to turn it on if we need it for debugging reason)

vertexSourceCodeBeforeMigration and fragmentSourceCodeBeforeMigration are used by ShadowDepthWrapper. If this class is not used, these variables can be cleared, else we should keep their values. Maybe we should create a static flag on the Effect class to let users indicate they are not interested to use ShadowDepthWrapper and let the system save memory by not storing data in these variables?

1 Like

@rapid.tore.levenstam here is my proposal:
Allow users to clear code cache by deltakosh · Pull Request #15706 · BabylonJS/Babylon.js (github.com)

3 Likes

Awesome, thanks! I think it looks good :slight_smile: Will give this a try in our project as soon as possible.

We might be in different timezones, sorry for the slow reply :stuck_out_tongue:

No worries :slight_smile: Keep us posted!

Was finally able to try this out in our product, took some time to get us upgraded to the new version. The fix you implemented seems to do the trick in our product as well.
Thank you very much!

2 Likes