Unreal game memory optimization on Android

Memory usage is critical in Android game development. When developing games using Unreal Engine, you must continuously analyze and track the memory state to satisfy these memory constraints, preventing memory leaks and excessive memory allocations.

Memory savings through shader permutation reduction

By removing unused options in Lighting or Mobile Shader Permutation Reduction, you can minimize the number of shader permutations. This significantly reduces the app packaging size, lowers the number of pipeline state objects (PSOs), and prevents the creation of unused render targets or dummy textures, leading to reduced runtime memory and better performance.

For more information, see the Misc Lighting and Shader Permutation Reduction Settings for Mobile section in Performance Guidelines for Mobile Devices.

Asset loading optimization

When loading assets using hard references, connected content that isn't even used in the game is loaded into memory simultaneously, causing unnecessary memory consumption. In particular, when inheritance is misused in Blueprints, unnecessary CDOs (Class Default Objects) from parents are allocated, wasting a significant amount of memory. In such cases, apply the following techniques to optimize memory usage.

  • C++ code definition and data separation: Define core functionalities using UCLASS(Abstract) in C++ code, separating them from actual data.
  • Minimize inheritance: Perform data setup and asset loading only in leaf-node Blueprints.
  • Use indirect references: Apply indirect property references using TSoftObjectPtr to avoid hard references, and asynchronously load assets strictly when needed at runtime.
  • Precautions: When using Async Asset Loading, ensure that validity checks (IsValid) are performed during the loading completion callback to prevent dangling pointers or crashes.

For more information, see Referencing Assets and Building Mobile Games with UE5.

Smart pointers and garbage collection (GC) tuning

Using smart pointers (such as TSharedPtr and TWeakPtr) during C++ development prevents dangling pointers and memory leaks. Furthermore, using TWeakPtr avoids creating strong references to objects, so the garbage collector doesn't increment reference counts when collecting objects. This prevents unnecessary object survival and lowers the complexity of the reference graph that GC needs to traverse, significantly reducing GC overhead. Garbage collection can be effectively managed in mobile environments by triggering ForceGarbageCollection during level transitions or periods of inactivity, such as when there's no user interaction. Additionally, it can be triggered when the game is in a perceptible state or backgrounded (for example, onStop callbacks), ensuring that garbage collection is performed without affecting the active gameplay experience.

For more information, see Garbage Collection and Smart Pointer.

Optimization using relocation table compression

Relocation Table Compression is an optimization method that compresses the Relocation Table size in ELF binaries to reduce Android binary size. When the Android minSDKVersion is 23 or higher, APS Relocation Table Compression is supported; when minSDKVersion is 28 or higher, RELR Relocation Table Compression is additionally supported, significantly reducing the size of the .so file. This not only decreases download size, but also reduces the memory footprint of the .so mmap at runtime, mitigating overall physical memory pressure.

AndroidToolChain.cs

if (MinSDKVersion >= 28)
{
  Result += " -Wl,--pack-dyn-relocs=android+relr,--use-android-relr-tags";
}
else if (MinSDKVersion >= 23)
{
  Result += " -Wl,--pack-dyn-relocs=android";
}

To apply this optimization, verify that Enable compression of relocation tables in Project Settings or the bEnableAdvancedBinaryCompression variable is enabled.

To check whether RELR compression is properly applied to the binary, use the llvm-readelf tool from the Android NDK.

llvm-readelf -d libUnreal.so | grep RELR

In practice, building Unreal Engine's ThirdPerson template and measuring using dumpsys meminfo confirms a reduction in PSS and RSS usage for .so mmap.

Category PSS Total Private Dirty Private Clean Swap Dirty RSS Total
.so mmap Before compression 170,210 24,360 141,564 0 242,204
.so mmap After compression 138,031 24,748 108,976 0 210,140

Texture compression

Use appropriate texture compression to reduce the memory footprint of overall graphics resources. Using Adaptive Scalable Texture Compression (ASTC), the mobile standard, provides superior visual quality compared to ETC2 at the same size, or achieves higher compression ratios with lower memory consumption under the same visual quality target.

Texture streaming optimization

In mobile environments, using the default Unreal Engine texture streaming pool size directly can pre-allocate an excessively large memory pool relative to device specifications. Therefore, it's ideal to define mobile Device Profiles and select an appropriate texture streaming pool size according to device memory limits. Additionally, use the Editor's Statistic feature to check texture stats and identify high-memory-consuming textures occupying extreme resolutions at runtime. Pair this with the Required Texture Resolution feature to enforce limits or reduce maximum resolutions on demanding textures, keeping resource usage controlled within the allocated texture memory buffer.