Skip to content

Optimize Memory ​

1. Memory and OOM ​

Unity WebGL games usually use more memory than ordinary H5 (JS) games, and under the operating system's control policies they are very easily killed by OOM when the threshold is exceeded.

To improve game stability on low- and mid-end devices, memory optimization is extremely important. So, how much memory is reasonable? We recommend keeping the peak memory within the following ranges (performance evaluation standard):

Android: low-end devices < 1.2G, mid- and high-end devices < 1.5G iOS: low-end devices < 1G, mid- and high-end devices < 1.4G Relatively speaking, Android devices have more relaxed memory limits.

iOS low-end devices are mainly RAM 2G models such as iPhone 6sp/7/8, while mid- and high-end devices are models with RAM 3G or above, such as iPhone 7P/8P/iPhoneX/iPhone11.

2. Memory Structure of Unity WebGL Adapted to Minigames ​

Unity WebGL is an application based on WebAssembly + WebGL technology and runs in a browser environment, so game memory allocation is entirely managed within this environment.

After adaptation to the minigame, the minigame process becomes the "container." Although it is no longer a standard browser, the memory composition is basically the same.

Base library + Canvas: There is no DOM in the minigame environment, but some basic consumption still exists, such as the minigame public library and the Canvas. Typically, the minigame public library uses about 100~150MB, and the Canvas is related to the device's physical resolution—for example, an iPhone 11 Promax uses about 80MB.

Unity Heap: managed heap, native heap, and the low-level memory of native plugins. For example, managed memory such as C# objects allocated by game logic, native memory such as AssetBundles and scene structures managed by Unity, and malloc allocations made by third-party native plugins (such as Lua).

WASM compilation: memory produced by code compilation and runtime instruction optimization. On Android v8 and iOS JavascriptCore, a large amount of memory is also needed for JIT optimization.

GPU memory: video memory occupied after textures or models are uploaded to the GPU. Since Unity did not support compressed textures before 2021, texture memory causes noticeable bloat.

Audio: memory occupied when playing audio after Unity passes the audio to the container (browser or minigame). Currently UnityAudio will automatically adapt to minigames; in particular, please avoid using FMOD to play long audio.

Emscripten uses the file system to simulate Linux/POSIX interfaces, at the cost of occupying memory equal to the file size. Please do not use the first resource package, the Addressable Cache mechanism, <WWW.LoadFromCacheOrDownload>, or other Cache APIs.*** Temporary JS memory on the browser side caused by network requests, and garbage collection

3. Memory Inspection Tools ​

Process level: Perfdog, Android Studio, Mac Xcode Instrument

UnityHeap (CPU main memory): performance panel, ProfilingMemory, JavaScript Heap

Engine and resources: UnityProfiler

4. Memory Optimization Solutions ​

Calculation formula: minigame base library + Canvas + compilation memory + UnityHeap + Gfx video memory + audio + JavaScript memory. UnityHeap = max(managed/Mono memory) + max(Native/Reserved memory + C native code memory)

Taking iOS High Performance mode as an example, for a game whose code (export directory/webgl/Build/xxx.code.unityweb or code.wasm) is 30MB, the memory used is: minigame base library (130MB) + Canvas (70MB) + compilation memory (300MB) + UnityHeap + Gfx video memory + audio + JavaScript (usually <100MB).

If the game needs to support low-end devices and keep memory within 1G, the business side (UnityHeap, Gfx video memory, audio, JavaScript) needs to be controlled to around 500MB. Here we present the memory problems most commonly encountered when converting games, along with solutions. If developers encounter memory problems, please investigate and optimize them one by one.

4.1 WASM Code Compilation Memory ​

Cause: Unity WebGL compiles all code (engine, business code, third-party plugins) into cross-platform WebAssembly binary code, which needs to be compiled and executed at runtime. Compilation uses a very large amount of memory (for example, on iOS, 30MB of uncompressed code requires 300MB of runtime compilation memory).

Solutions:

  • Using a code sub-packaging tool can reduce the original compiled code memory by more than 50%.
  • Manually delete redundant plugins and reduce unnecessary Unity module imports (such as physics, Unity data analytics, etc.)

4.2 GPU Memory ​

Cause: Unity only began supporting compressed textures on mobile platforms in 2021. Using texture formats such as RGBA and DXT will cause huge memory overhead and runtime decompression consumption.

Solutions:

  • Texture compression optimization can minimize memory and decompression overhead.
  • Upgrade the engine to 2021 and use ASTC compressed textures.
  • Turn off HDR. For the standard render pipeline, uncheck "Use HDR" under "GraphicsSetting-tier2" (WebGL uses tier2); for the URP pipeline, disable
  • it via the renderer configuration.
  • Using High Performance+ mode will significantly reduce GPU memory.

4.3 UnityHeap ​

Cause: UnityHeap is used to store all state, managed objects, and native objects. It often causes an instantaneous memory peak due to an overly large scene or for business reasons. Since Unity WebGL cannot GC within a single first frame, instantaneous memory usage within a single frame can very easily cause a crash. In addition, the Heap only grows and never shrinks, and memory fragmentation exists.

Solutions:

  • Set a reasonable "UnityHeap reserved memory" in the conversion panel, and be careful never to exceed it in use.
  • Avoid overly large scenes that cause instantaneous peaks.
  • Avoid overly large AssetBundles that cause instantaneous peaks.
  • Avoid allocating too many objects within a single frame; be careful not to produce spike peaks.

How to set "UnityHeap reserved memory"? This value only indicates the reservation for the UnityHeap peak, avoiding the spikes caused by expansion when memory is insufficient;

  • Check "Show performance panel" in the export panel, or turn on the performance panel via the enableProfileStats variable in unity-namespace.js.

  • Run the game for a while and observe the peak of DynamicMemory.

  • UnityHeap = DynamicMemory + a small amount of static memory (usually <10MB), so set the "UnityHeap reserved memory" in the conversion panel to be slightly larger than the DynamicMemory peak (you can reserve an extra 50-100MB, subject to the actual game). Recommended values: hyper-casual games 256, mid-core games (simulation/business, card growth) 496, hardcore games (SLG, MMO) 768.

  • UnityHeap should not be too large. When UnityHeap >= 1024MB, most devices will fail to launch; when UnityHeap >= 500MB, launch will very likely fail in iOS normal mode, so it is recommended that light and mid-core games not exceed this value.

4.4 First Resource Package and AssetBundle Memory ​

Cause: The first resource package always occupies memory and cannot be released; the cache mechanisms built into the first resource package and AssetBundle both use the Emscripten file system, and should be avoided.

Solutions:

  • Reduce the size of the first resource package, since this part always occupies memory and cannot be released; use AssetBundle;

  • Load AssetBundles on demand and release them promptly to save memory;

  • AssetBundles are decompressed when used and occupy Unity Native memory, so reduce the AssetBundle size;

  • Avoid using Unity's built-in file cache mechanism; neither the first resource package nor AssetBundles should use file Cache;

Audio Memory ​

Cause: Audio will occupy memory in the minigame environment.

Solutions:

  • Do not use FMOD to play long audio, such as game BGM.

  • Control the number of sound effects; the number of audio playing simultaneously should not exceed 20.

  • Force mono audio whenever possible; stereo produces 2x the memory consumption.