莫度编程网

技术文章干货、编程学习教程与开发工具分享

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

内存优化是游戏开发中经常关注的课题,为了避免App过度分配内存触发OOM被系统强杀,通常的优化手段是从使用层面入手,提升内存的利用效率,裁剪不需要的功能、控制加载的资源等等。

但还有一种情况,提升App触发OOM的阈值,让系统允许我们的App分配更多的内存。在新的IOS版本中,苹果为App引入了新的内存特性,可以允许App扩展寻址空间和提高可分配内存。

本篇文章,我将研究如何把这些特性在UE内利用起来,提高游戏在IOS平台的可分配内存总量。

Apple系列的系统平台中,都提供了一种Entitlements机制,和权限授权不同,它可以授予指定App支持访问特权的能力,如访问HomeKit、地图、iCloud等等,根据具体应用的需要选择性授予,避免能力的滥用。

在Apple Developer中注册App ID时,在创建Identidiers时,会让用户选择App要使用的能力。

Forward

允许App分配更多内存

在IOS15.0+、iPadOS 15.0+的系统版本中,Apple为App开放了一种能力,允许App使用更多的内存,提高App内存分配触发OOM的阈值。

支持版本:iOS 15.0+、iPadOS 15.0+ Entitlement Keycom.apple.developer.kernel.increased-memory-limit

oincreased-memory-limit[1]

苹果官方的介绍:

将此权利添加到您的应用程序,以通知系统您的应用程序的某些核心功能可能会因超出受支持设备上的默认应用程序内存限制而表现更好。如果您使用此权利,请确保您的应用程序在额外内存不可用时仍能正常运行。

但苹果没说具体的设备上可分配内存数量能提升多少,后面我会用手头的设备在UE中做一些内存分配测试。

扩展虚拟内存地址

从IOS11开始,Apple就强制要求,、所有上架App Store的App都必须支持64位,32位应用不再支持。

64位应用的好处显而易见:指针范围更大,可使用的虚拟内存也更大,也能超越32位的4G内存限制。在目前流行的设备中,应该绝大部分设备都升级到了IOS11+,所以充分利用64位的性能是有必有的。

在IOS14.0+、iPadOS 14.0+的系统版本中,Apple为App开放了一种能力,允许App扩展虚拟内存地址:Extended Virtual Addressing Entitlement[2]

苹果官方的介绍:

如果您的应用有需要更大可寻址空间的特定需求,请使用此权利。例如,内存映射资产以流式传输到 GPU 的游戏可能受益于更大的地址空间。使用 Xcode 项目编辑器中的“扩展虚拟寻址”功能启用此权利。

启用之后,内核将启用jummbo mode[3],该模式为进程提供完整的64位地址空间访问权限。

支持版本:iOS 14.0+、iPadOS 14.0+、tvOS 14.0+ Entitlement Keycom.apple.developer.kernel.extended-virtual-addressing

国外有位大佬做了IOS虚拟内存扩展的分析:Size Matters: An Exploration of Virtual Memory on iOS[4]

参考资料

oExtended Virtual Addressing Entitlement[5]oincreased-memory-limit[6]oSize Matters: An Exploration of Virtual Memory on iOS[7]o一键释放iOS 64位App潜力[8]o如何增加 iOS APP 虚拟地址空间及内存上限?XNU 内核源码解读[9]

实现方式

Capabilities启用

首先要在Apple Developer的页面里开启对应的Capabilities

oCertificates, Identifiers & Profiles[10]

添加之后下载新的MobileProvision,可以在UE中导入它。

如果是原生XCode工程,可以在Xcode中添加Entitlements:

以上操作,会在*.xcodeproj*的同级目录创建一个*.entitlements文件,记录着对应的状态:

<?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>

注意,App的Mobile Provision一定要启用对应的能力,不然会在签名时失败。

检查MobileProvision

Mobile Provision是IOS开发的设备描述文件,用于记录证书信息、设备UUID列表、Bundle Identifier等等。

当在App ID中开启大内存支持后,使用MobileProvision打包之前,需要对Mobile Provision中支持的Entitlements进行检查,确保其包含我们需要的两个Key:

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

*.mobileprovision*文件是二进制格式,不能直接使用文本方式打开。但可以在mac上使用security来查看:

 security cms -D -i imzlp.mobileprovision

会输出该MobileProvision的具体信息,检查其中是否包含以下两个Key,且值是否位true

<?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>%

UE中的Entitlements

UE中,在打包时会生成entitlements,会保存在Intermediate-IOS-*.entitlements

<?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>

它是在UBT中生成的,具体代码为:Programs/UnrealBuildTool/Platform/IOS/IOSExport.cs[11] 其中有一个WriteEntitlements函数,解析MobileProvision文件,检测里面是否有指定的Identifier。

默认情况下引擎里没有提供方便的追加元素的方式,只能修改UBT实现。

在UE中开启大内存支持

前面提到了UE中,UE中默认没有可以方便追加元素的方式,只能修改UBT的代码实现扩展:

WriteEntitlements中加入以下代码:



//++[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]

然后在项目的DefaultEngine.ini中的[/Script/IOSRuntimeSettings.IOSRuntimeSettings]添加以下项:

bIncreasedMemoryLimit=True
bExtendedVirtualAddressing=True

就可以通过控制项目中的配置,来决定是否启用内存扩展的能力。

在打包时就会有以下的Lod:

IOSExport.cs中的逻辑依托于UBT执行,所以如果修改了entitlement,也要让代码有变动(只要UBT能启动编译就可以),不然会出现以下错误:

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')

也可以删除Intermediate/IOS目录后重试:

测试数据

测试基准:iPhone12,IOS 16.1、iPad Pro 3rd,IOS 16.2

测试工程环境:UE4.27.2,空C++工程,不包含任何额外资源。测试方式:

1.默认实现打包,运行游戏、分配内存直到OOM。2.支持Memory-Limit,运行游戏、分配内存直到OOM。

两者工程和代码均一致,仅有Memory-Limit的支持区别。

运行时的内存分配方式:每次分配1M,直到触发系统OOM,统计分配的内存大小。

Original

iPhone12 Original,游戏启动后的内存:

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

OOM前的内存:

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

在距离OOM还有410M时,App触发了系统的内存警告:

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版本,游戏启动后的内存:

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

OOM前的内存:

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

在距离OOM线约460M时,App触发了系统的内存警告:

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

总结

启用Memory-Limit机制两个版本对比数据:


Original

Memory-Limit

iPhone12

1889

2139

iPadPro 3rd(2021)

4864

7927

在iPhone12上,开启之后增加了250M可分配内存。

而在iPad Pro 3rd(2021)上,则增加了惊人的3063M!

启用该特性,在高端设备上提升最为明显。不需要做额外的内存使用优化,就从系统手中捡了一大块物理内存,岂不美哉。

References

[1] increased-memory-limit: https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_kernel_increased-memory-limit
[2] Extended Virtual Addressing Entitlement:
https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_kernel_extended-virtual-addressing
[3] jummbo mode:
https://github.com/apple-oss-distributions/xnu/blob/xnu-7195.141.2//bsd/kern/kern_exec.c#L2930-L2932
[4] Size Matters: An Exploration of Virtual Memory on iOS:
https://alwaysprocessing.blog/2022/02/20/size-matters
[5] Extended Virtual Addressing Entitlement:
https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_kernel_extended-virtual-addressing
[6] increased-memory-limit:
https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_kernel_increased-memory-limit
[7] Size Matters: An Exploration of Virtual Memory on iOS:
https://alwaysprocessing.blog/2022/02/20/size-matters
[8] 一键释放iOS 64位App潜力:
https://www.toutiao.com/article/7116717500678570507?wid=1674961795942
[9] 如何增加 iOS APP 虚拟地址空间及内存上限?XNU 内核源码解读:
https://redian.news/wxnews/225476
[10] Certificates, Identifiers & Profiles:
https://developer.apple.com/account/resources/identifiers/list
[11] Programs/UnrealBuildTool/Platform/IOS/IOSExport.cs:
https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Programs/UnrealBuildTool/Platform/IOS/IOSExports.cs#L363

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言

    Powered By Z-BlogPHP 1.7.4

    蜀ICP备2024111239号-43