UE 性能分析与调试方案

UE 性能分析工具与方案

一、代码插桩宏(Code Instrumentation Macros)

通过在 C++ 代码中插入统计宏,标记需要测量耗时的代码块。这些数据会被 Unreal Insights 和 stat 命令采集。

1. 循环计数器宏(Cycle Counter)

用途 说明
DECLARE_STATS_GROUP(GroupDesc, GroupName, StatType) 声明统计分组 在 .cpp 顶部声明,将相关统计归类
DECLARE_CYCLE_STAT(StatName, StatId, GroupName) 声明一个循环计数统计 通常在文件作用域声明
SCOPE_CYCLE_COUNTER(StatId) 测量当前作用域耗时 作用域结束时自动记录
QUICK_SCOPE_CYCLE_COUNTER(StatId) 快速声明+测量 无需额外 DECLARE,一行搞定
SCOPED_NAMED_TIMER(Text, Flags) 快速命名计时器 无需提前声明,直接使用字符串标识

典型用法:

1
2
3
4
5
6
7
8
9
10
11
12
// 文件顶部声明
DECLARE_STATS_GROUP(TEXT("MyGame"), STATGROUP_MyGame, STATCAT_Advanced);
DECLARE_CYCLE_STAT(TEXT("ProcessAI"), STAT_ProcessAI, STATGROUP_MyGame);

void AMyActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// 测量整个函数体
SCOPE_CYCLE_COUNTER(STAT_ProcessAI);

// ... AI 逻辑 ...
}

2. 快速计时(无需预声明)

1
2
3
4
5
6
7
8
9
10
11
12
13
// 方式一:QUICK_SCOPE_CYCLE_COUNTER(一步到位)
void MyFunction()
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_MyFunction_LoadData);
// ... 加载数据逻辑 ...
}

// 方式二:SCOPED_NAMED_TIMER(用字符串标识)
void MyFunction()
{
SCOPED_NAMED_TIMER("MyFunction_LoadData", FStatGroup::Get_STATGROUP_Threads());
// ... 加载数据逻辑 ...
}

3. 计数统计宏(Counter Stats)

用途
DECLARE_DWORD_ACCUMULATOR_STAT(StatName, StatId, GroupName) 声明累计计数器
INC_DWORD_STAT(StatId) 计数器 +1
DEC_DWORD_STAT(StatId) 计数器 -1
SET_DWORD_STAT(StatId, Value) 设置计数器值
1
2
3
4
5
6
DECLARE_DWORD_ACCUMULATOR_STAT(TEXT("ActiveEnemies"), STAT_ActiveEnemies, STATGROUP_MyGame);

// 生成敌人时
INC_DWORD_STAT(STAT_ActiveEnemies);
// 敌人死亡时
DEC_DWORD_STAT(STAT_ActiveEnemies);

4. 内存统计宏

用途
DECLARE_MEMORY_STAT(StatName, StatId, GroupName) 声明内存统计
TRACK_OBJECT_STAT(ObjectClass, StatId) 追踪 UObject 内存占用

二、Unreal Insights

UE5 官方推出的高性能分析工具,取代旧版 Session Frontend / Profiler。

启动方式

  • 编辑器菜单:Tools → Unreal Insights
  • 命令行启动:UnrealInsights.exe
  • 运行游戏时添加参数:-trace=cpu,gpu,memory,loadtime

核心功能模块

模块 功能 查看内容
Timing Insights CPU/GPU 时间线 帧时间、函数调用耗时、线程活动、任务图(TaskGraph)
Memory Insights 内存分配追踪 内存泄漏、分配热点、对象生命周期
Networking Insights 网络分析 网络包大小、RPC 调用、复制带宽
Loading Insights 加载分析 资源加载耗时、异步加载瓶颈

Trace 通道(Channels)

启动时可指定采集哪些通道的数据:

1
-trace=cpu,gpu,memory,loadtime,net,frametime
通道 内容
cpu CPU 计时、线程调度
gpu GPU 渲染耗时、Draw Call
memory 内存分配/释放事件
loadtime 资源加载时间
net 网络复制数据
frametime 帧时间统计

常用分析场景

  1. 帧时间分析:Timing Insights → 展开 Game Thread → 找到耗时最长的 SCOPE_CYCLE_COUNTER 标记
  2. GPU 瓶颈定位:Timing Insights → GPU 视图 → 查看各 Pass 耗时
  3. 内存泄漏排查:Memory Insights → 对比两个时间点的内存快照
  4. 加载卡顿:Loading Insights → 排序找出加载最慢的资源

三、Stat 命令

在编辑器或运行时通过控制台(~ 键)输入 stat 命令查看实时性能数据。

常用命令

命令 说明
stat fps 显示帧率和帧时间
stat unit 显示各线程耗时(Game/Draw/RHI/GPU)
stat unitgraph 以图表形式显示线程耗时
stat scenerendering 场景渲染统计(Draw Call、三角形数、可见物体数)
stat engine 引擎基础统计
stat streaming 资源流式加载统计
stat memory 内存使用概览
stat particles 粒子系统统计
stat physics 物理模拟统计
stat net 网络统计
stat dumphitches 记录卡顿信息到日志

关键指标解读

1
2
3
4
5
6
7
8
9
10
11
stat unit 输出示例:
Frame: 33.2 ms ← 整帧耗时(目标 16.67ms = 60fps)
Game: 12.1 ms ← Game Thread(逻辑/AI/动画)
Draw: 5.3 ms ← Render Thread(渲染命令准备)
RHI: 4.1 ms ← RHI Thread(GPU 指令提交)
GPU: 18.7 ms ← GPU 实际渲染耗时

瓶颈判断:
- Game 高 → 逻辑代码优化(减少 Tick、优化 AI)
- Draw 高 → 减少可见物体、LOD、裁剪优化
- GPU 高 → 材质/光照/后处理优化

自定义 Stat 分组

1
2
// 声明自定义分组后,可通过 stat MyGroup 在控制台查看
DECLARE_STATS_GROUP(TEXT("My Game Stats"), STATGROUP_MyGame, STATCAT_Advanced);

Stat 命令进阶

命令 说明
stat startfile 开始记录统计到文件
stat stopfile 停止记录,生成 .ue4stats 文件,可用 Unreal Insights 打开
stat dumphitches 开启卡顿记录,超过阈值时输出调用栈到日志
t.MaxFPS 60 限制最大帧率
r.VSync 0 关闭垂直同步,测试裸性能

四、GPU 分析工具

1. ProfileGPU(控制台命令)

输入 ProfileGPU 后,会在 Output Log 中输出当前帧的 GPU Pass 明细:

1
2
3
4
5
6
Scene (18.2ms)
├── Base Pass (6.1ms)
├── Shadow Pass (3.2ms)
├── Lighting (4.8ms)
├── Translucency (2.1ms)
└── Post Process (2.0ms)

2. GPU Visualizer(vis 命令)

命令 说明
vis 打开 GPU Visualizer 窗口
ProfileGPU 单帧 GPU Profile
r.ScreenPercentage 50 降低渲染分辨率测试 GPU 负载

3. RenderDoc / PIX

  • RenderDoc:通用 GPU 帧分析器,支持 UE5。可逐 Draw Call 检查像素历史、Shader 资源。
  • PIX (Windows):微软 GPU 分析工具,对 Xbox/DirectX 项目友好。
  • Xcode GPU Profiler (macOS/iOS):Metal 平台 GPU 分析。
  • 这个功能(原本是UE4的)被整合进了 UE5 Unreal Insights 的 GPU 视图中。

五、内存分析

工具/命令 说明
obj list 列出所有 UObject 及其内存占用
obj list class=StaticMesh 列出指定类型的对象
memreport -full 生成完整内存报告
stat memory 实时内存统计
mimalloc UE5 可选的现代内存分配器(.ini 中配置)

六、蓝图性能分析

  • Blueprint Profiler:编辑器菜单 Tools → Blueprint Profiler
  • 查看蓝图节点执行耗时,定位慢节点
  • 优化建议:热点蓝图逻辑迁移到 C++

七、调试技巧

1. UE_LOG 标记用时

用日志打印时间戳来测量代码段耗时,适合快速排查:

1
2
3
4
UE_LOG(LogTemp, Warning, TEXT("disconnect(): waiting for receiveThread %s"), *FDateTime::Now().ToString());
QUICK_SCOPE_CYCLE_COUNTER(STAT_WaitReceive);
WaitForSingleObject(receiveThread, INFINITE);
UE_LOG(LogTemp, Warning, TEXT("disconnect(): receiveThread finished %s"), *FDateTime::Now().ToString());

输出示例:

1
2
Warning  LogTemp  disconnect(): waiting for receiveThread 2025.12.04-16.48.31
Warning LogTemp disconnect(): receiveThread finished 2025.12.04-16.48.46

两个时间戳相减即可得到该段代码的实际耗时(此处约 15 秒)。

2. 关闭局部代码优化

调试时编译器优化会干扰断点和变量观察,可对指定代码段临时关闭优化:

1
2
3
4
#pragma optimize("", off)
// 这里写你想调试的函数/代码
// 优化被关闭,变量不会被优化掉,断点能正常命中
#pragma optimize("", on)
  • "" 表示使用当前工程设置的优化选项集合
  • off/on 用来临时关/开优化
  • 只对它之后的代码生效(直到再 on 回去),一般包住某个函数实现

3. 自动化性能测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FMyPerfTest,
"Performance.MyGame.StressTest",
EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter
)

bool FMyPerfTest::RunTest(const FString& Parameters)
{
AddCommand(new FWaitLatentCommand(5.0f)); // 等待预热
AddCommand(new FMeasureTimeLatentCommand(
TEXT("StressTest_100Enemies"),
[this]() { Spawn100Enemies(); },
0.5f // 期望耗时阈值
));
return true;
}

八、性能分析流程建议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1. 定位瓶颈
stat fps + stat unit → 确认是 CPU 还是 GPU 瓶颈

2. CPU 瓶颈
├─ stat unitgraph → 确认哪个线程慢
├─ Unreal Insights Timing → 找到具体函数
├─ 检查 Tick、蓝图、GC、物理
└─ 用 SCOPE_CYCLE_COUNTER 细化测量

3. GPU 瓶颈
├─ ProfileGPU → 看 Pass 分布
├─ stat scenerendering → Draw Call / 三角形数
├─ GPU Visualizer → 可视化分析
└─ RenderDoc → 深入单个 Draw Call

4. 内存问题
├─ stat memory → 概览
├─ memreport -full → 详细报告
└─ Unreal Insights Memory Insights → 内存泄漏追踪

5. 快速调试
├─ UE_LOG + 时间戳 → 快速定位耗时
└─ #pragma optimize("", off) → 关闭优化方便断点调试

参考文章

https://zhuanlan.zhihu.com/p/273608458