Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Bug Unity WebGL builds cause disproportionate browser memory usage

Discussion in 'WebGL' started by jrabek, Jun 16, 2021.

  1. jrabek

    jrabek

    Joined:
    Feb 4, 2020
    Posts:
    15
    Question
    Is there any way to reduce the amount of memory that the browser process uses when running Unity webgl builds?

    Excessive memory usage causes low memory warnings and failures to load on mobile.

    Context

    On both mobile and desktop browsers (tested on Mac but assuming Windows as well), a single-scene project with a single image will use around 46MB according to a heap snapshot in Chrome, but the browser process will have ~160MB or more memory footprint ("memory" column in Activity Monitor or top, or "Physical footprint" in vmmap).

    Additional Info
    Tests in this thread were done using Unity version 2020.2.7f1

    upload_2021-6-16_19-47-17.png

    This problem was originally noticed since a relatively simple 2d game with ~90MB js heap snapshot was resulting in a Safari webkit process using 700MB(!) on load.

    Is the excessive memory usage due to assets or webassembly?
    To determine what may be causing it I tried measuring a variety of pages using the same approach as above.

    Note that `basic-unity-project` is the single-scene, single-image project referenced above.

    While graphical assets do take up memory, the usage of webassembly is what really seems to cause the issue.

    Is it excessive unused heap?
    Juj from Unity mentioned that WebAssembly needs a better memory model in this thread: https://github.com/WebAssembly/design/issues/1397.

    However in this case the excessive memory usage doesn't seem to be from unused heap space since attempting to lower the amount of available memory causes the `basic-unity-project` to crash with an out-of-bounds error and the minimum values that allow it to run result in the same chrome heap snapshot size and the same browser memory size.
    Code (CSharp):
    1. PlayerSettings.WebGL.emscriptenArgs = "-s WASM_MEM_MAX=32MB -s ALLOW_MEMORY_GROWTH=0 -s TOTAL_MEMORY=32MB";
    What inside Chrome is causing the issue?
    To see if the memory usage was obvious, I set a breakpoint in the index.html page of the basic-unity-project before the unityLoader runs and captured a memory map of Chrome using vmmap. Then I let the loader run and took another memory map. Those files are attached if someone wants to look at the diff. It isn't super obvious what is happening since "Memory Tag 255" is a general allocation.

    The other approach was to use Instruments and repeat the same experiment.
    Allocations:
    upload_2021-6-16_20-12-47.png

    Generations:
    upload_2021-6-16_20-13-51.png

    In the generations view you can see that there is a ~127MB growth when the basic-unity-project loads. And that growth is largely due to the non-object growth. The non-object allocations seem to be a combo of capturing stack traces (because the js console is open?) and AudioParamTimeline parameter creation.
     

    Attached Files:

    gtk2k and Meltdown like this.
  2. jrabek

    jrabek

    Joined:
    Feb 4, 2020
    Posts:
    15
  3. jrabek

    jrabek

    Joined:
    Feb 4, 2020
    Posts:
    15
    Here is another interesting data point. Doom 3 compiled to web assembly (https://wasm.continuation-labs.com/d3demo/) has a ~800MB memory footprint. This game has many textures, sound, animation, behavior code, etc all loaded into memory (http://www.continuation-labs.com/projects/d3wasm/).

    The js heap looks like this
    upload_2021-6-18_14-13-6.png


    Our simple 2d game built in Unity (http://what-are-you-talking-about-staging.airtime-gaming.live/) has a 500MB footprint. Note I said 700MB above since sometimes it can go this high, but this most recent measurement was 500MB.

    The js heap looks like this.
    upload_2021-6-18_14-15-48.png

    So it doesn't seem that webassembly directly triggers the problem but rather something in the compiled webassembly code that Unity produces that triggers large or large numbers allocations in Chrome and Safari.
     
    Last edited: Jun 18, 2021
  4. jrabek

    jrabek

    Joined:
    Feb 4, 2020
    Posts:
    15
    One more data point: In the Instruments traces there were many small wasm allocations being done by Chrome. By disabling webassembly streaming by changing the mime type so ArrayBuffer fallback was used, the memory footprint dropped by 200MB for our simple 2d Unity game. The memory fragmentation has a huge effect.

    Even simple games like https://play.unity3dusercontent.com...d6886b472d81?screenshot=true&embedType=detail have 200MB to 300MB memory footprints and have webassembly streaming.

    So the only other big culprits left may be related to leaks in WebAudio (the AudioParamTimeline allocations mentioned before), and the constant stack traces being triggered which may be related to exception handling being enabled in builds(?). For the exception handling, disabling it causes the Unity editor to crash so need to check a different Unity version.
     
  5. De-Panther

    De-Panther

    Joined:
    Dec 27, 2009
    Posts:
    553
    Did you disable the read/write of the textures?
    I know about a leak in the Audio in Chrome, but not in Safari.
    Interesting stuff
     
  6. jrabek

    jrabek

    Joined:
    Feb 4, 2020
    Posts:
    15
    I did disable the writing of textures since I had read . There aren't that many textures anyway so I don't think it would explain such a massive memory footprint even if fully decompressed and residing in the js heap and not just GPU memory.

    I had passed through the following:
    * https://blog.unity.com/technology/understanding-memory-in-unity-webgl
    * https://learn.unity.com/tutorial/memory-management-in-unity
    * https://docs.unity3d.com/2019.3/Documentation/Manual/webgl-memory.html

    Note that the issue seems to affect multiple games built by Unity including the basic-unity-project I referenced above which only has a single 256x256 texture in it.
     
  7. De-Panther

    De-Panther

    Joined:
    Dec 27, 2009
    Posts:
    553
    Maybe it's related to the open devtools? the TypedArrays have a reference from the devtools, so the JS GC doesn't clean them?
     
  8. jrabek

    jrabek

    Joined:
    Feb 4, 2020
    Posts:
    15
    So even without the devtools open it reaches a similar memory footprint across Safari and Chrome on both mobile and desktop. So while it is possible the stack traces come from the devtools, it isn't the cause of the large memory footprint.
     
    De-Panther likes this.
  9. jrabek

    jrabek

    Joined:
    Feb 4, 2020
    Posts:
    15
    Some additional experiments with our simple 2d game in Chrome 91 on a Macbook pro.

    1. Starting WebL memory footprint 375MB. Webassembly streaming is disabled (web server returns binary/octet-stream instead of application/wasm for the mime type).
    2. Enable audio compression in memory (had missed this before). 368MB.
    3. Optimize for size instead of speed. 340MB.
    4. Enable webassembly streaming. 360MB.
    5. Set exception handling to none (note game no longer will work because exception handling is used for flow control in some dependencies). 345MB.

    Note that the actual memory footprint can depend on reloads as discussed elsewhere so a fresh page load was used every time.
     
  10. jrabek

    jrabek

    Joined:
    Feb 4, 2020
    Posts:
    15
    Interestingly, Firefox (below) and Chrome have similar memory footprints of ~350MB but Safari 14.1.1 has 470MB.

    Memory in Firefox 90:

    373.08 MB (100.0%) -- explicit
    ├──245.42 MB (65.78%) -- window-objects
    │ ├──245.27 MB (65.74%) -- top(http://what-are-you-talking-about-staging.airtime-gaming.live/, id=36)
    │ │ ├──241.35 MB (64.69%) -- active/window(http://what-are-you-talking-about-staging.airtime-gaming.live/)
    │ │ │ ├──241.22 MB (64.66%) -- js-realm(http://what-are-you-talking-about-staging.airtime-gaming.live/)
    │ │ │ │ ├──237.97 MB (63.79%) -- classes
    │ │ │ │ │ ├──154.34 MB (41.37%) -- class(WebAssembly.Instance)/objects
    │ │ │ │ │ │ ├───87.63 MB (23.49%) ── non-heap/code/wasm
    │ │ │ │ │ │ ├───66.72 MB (17.88%) ── malloc-heap/misc
    │ │ │ │ │ │ └────0.00 MB (00.00%) ── gc-heap
    │ │ │ │ │ ├───82.27 MB (22.05%) -- class(ArrayBuffer)/objects
    │ │ │ │ │ │ ├──64.00 MB (17.15%) ── non-heap/elements/wasm
    │ │ │ │ │ │ ├──18.27 MB (04.90%) ── malloc-heap/elements/normal
    │ │ │ │ │ │ └───0.00 MB (00.00%) ── gc-heap
    │ │ │ │ │ └────1.36 MB (00.36%) ++ (6 tiny)
    │ │ │ │ └────3.25 MB (00.87%) ++ (7 tiny)
    │ │ │ └────0.13 MB (00.04%) ++ (3 tiny)
    │ │ └────3.92 MB (01.05%) ++ js-zone(0x101e8c000)
    │ └────0.15 MB (00.04%) ++ top(none)/detached/window(view-source:http://what-are-you-talking-about-staging.airtime-gaming.live/)
    ├──108.86 MB (29.18%) -- webaudio
    │ ├──108.86 MB (29.18%) ── audiobuffer
    │ └────0.00 MB (00.00%) ++ (2 tiny)
    ├────8.89 MB (02.38%) -- js-non-window
    │ ├──4.87 MB (01.31%) ++ zones
    │ └──4.02 MB (01.08%) ++ (4 tiny)
    ├────4.40 MB (01.18%) ── heap-unclassified
    ├────4.12 MB (01.10%) ++ heap-overhead
    └────1.39 MB (00.37%) ++ (22 tiny)


    Finally, looking at Safari mobile which is what started this investigation, you can see that the same game takes up ~1GB (webassembly streaming enabled) or 580MB (webassembly streaming disabled) when running as a webview. Note the 200% CPU also from just sitting on the splash screen.
    upload_2021-6-21_23-9-25.png

    upload_2021-6-21_23-13-5.png
     
  11. jrabek

    jrabek

    Joined:
    Feb 4, 2020
    Posts:
    15
    When our simple game, what-are-you-talking-about, is running in Chrome and has a 635MB memory footprint, here is what the Unity profiler captures:

    upload_2021-6-25_16-27-52.png

    Which all looks very reasonable. So still not sure what is triggering the huge memory footprint.
     
    De-Panther likes this.
  12. jrabek

    jrabek

    Joined:
    Feb 4, 2020
    Posts:
    15
    Note for anyone who reads this thread later:

    After discussing with Unity, the WebGL target on mobile even for relatively simple games is not currently a viable solution.

    The team is currently working on it and with optimizations/improvements, iOS 15 using webgl2, and Android moving to 64 bit webassembly support the situation should improve.

    It sounds like it is better to wait until some time in 2022 before targeting mobile web for games built with Unity.
     
    KamilCSPS likes this.
  13. CodeStarrk

    CodeStarrk

    Joined:
    Jan 5, 2017
    Posts:
    37
    4 months later, have you found any other solutions to improve webgl? Other than updating unity version?
     
  14. pitibonom

    pitibonom

    Joined:
    Aug 17, 2010
    Posts:
    188
    I guess the best and first improvement for webgl would be to leave it apart :D lol
     
  15. OceanX000

    OceanX000

    Joined:
    Feb 24, 2021
    Posts:
    120
    154.34 MB (41.37%) -- class(WebAssembly.Instance)/objects----used by WebAssembly compliation and JIT
    66.72 MB (17.88%) ── malloc-heap/misc--(Maybe)uesd by page(mainly canvas and other things)
    64.00 MB (17.15%) ── non-heap/elements/wasm---used by Heap(Mono and Native)
    108.86 MB (29.18%) -- webaudio---used by browser audio
     
  16. rendermouse

    rendermouse

    Joined:
    Dec 9, 2015
    Posts:
    8
    Has anyone seen a WebGL audio leak in iOS mobile Safari? Because I am seeing my audio loops slowly drag down and degrade into static after about 2-3 minutes.
     
  17. RamblingCoder

    RamblingCoder

    Joined:
    Jul 13, 2013
    Posts:
    16
    We had a similar issue with audio static after a couple of minutes. I was able to resolve that by using the code provided in this thread:

    From https://forum.unity.com/threads/audio-distortion-crackling-on-ios-devices.1111855/#post-7329685

    Specifically patching the Audio.js file in the Unity file
    C:\Program Files\Unity\Hub\Editor\2020.3.25f1\Editor\Data\PlaybackEngines\WebGLSupport\BuildTools\lib
    Audio.js for this function:

    Code (JavaScript):
    1.  
    2. JS_Sound_SetVolume: function (channelInstance, v)
    3.  
    Note: if you are using a different version of Unity you will find the Audio.js file in the appropriate folder instead of 2020.3.25f1
     
  18. kjempff

    kjempff

    Joined:
    Aug 14, 2013
    Posts:
    3
    I am researching whether Unity would be a viable option for WebGL on mobile right now.
    Since we have had a lot of problems with running out of memory with our current technology, I would like to share some of the information I have gathering during months of testing and optimization of our games and similar games for WebGL.

    1. All the libraries and game engines out there use a lot of memory. Generally libraries like Three.js (which we currently use), pixi, or playcanvas use roughly the same amount of memory - Measured by doing use-cases and also measuring similar games from other companies (to see if they had some secret solution we didn't know about.. they didn't). First use-case build in Unity uses double the amount of memory that the other libraries or playcanvas does, however I am still in the process of looking for optimizations for Unity so that might not be the final result.

    2. Memory problems are only really present on IOS. Same games on Android use a fraction of the memory, and pc doesn't matter, so we are only focusing on getting IOS games to not crash (aka out of memory = browser refreshing page).
    It is always nice to bash Apple, but it is completely warranted in this case, with games literally taking 4x-10x the memory on IOS compared to Android or pc. And before the fanbois start claiming that is not true ... this is from extensive research and a lot of testing of theories and optimization work, so I doubt you have a magic solution.

    3. Canvas resolution (IOS) is a huge memory eater. Going from a canvas resolution of full hd 1920x1080 to 800x480 (which was the lowest we could go quality wise) saved up to 250mb (on device with pixel ratio of 3, in our case iPhone 12). Btw canvas resolution is the width/height of the canvas element, while you can set the actual size of the canvas by css styles .. which means we run for example 800x400 resolution while displaying for example 1920x1080.

    4. The only semi reliable way I have found to measure memory on IOS is by:
    - Using safari dev tools on a Mac to monitor (Timeline tab), then adding page+js memory together.
    Steps...
    1. ALWAYS! go to about:blank
    2. KILL! (swipe out) the browser (do not re-use the same browser process!! this can not be stressed enough)
    3. clear website data in Safari settings
    4. re-open browser and directly open our game while having the timeline tab monitoring on the Mac.

    Also only have only one tab open.
    If you don't do all of this, your measurements will be all over the place because a page reload will not free up memory (until it is needed).



    So after this wall of text, I hope maybe someone can use this information for something so you don't have to spend months like we did on trying to figure out what the heck is going on with our IOS webgl games.
    Now, back to trying to see if I can tweak Unity settings to get its memory usage down to acceptable levels - And if I can, next step is to consider whether we dare to ignore the "WebGL is not supported on mobile" ... because there are huge advantages to using Unity over maintaining our own proprietary editor and engine solution, but Unity HAS to perform memory wise otherwise it is a no-go.
     
    KamilCSPS and De-Panther like this.
  19. OceanX000

    OceanX000

    Joined:
    Feb 24, 2021
    Posts:
    120
    You can use Xcode-Instrument to get memory of WebContent Process. That's the footprint memory limitation causing your game crashed. On 2G ram devices(such as IPhone6s/7), safari crashed If it exceed 1024MB. On >2G ram devices(such as iPhoneX, 8P), safari crashed if exceed 1.4G more or less.
     
    MisfitXXX likes this.
  20. OceanX000

    OceanX000

    Joined:
    Feb 24, 2021
    Posts:
    120
    The memory of the entire process is roughly composed of the following parts: WebAssembly compilation, cavas, WebAssembly.Memory, GPU, Audio.
    - Compilation memory:
    The biggest difference between Unity and other engines is that it uses WebAssembly, and the memory compiled on iOS is very large. We have tested that every 10MB of uncompressed code will generate 100MB of memory.
    - cavas: related to resolution and usage characteristics (such as antialiasing)
    - WebAssembly.Memory: Unity native+mono heap, Be especially careful with the size of the AssetBundle, as instantaneous loading of assets in large bundles (such as those containing uncompressed textures) can cause memory spikes.
    - GPU: Much better if you use compressed textures, otherwise there will be a 3~4x difference
    - Audio: Uncompressed long audio will cause a lot of memory consumption

    Through some optimizatios, we can also run more complex games on low-memory mobile phones (such as iPhone7)
     
    MisfitXXX and De-Panther like this.
  21. K_Kuriyama

    K_Kuriyama

    Joined:
    Jul 4, 2020
    Posts:
    65
    Last edited: Nov 9, 2022