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
- Extended Virtual Addressing Entitlement
- increased-memory-limit
- Size Matters: An Exploration of Virtual Memory on iOS
- One-click release of iOS 64-bit app potential
- How to increase iOS APP virtual address space and memory limits? XNU kernel source code interpretation
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 |
|
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 | com.apple.developer.kernel.increased-memory-limit |
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 |
|
Entitlements in UE
In UE, during packaging, entitlements will be generated and saved in Intermediate
-IOS
-*.entitlements
:
1 |
|
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
:
1 |
|
Then, in the project’s DefaultEngine.ini
, add the following items under [/Script/IOSRuntimeSettings.IOSRuntimeSettings]
:
1 | bIncreasedMemoryLimit=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 inDefaultEngine.ini
under[XcodeConfiguration]
, avoiding this error. The engine code: IOSExports.cs#L301.
In UE5.2, the engine’s IOSToolChain.cs has setCODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION
toYES
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:
1 | private void AppendProjectBuildConfiguration(StringBuilder Content, string ConfigName, string ConfigGuid) |
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:
- Default implementation packaging, running the game, allocating memory until OOM.
- 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 | LogMemoryUsageProfiler: Display: Constants: TotalPhysical 2099.20 MB, TotalVirtual 2099.20 MB PageSize 16384 byte, OsAllocationGranularity 16384, Constants 65536 BinnedAllocationGranularity 0, AddressLimit 100000000 |
Memory before OOM:
1 | LogMemoryUsageProfiler: Display: AllocSystemMemory: 1 M, Alloced 1889 (M) |
At 410M before OOM, the app triggered the system memory warning:
1 | LogMemory: Platform Memory Stats for IOS |
Memory-limit
iPhone12 Memory-limit version, memory after game startup:
1 | LogMemoryUsageProfiler: Display: Constants: TotalPhysical 2867.20 MB, TotalVirtual 2867.20 MB PageSize 16384 byte, OsAllocationGranularity 16384, Constants 65536 BinnedAllocationGranularity 0, AddressLimit 100000000 |
Memory before OOM:
1 | LogMemoryUsageProfiler: Display: AllocSystemMemory: 1 M, Alloced 2139 (M) |
At about 460M before OOM, the app triggered the system memory warning:
1 | LogMemory: Platform Memory Stats for IOS |
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.