UE4 IO Store
在 UE5 中 PAK 打包时发现了每个 PrimaryAssetLabel 都会生成 3 个文件:.pak,.utoc,.ucas。
这对 Chunkloader 测试造成了影响,在 Mount 的时候将无法正确挂载,从存储服务器下载的 .pak 需要依赖 .utoc 及 .ucas 文件,否则我们需要将 .utoc 及 .ucas 文件上传到 OSS 进行完整下载和校验。
在 “Project Settings>Packaging” 有一个选项 “ Use Io Store” 导致了 .utoc 及 .ucas 文件的生成。
该选项到底有什么作用呢,引擎提示说是 “将所有包放入一个或多个容器文件中”。容器文件是谁,似乎没有解释。但是启用该选项会减少大量加载资产时间。PAK 包也会变的更大。
实际这是 UE4.25.4 推出的新的加载系统中处理高速加载的新特性。 IO Store 仍然是一个实验性功能,所以未来配置和细节可能会发生变化。
【IOStore 概述】
- 从 UE4.25 实现的新加载系统的一部分
- 能够存储 IO 信息
- 提高 IO 性能和加速加载
- IO Store 对启用了功能的打包应用程序有效
Unreal 的加载系统路线中,在 UE4.25 Experimental 版本之前采用了 AsyncLoader 的传统加载方式,之后采用 AsyncLoader2 新的加载方式。
新的加载方式中如果是编辑器行为,默认将不启用 IO Store 编译构建项目,否则默认采用 AsyncLoader2 新的加载方式,启用 IO Store 构建项目。
什么是 AsyncLoader2 呢?在 UE4.26 预览版中有如下描述:
Memory Insights (Beta). For users wanting an insight into memory usage, we are adding Memory Insights to help developers better understand how their work impacts performance and engine behavior.
Garbage Collection. We are improving garbage collector performance by reducing FArchive usage in favor of fast reference collection.
Zen Loader (Beta). Next-generation consoles will have improved load times, a new input/output mechanism that offers a low-overhead interface for data access. The APIs will interface with next-generation storage APIs that will enable data to be read directly into target memory with minimal CPU overhead.
Zen Loader 是新的下一代运行时加载器。作为大幅缩短下一代游戏机加载时间并更好地利用新的更快硬件的计划的一部分,它基于 EDL 加载器,但经过重构以减少开销,并具有新的 IO 机制,为数据访问提供低开销接口。
这是一种新的输入/输出机制,可为数据访问提供低开销接口,改进加载时间。这些 API 将与下一代存储 API 接口,使数据能够以最小的 CPU 开销直接读入目标内存。
那么 IO Store 的作用是怎样呢?
在 UE4 包的创建流程( IOStore = OFF时)Build ->Cook ->Stage ->Package->Archive 中 ,Stage 阶段负责将多个 .uasset 合并成一个文件进行加密和压缩(.pak)。
(IOStore=ON) 时,Stage 阶段负责将多个 .uasset 合并成 .pak .utoc .ucas 三个文件进行加密和压缩,并且提供了 Zen Loader 资产加速。
如何使用 IO Store 进行 Zen Loader 资产加速呢?
创建一个支持 IO Store 的 PAK 包,需要先在 “Project Settings>Packaging”中启用 “IO Store”,然后为项目创建 PAK 包。
有如下 3 种方式,可选择其中任何一种:
- 在项目设置中启用 “Use PakFile” 和 “Use Io Store”
- 在 Project Launcher 项目启动器 Profile 的 Cook 中启用 UnrealPak 和 I/OStore,包括启用 “Store all content in a single file(UnrealPak)” 将所有 Cook 的资源内容放到一个 .pak 文件中,移动平台不管是否勾选该选项都会将资源文件放到 .pak 文件。以及启用 “use container files for optimized loading (i/o store)” 。注 UE5 该方式可忽略。
- 通过 UAT 编译的 “BuildCookRun” 在运行时指定 “-iostore” 和 “-pak”
“IO Store = OFF” 或 “IO Store = ON” 包创建后文件结构的差异?
“IO Store” 关闭时会在构建路径的 Paks 目录中生成 .pak 文件;
“IO Store” 开启时除了 .pak 文件之外,还会在构建路径的 Paks 目录中添加了 .ucas 和 .utoc 文件,以及 global.ucas, global.utoc 文件;
其中 .pak 是打包文件, .ucas 是容器文件, .utoc 是目录文件。
.pak:打包文件
- IOStore = OFF 时存储所有资产文件
- IOStore = ON 时存储不常访问的文件,包括 .ini, .ushaderbytecode, .uproject 等的保存,并将之前保存的资产文件移动到 .ucas
.ucas:文件资产容器,存储资产文件(.uasset、.umap、.ubulk、.uptnl、uexp),作用有以下:
- 资产信息 (FIoBuffer) 中 16Byte 对齐顺序的性能提升
- 通过有效排列资产数据来加速文件访问
.utoc:目录文件,存储目录(TOC:目录)信息,作用有以下:
- 链接 .ucas 和包的目录信息
- 包含标头信息 (FIoStoreTocHeader):Magic, Header Size, Entry Count, Entry Size, Padding, 以及资产信息 (FIoStoreTocEntry): ChunkId, OffsetAndLength。
启用 IO Store 时,会在项目文件结构之外单独添加一个全局的 .ucas / .utoc 文件。
global.ucas:全局容器文件,包含从容器加载包所需的信息。IoChunkType 和 Contents 如下:
- LoaderGlobalMeta 全局元信息表,存储关于加载包的信息。
- LoaderInitialLoadMeta 初始加载元信息表,将包与包关联的信息。
- LoaderGlobalNames 全局名称表,加载时将 bundle 与对象名称关联的名称表。
- LoaderGlobalNameHashes 全局名称散列,加载时将捆绑包与对象名称关联的散列表。
global.utoc:全局目录文件,这只是一个未使用的头信息。
“IO Store” 能优化加载速度到一个什么样的程度,效果如何?
采用官方的 Elemental Demo, Infiltrator Demo, 及 Kite Mode 分别进行 “ IO Store = OFF” 和 “ IO Store = ON” 的测试,测试条件如下:
- 使用样本测量启动级加载时间
- Win64 / 测试构建包,使用 Unreal Insights
示例的验证结果中,启用 "IO Store" 改善加载时间分别为 28%(OFF 时为 1.23,ON 时为 0.88),28%(OFF 时为 2.62,ON 时为 1.88), 5%(OFF 时为 6.30,ON 时为 5.98) 。
【IO Store 和 NewLoader】
传统的 AsyncLoader 的加载流程:
Game Thread 在资产处理中会向 AsyncLoading Thread 异步加载线程请求 Load 资产并等候,
传统的 AsyncLoading Thread 会控制后台负载,向 PoolThread 线程池请求加载资产数据并等候,
Pool Thread 主要负责文件读取控制, 对 .pak 进行查找 & 读取,当 PoolThread 查找并读取到 .pak 数据时,会向 AsyncLoading Thread 发出通知并返回加载资产数据,
AsyncLoading Thread 拿到加载的资产数据通过 Complete 回调给 Game Thread ,Game Thread 异步加载线程接收到返回的数据并通过 Complete 回调通知游戏 .pak 数据加载完成。
使用 AsyncLoader 加载和文件访问:
GameThread 向 AsyncLoading 发出 LoadPackage 加载和文件访问请求,LoadPackage 为一个加载包线程。
AsyncLoading 会创建 QueuePackage 加载包队列(包含多个 AsyncPackage 异步加载包线程),然后加入到 EventQueue 线程事件队列(包含多个 AsyncPackage)中进行等候,同时创建一个 CreateLinker 链接器到 AsyncArchive 异步归档线程进行异步归档,AsyncArchive 会启动线程池中的 PAK 查找和读取的线程进行加载。
AsyncLoader Thread 是一个异步加载线程 ,负责在游戏线程后台加载资产 ;
PoolThread 是一个池线程 ,执行 FileIO API 从 .pak 文件加载资产;
新的 AsyncLoader2 的加载流程:
Game Thread 在资产处理中会向新的 AsyncLoading Thread2 异步加载线程请求 Load 资产并等候,
新的 AsyncLoading Thread2 会控制后台负载,向 IO Service 发出请求文件读取控制,
Pool Thread 主要负责文件读取控制, 对 .ucas 进行查找 & 读取,当 PoolThread 查找并读取到 .ucas 文件资产容器数据时,会向 AsyncLoading Thread2 发出通知并返回文件资产容器数据,
AsyncLoading Thread2 拿到加载的文件资产容器数据通过 Complete 回调给 Game Thread ,Game Thread 异步加载线程接收到返回的数据并通过 Complete 回调通知游戏 .ucas 数据读取完成。
使用 AsyncLoader2 加载和文件访问:
GameThread 向 AsyncLoadingThread2 发出 LoadPackage 加载和文件访问请求,LoadPackage 为一个加载包线程。
AsyncLoadingThread2 会创建 QueuePackage 加载包队列(包含多个 AsyncPackage2 异步加载包线程),然后加入到 EventQueue 线程事件队列(包含多个 AsyncPackage2)中进行等候,同时创建一个 ReadWithCallback 回调到 IO Dispatcher 调度器, IO Dispatcher 调度器会通过 ReadFromBlockFile 向 IO Service 发出请求文件读取控制, IO Service 查找和读取数据。
AsyncLoader2:
- 包含 AsyncLoadingThread2 新的异步加载线程;它利用 IO Store 进行更改;以有效的方式请求 IO 以减少 CPU 开销;
- IoDispatcher IO调度器:中继异步加载线程和 Io Service 服务;
- Io Service 服务运行 FileIO API 以从 .ucas 文件加载资产;
【加载的负载因素和加载对策】
使用 Unreal Insights 分析负载并探索补救措施。
可视化每个资产的线程处理和处理时间(资产加载洞察)。
使用 AsyncLoader2 加载和文件访问:
在 Unreal Insights 启用 GameThread 、Asynloading、IoService、IoDIspatcher;
捕获配置文件,通过添加启动参数 -trace = cpu, file, loadtime -statnamedevents -AsyncLoadingThread 启动应用程序;
验证使用 AsyncLoader2 加载和文件访问,耗时长的过程在哪里?
加载资产时的时间轴(流程)中,耗时最多的部分在 IO 、AsyncLoading Thread、Game Thread 线程中,因此有如下对策:
- (IO 对策)读取文件时的查找和读取对策;
- (IO 对策)减少资产数据大小;
- (IO 对策)更改资产的加载时间(内存常驻、先加载、后加载);
- (AsyncLoading 对策)参考(硬件/软件)管理;
- (AsyncLoading 对策)构造函数处理的优化;
- (AsyncLoading 对策)序列化过程(纹理、材料、静态网格等...)中产生的耗时,查看纹理(Shadowmap、Cubemap、Lightmap)分辨率和 Mip 设置;
- (AsyncLoading 对策)减少着色器引用的资源数量;
- (AsyncLoading 对策)减少静态网格物体的 LOD 设置;
- (Game 对策)材料后处理措施
- (Game 对策)关卡流送时序调整
- (Game 对策)组织多余的 Actor 和 Component 部分
- (Game 对策)动画控制、碰撞设置、物理物体数量的调整
- (Game 对策)不需要的材料的负载检查
总结:
- 使用 IO Store 缩短加载时间
- IO Store 在 4.25 和 4.26 中作为实验版提供
- 降低 IO 性能和 CPU 开销
- 加载时间得到积极改善
- Unreal Insights 对分析很有用
感谢日本工程师 Ken Kuwano 的全网唯一解释。