Memory Expansion: Utilize new memory features of IOS in UE

内存扩展:UE中利用IOS新的内存特性

Memory optimization is a frequent focus in game development. To avoid excessive memory allocation that triggers an OOM (Out of Memory) to be forcibly terminated by the system, common optimization methods typically start from the usage perspective, enhancing memory utilization efficiency by trimming unnecessary features and controlling resource loading.

However, there is another approach to increase the threshold for triggering OOM, allowing the system to permit our app to allocate more memory. In the new iOS versions, Apple has introduced new memory features for apps, allowing them to extend addressable space and increase allocatable memory.

In this article, I will explore how to leverage these features in UE to enhance the total allocatable memory for games on the iOS platform.

All systems in the Apple ecosystem provide a mechanism called Entitlements. Unlike permission grants, it can grant a specified app the ability to access certain privileges, such as HomeKit, maps, iCloud, etc. The rights are selectively granted based on the specific application needs to avoid misuse.

When registering the App ID in the Apple Developer portal and creating Identifiers, users will be prompted to select the capabilities their app will use.

Forward

Allowing Apps to Allocate More Memory

In iOS 15.0+ and iPadOS 15.0+, Apple has opened up a capability for apps that allows them to use more memory, increasing the threshold for memory allocation that triggers OOM.

Supported Versions: iOS 15.0+, iPadOS 15.0+
Entitlement Key: com.apple.developer.kernel.increased-memory-limit

Official introduction from Apple:

Add this entitlement to your app to notify the system that certain core features of your app may perform better due to exceeding the default memory limits for apps on supported devices. If you use this entitlement, ensure that your app can still function properly without additional memory.

However, Apple hasn’t specified how much the allocatable memory can be increased on specific devices, and later I will conduct some memory allocation tests in UE with the devices I have.

Extending Virtual Memory Address

Starting from iOS 11, Apple has mandated that all apps listed on the App Store must support 64-bit; 32-bit applications are no longer supported.

The benefits of 64-bit apps are obvious: larger pointer range and greater usable virtual memory, which can also surpass the 4G memory limit of 32-bit. Most currently popular devices should have upgraded to iOS 11+, so fully utilizing the performance of 64-bit is essential.

In iOS 14.0+ and iPadOS 14.0+, Apple has opened a capability for apps that allows them to extend virtual memory address space: Extended Virtual Addressing Entitlement.

Official introduction from Apple:

If your application has specific needs for larger addressable space, use this entitlement. For instance, games that memory-map assets for streaming to the GPU may benefit from a larger address space. Enable this entitlement using the “Extended Virtual Addressing” feature in the Xcode project editor.

Once enabled, the kernel will activate jummbo mode, which provides the process with full access to 64-bit address space.

Supported Versions: iOS 14.0+, iPadOS 14.0+, tvOS 14.0+
Entitlement Key: com.apple.developer.kernel.extended-virtual-addressing

An expert abroad has conducted an analysis of iOS virtual memory expansion: Size Matters: An Exploration of Virtual Memory on iOS

References

Implementation

Enabling Capabilities

First, you need to enable the corresponding Capabilities in the Apple Developer page:

After adding, download the new MobileProvision, which can be imported into UE.

If it’s a native XCode project, you can add Entitlements in Xcode:

These actions will create a *.entitlements file in the same directory as *.xcodeproj*, documenting the corresponding states:

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>com.apple.developer.kernel.extended-virtual-addressing</key>
        <true/>
</dict>
</plist>

Note: The Mobile Provision for the app must have the corresponding capabilities enabled; otherwise, it will fail during signing.

Checking MobileProvision

Mobile Provision is a device description file for iOS development, used to document certificate information, device UUID list, Bundle Identifier, and more.

After enabling large memory support in the App ID, before packaging using MobileProvision, one needs to check the Entitlements supported in the Mobile Provision to ensure it contains the two required keys:

1
2
com.apple.developer.kernel.increased-memory-limit
com.apple.developer.kernel.extended-virtual-addressing

The *.mobileprovision* file is in binary format and cannot be opened with a text editor. However, you can use security on a Mac to view it:

1
security cms -D -i imzlp.mobileprovision

This command will output the specific information of the MobileProvision, checking if it contains the following two keys and whether their values are true:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Entitlements</key>
<dict>
<key>com.apple.developer.kernel.increased-memory-limit</key>
<true/>
<key>com.apple.developer.kernel.extended-virtual-addressing</key>
<true/>
</dict>
</dict>
</plist>%

Entitlements in UE

In UE, during packaging, entitlements will be generated and saved in Intermediate-IOS-*.entitlements:

*.entitlements
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>get-task-allow</key>
<true/>
</dict>
</plist>

It is generated in UBT, and the specific code is: Programs/UnrealBuildTool/Platform/IOS/IOSExports.cs
There is a WriteEntitlements function that parses the MobileProvision file to check if it has the specified Identifier.

By default, the engine does not provide an easy way to append elements, only allowing modifications to UBT implementation.

Enabling Large Memory Support in UE

As mentioned earlier, there is no easy way to append elements in UE; thus, it requires modifying the UBT code implementation to extend:

Add the following code to WriteEntitlements:

IOSExports.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

//++[lipengzha] support memory-limit
Action<string,string> SupportMemoryOpt = (BoolName,IdStr) =>
{
bool bBoolNameValue = false;
// read property in DefaultEngine.ini
PlatformGameConfig.GetBool("/Script/IOSRuntimeSettings.IOSRuntimeSettings", BoolName, out bBoolNameValue);
Console.WriteLine(string.Format("Write {0}({1}) {2} entitlements",BoolName, bBoolNameValue?"true":"false" ,IdStr));

if (bBoolNameValue)
{
Text.AppendLine(string.Format("\t<key>{0}</key>",IdStr));
Text.AppendLine(string.Format("\t<{0}/>", bBoolNameValue ? "true" : "false"));
}
};
SupportMemoryOpt("bIncreasedMemoryLimit","com.apple.developer.kernel.increased-memory-limit");
SupportMemoryOpt("bExtendedVirtualAddressing","com.apple.developer.kernel.extended-virtual-addressing");
//--[lipengzha]

Then, in the project’s DefaultEngine.ini, add the following items under [/Script/IOSRuntimeSettings.IOSRuntimeSettings]:

DefaultEngine.ini
1
2
bIncreasedMemoryLimit=True
bExtendedVirtualAddressing=True

By doing this, you can control whether the memory extension capability is enabled in the project configuration.

During packaging, there will be a corresponding log:

The logic in IOSExport.cs relies on UBT execution, so if the entitlement is modified, the code must also change (as long as UBT can start compiling), otherwise, the following error may occur:

1
PackagingResults: Error: Entitlements file "MemoryProfiler.entitlements" was modified during the build, which is not supported. You can disable this error by setting 'CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION' to 'YES', however this may cause the built product's code signature or provisioning profile to contain incorrect entitlements. (in target 'MemoryProfiler' from project 'MemoryProfiler')

You can also try deleting the Intermediate/IOS directory and retry:

In UE5.1 and later versions, the engine can control whether to allow modification of entitlement values during the build process by setting the bUseModernXcode value in DefaultEngine.ini under [XcodeConfiguration], avoiding this error. The engine code: IOSExports.cs#L301.
In UE5.2, the engine’s IOSToolChain.cs has set CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION to YES by default: IOSToolChain.cs#L1807

Directly packaging on Mac and using remote builds result in different logic when generating the IPA. If packaging directly on Mac, you also need to add the definition of CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION in the AppendProjectBuildConfiguration function in IOSExport.cs, which will appear in the BuildSettings - User-Defined of the Xcode project:

UnrealBuildTool/ProjectFiles/Xcode/XcodeProject.cs
1
2
3
4
5
6
7
private void AppendProjectBuildConfiguration(StringBuilder Content, string ConfigName, string ConfigGuid)
{
// ...
//++[lipengzha] support xcode14+
Content.Append("\t\t\t\tCODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES;");
//--[lipengzha]
}

Test Data

Test benchmarks: iPhone12, iOS 16.1; iPad Pro (3rd generation), iPadOS 16.2; iPhoneXs, iOS 14.7

Test project environment: UE4.27.2, empty C++ project without any extra resources.
Testing method:

  1. Default implementation packaging, running the game, allocating memory until OOM.
  2. Supporting Memory-Limit, running the game, allocating memory until OOM.

Both projects and code are identical, with the only difference being Memory-Limit support.

The method for memory allocation during runtime: allocate 1M each time until the system OOM is triggered, and record the allocated memory size.

Original

iPhone12 Original, memory after game startup:

1
2
LogMemoryUsageProfiler: Display: Constants: TotalPhysical 2099.20 MB, TotalVirtual 2099.20 MB PageSize 16384 byte, OsAllocationGranularity 16384, Constants 65536 BinnedAllocationGranularity 0, AddressLimit 100000000
LogMemoryUsageProfiler: Display: Stats: Mem Used 217.73 MB, Texture Memory 8.60 MB, Render Target memory 0.07 MB, OS Free 1881.47 MB

Memory before OOM:

1
2
3
LogMemoryUsageProfiler: Display: AllocSystemMemory: 1 M, Alloced 1889 (M)
LogMemoryUsageProfiler: Display: Constants: TotalPhysical 2099.20 MB, TotalVirtual 2099.20 MB PageSize 16384 byte, OsAllocationGranularity 16384, Constants 65536 BinnedAllocationGranularity 0, AddressLimit 100000000
LogMemoryUsageProfiler: Display: Stats: Mem Used 2098.19 MB, Texture Memory 8.60 MB, Render Target memory 0.07 MB, OS Free 1.01 MB

At 410M before OOM, the app triggered the system memory warning:

1
2
3
4
5
LogMemory: Platform Memory Stats for IOS
LogMemory: Process Physical Memory: 1688.24 MB used, 1688.24 MB peak
LogMemory: Process Virtual Memory: 400957.09 MB used, 400957.09 MB peak
LogMemory: Physical Memory: 1688.24 MB used, 410.96 MB free, 2099.20 MB total
LogMemory: Virtual Memory: 2099.20 MB used, 0.00 MB free, 2099.20 MB total

Memory-limit

iPhone12 Memory-limit version, memory after game startup:

1
2
LogMemoryUsageProfiler: Display: Constants: TotalPhysical 2867.20 MB, TotalVirtual 2867.20 MB PageSize 16384 byte, OsAllocationGranularity 16384, Constants 65536 BinnedAllocationGranularity 0, AddressLimit 100000000
LogMemoryUsageProfiler: Display: Stats: Mem Used 735.51 MB, Texture Memory 8.60 MB, Render Target memory 0.07 MB, OS Free 2131.69 MB

Memory before OOM:

1
2
3
LogMemoryUsageProfiler: Display: AllocSystemMemory: 1 M, Alloced 2139 (M)
LogMemoryUsageProfiler: Display: Constants: TotalPhysical 2867.20 MB, TotalVirtual 2867.20 MB PageSize 16384 byte, OsAllocationGranularity 16384, Constants 65536 BinnedAllocationGranularity 0, AddressLimit 100000000
LogMemoryUsageProfiler: Display: Stats: Mem Used 2866.86 MB, Texture Memory 8.60 MB, Render Target memory 0.07 MB, OS Free 0.34 MB

At about 460M before OOM, the app triggered the system memory warning:

1
2
3
4
5
LogMemory: Platform Memory Stats for IOS
LogMemory: Process Physical Memory: 2407.14 MB used, 2407.14 MB peak
LogMemory: Process Virtual Memory: 401157.25 MB used, 401157.25 MB peak
LogMemory: Physical Memory: 2407.14 MB used, 460.06 MB free, 2867.20 MB total
LogMemory: Virtual Memory: 2867.20 MB used, 0.00 MB free, 2867.20 MB total

Summary

Comparison of the two versions with the Memory-Limit mechanism enabled:

Original Memory-Limit
iPhone12(iOS16.1) 1889 2139
iPadPro 3rd(2021, iPadOS 16.2) 4864 7927
iPhoneXs(iOS 14.7) 1892 2144

On the iPhone12, enabling it increased the allocatable memory by 250M.

On the iPad Pro (3rd generation, 2021), it increased by an astonishing 3063M!

Enabling this feature shows significant improvements on high-end devices. Without requiring any additional memory usage optimizations, this effectively reclaimed a large portion of physical memory from the system—what a wonderful benefit.

Supplement

The kernel used by iOS is also based on XNU, as is the Unix-based kernel used by macOS.

The two rights introduced in this article, virtual memory extension and increased app memory limits, are supported in the xnu-7195.50.7.100.1 kernel version. Therefore, any iOS version based on that kernel can upgrade.

You can refer to the table of iOS versions and their corresponding XNU kernel versions: Kernel - The iPhone Wiki

From iOS 14.3 beta onward, the XNU kernel version was upgraded to xnu-7195.60.63~22, practically providing the kernel capabilities needed for memory extension.

Actual tests have shown that even on the iPhone11 (iOS 14.7), there are similar enhancements:

Original Memory-Limit
iPhoneXs(iOS 14.7) 1892 2144

Update

2023.08.14

In the UE5.3 Roadmap updates, support for extended-virtual-address on iOS has been made, allowing the use of mallocBinned2 allocator when enabled.

So theoretically, support for Binned2 can also be expanded to UE4; I’ll explore that when I have time.

The article is finished. If you have any questions, please comment and communicate.

Scan the QR code on WeChat and follow me.

Title:Memory Expansion: Utilize new memory features of IOS in UE
Author:LIPENGZHA
Publish Date:2023/01/31 10:37
Word Count:8.2k Words
Link:https://en.imzlp.com/posts/56381/
License: CC BY-NC-SA 4.0
Reprinting of the full article is prohibited.
Your donation will encourage me to keep creating!