18 KiB
背景
文件名:2025-12-01_1_performance_optimization_aggressive.md 创建于:2025-12-01 创建者:User 主分支:main 任务分支:task/performance_optimization_aggressive_2025-12-01_1 Yolo模式:Off
任务描述
对Unity音游编辑器项目进行激进的、彻底的性能优化,使用最先进的技术栈。项目限定在Windows 10+平台运行。
核心问题
基于Tracy Profiler分析结果:
- SplineNode.ScriptUpdate: 5.66ms (19.37%), 3,240次调用 - 函数调用开销过大
- 递归Update模式: 深度树形遍历导致缓存不友好
- 剪枝不彻底: 大量对象在非活跃时间范围内仍在Update
- Timeline UI更新: 存在严重性能开销(已知问题)
性能目标
- 将SplineNode.ScriptUpdate从5.66ms优化到<0.5ms(10倍以上提升)
- 帧率从77fps提升到120fps+
- 消除不必要的Update调用(减少70%+调用次数)
项目概览
- 项目类型: Unity音游编辑器
- 核心框架: Convention框架 + RScript脚本系统(基于FLEE)
- 主要系统:
- ScriptableObject树形更新系统
- Updatement插值系统
- IInteraction多级判定系统
- SplineCore样条线渲染
- TimelineScriptObject时间轴对象
技术栈
- Unity 6.2 (6000.2.51)
- C# (支持最新特性)
- Tracy Profiler 0.11.1
- Windows 10+ 专用
⚠️ 警告:永远不要修改此部分 ⚠️
RIPER-5核心规则摘要
- 必须在响应开头声明当前模式:[MODE: MODE_NAME]
- RESEARCH模式:只读取和理解,禁止建议和实施
- INNOVATE模式:讨论多种方案,禁止具体实施
- PLAN模式:创建详尽技术规范,必须转换为编号清单
- EXECUTE模式:只实施已批准计划,禁止偏离
- REVIEW模式:验证实施与计划的完全一致性
关键原则:
- 未经明确许可不得在模式间转换
- EXECUTE模式必须100%忠实遵循计划
- 标记任何偏差,无论多小
- 使用中文响应(除模式声明和代码) ⚠️ 警告:永远不要修改此部分 ⚠️
分析
当前架构分析
1. Update调用链(存在问题)
GameController.Update()
└─ RootObject.ScriptUpdate()
└─ foreach (child in UpdateChilds)
└─ child.ScriptUpdate() // 递归,3,240次
└─ child.UpdateTicks() // 虚函数调用
└─ 具体逻辑
性能问题:
- 递归调用开销累积
- 虚函数调用开销
- 缓存不友好(对象内存分散)
- 无法并行化
- 大量对象在非活跃时间范围内仍被调用
2. 现有优化机制(不足)
代码中存在一些优化:
UpdatePerFrame: 降低Update频率IsEnableUpdate: 静态剪枝(只能剪掉永远不更新的对象)- Update树剪枝:简化单子节点调用链
不足之处:
- 无法剪枝"暂时不活跃"的对象
- 无法利用多核CPU
- 内存布局对缓存不友好
3. 技术约束
- Unity主线程限制:Transform等API必须在主线程调用
- MonoBehaviour限制:继承自MonoBehaviour的对象管理复杂
可用的先进技术
Windows 10+ 专属优势
- SIMD指令集: AVX2, AVX-512(如果CPU支持)
- 现代CPU特性: 更多核心,更好的缓存
- .NET 最新特性: Span, Memory, stackalloc
- Unity DOTS: ECS + Job System + Burst Compiler
Unity DOTS技术栈
-
ECS (Entity Component System)
- 数据驱动架构
- 缓存友好的内存布局
- 易于并行化
-
C# Job System
- 托管的多线程系统
- 自动线程池管理
- 安全检查避免竞态条件
-
Burst Compiler
- 将C#编译为高度优化的机器码
- SIMD自动向量化
- 接近C/C++的性能
-
Collections Package
- NativeArray, NativeList等非托管集合
- 可在Job中安全使用
- 避免GC压力
性能提升潜力
| 技术 | 预期提升 | 适用场景 |
|---|---|---|
| 时间范围剪枝 | 3-5倍 | 所有TimelineObject |
| 扁平化调度 | 2-3倍 | Update调用链 |
| Burst编译 | 5-10倍 | 数学密集计算 |
| Job并行 | 2-4倍 | 批量数据处理 |
| 缓存优化 | 2-3倍 | 数据访问模式 |
| 综合 | 30-100倍 | 整体系统 |
提议的解决方案
方案概览
采用混合架构:保留MonoBehaviour作为外壳,内部使用ECS数据处理。
架构设计
传统层(MonoBehaviour)
↓ 数据同步
ECS数据层(Burst Job处理)
↓ 结果同步
传统层(应用到Transform)
核心方案要素
1. 时间分片调度系统
目标: 只更新活跃时间范围内的对象
实现:
- 使用SortedSet维护时间排序
- 激活/停用事件驱动
- 扁平化对象列表(无递归)
预期: 减少70%无效Update调用
2. ECS化核心系统
2.1 Updatement系统(最适合ECS)
- 纯数据:Entry列表、插值参数
- 纯计算:Lerp、曲线求值
- 可完全Burst编译和并行化
2.2 Interaction判定系统
- 输入处理可并行化
- 判定计算可Burst优化
- 结果批量应用
2.3 SplineNode计算
- 样条线计算适合SIMD
- 批量计算所有节点位置
- Burst编译获得巨大提升
3. Burst编译的Job
[BurstCompile]
public struct UpdatementCalculationJob : IJobParallelFor
{
[ReadOnly] public float CurrentTime;
[ReadOnly] public NativeArray<UpdatementEntry> Entries;
[ReadOnly] public NativeArray<int> ContentIndices;
[WriteOnly] public NativeArray<float3> Results;
public void Execute(int index)
{
// 纯数学计算,Burst自动SIMD优化
int content = ContentIndices[index];
float percent = CalculatePercent(CurrentTime, Entries, content);
Results[index] = math.lerp(
Entries[content].Position,
Entries[content + 1].Position,
EvaluateCurve(percent, Entries[content].CurveType)
);
}
}
4. 内存布局优化
SoA (Structure of Arrays) 代替 AoS (Array of Structures):
// 坏:AoS(缓存不友好)
struct UpdatementData {
float timePoint;
Vector3 position;
EaseCurveType curve;
}
UpdatementData[] data; // 数据交错
// 好:SoA(缓存友好)
struct UpdatementDataSoA {
NativeArray<float> timePoints; // 连续
NativeArray<float3> positions; // 连续
NativeArray<byte> curveTypes; // 连续
}
5. 对象池系统
- 避免GC压力
- 重用NativeArray
- 预分配Job句柄
实施分层
Phase 1: 基础设施(1周)
- 时间分片调度器
- UpdateScheduler基类
- 基础性能测试框架
Phase 2: ECS核心(2周)
- Updatement系统ECS化
- Burst Job实现
- 数据同步机制
Phase 3: 扩展优化(2周)
- SplineNode优化
- Interaction判定优化
- 对象池和内存管理
Phase 4: 深度优化(1周)
- SIMD手动优化关键路径
- 内存布局调优
- 性能剖析和微调
技术细节
Job依赖管理
// 计算Job
JobHandle calcHandle = calculationJob.Schedule(count, 64);
// 应用Job(依赖计算Job)
JobHandle applyHandle = applyJob.Schedule(calcHandle);
// 等待完成
applyHandle.Complete();
安全检查
Unity Job System提供编译时和运行时安全检查:
- 检测数据竞争
- 验证NativeArray生命周期
- 防止悬空指针
Profiler集成
保持Tracy Profiler集成:
using (Profiler.BeginZone("BurstJob.Schedule"))
{
handle = job.Schedule(count, 64);
}
风险与挑战
技术风险
- 学习曲线: ECS和Burst需要学习
- 调试难度: Burst代码调试较困难
- 兼容性: 现有脚本系统集成
缓解措施
- 渐进式迁移,保持向后兼容
- 完善的性能测试
- 详细的文档和注释
回滚计划
每个Phase完成后打tag,必要时可回滚
当前执行步骤:
"阶段4.8 - 阶段4整体测试"
任务进度
阶段1:基础设施搭建
1.1 创建目录结构
[2025-12-01 17:16:16]
- 已修改:创建目录 Assets/Scripts/Framework/UpdateScheduler/
- 更改:新建UpdateScheduler模块目录
- 原因:为扁平化调度器准备代码组织结构
- 阻碍因素:无
- 状态:未确认
1.2 创建IUpdateable接口
[2025-12-01 17:17:30]
- 已修改:创建文件 IUpdateable.cs (48行)
- 更改:定义IUpdateable接口(3个方法)和UpdateMode枚举(3个值)
- 原因:为ScriptableObject提供统一的Update接口,支持扁平化调度
- 阻碍因素:无
- 状态:未确认
1.3 创建UpdateScheduler核心类
[2025-12-01 17:18:45]
- 已修改:创建文件 UpdateScheduler.cs (201行)
- 更改:实现完整的扁平化调度器(Register、DoUpdate、时间分片、Reset/Clear等)
- 原因:提供时间分片和扁平化Update管理的核心实现
- 阻碍因素:无
- 状态:未确认
1.4 RootObject添加Scheduler字段
[2025-12-01 17:20:15]
- 已修改:RootObject.cs (添加1行,Line 19)
- 更改:添加Scheduler字段并初始化
- 原因:为RootObject提供调度器实例
- 阻碍因素:无
- 状态:未确认
1.5 RootObject添加清理逻辑
[2025-12-01 17:21:30]
- 已修改:RootObject.cs UnloadScript方法 (添加2行,Line 38-39)
- 更改:在卸载时调用Scheduler.Clear()
- 原因:防止内存泄漏,正确释放注册对象
- 阻碍因素:无
- 状态:未确认
1.6 阶段1整体测试
[2025-12-01 17:22:45]
- 已修改:阶段1所有代码完成,等待测试
- 更改:基础设施搭建完成(3个新文件,RootObject修改2处)
- 原因:验证阶段1无破坏性影响
- 测试项目:
- 编译项目 - ✅ 已通过(无linter错误)
- 运行编辑器 - ✅ 用户确认成功
- 打开项目 - ✅ 用户确认成功
- 检查RootObject.Scheduler字段 - ✅ 用户确认成功
- 关闭项目 - ✅ 用户确认成功
- 检查控制台无错误 - ✅ 用户确认成功
- 阻碍因素:无
- 状态:✅ 成功
- Commit: 用户已提交
阶段2:接口适配
2.1 ScriptableObject添加IUpdateable接口声明
[2025-12-01 17:30:00]
- 已修改:ScriptableObject.cs 类声明 (Line 582,添加IUpdateable接口)
- 更改:ScriptableObject实现IUpdateable接口
- 原因:使所有ScriptableObject可以被调度器管理
- 阻碍因素:无
- 状态:未确认
2.2 添加Update模式字段
[2025-12-01 17:32:15]
- 已修改:ScriptableObject.cs (添加3个字段,Line 111-116)
- 更改:添加_updateMode、_activeStartTime、_activeEndTime字段
- 原因:存储对象的Update模式和时间范围
- 阻碍因素:无
- 状态:未确认
2.3 添加虚方法供子类重写
[2025-12-01 17:34:30]
- 已修改:ScriptableObject.cs (添加2个虚方法,Line 175-183)
- 更改:添加GetUpdateMode和GetActiveTimeRange虚方法
- 原因:允许子类自定义Update模式和时间范围
- 阻碍因素:无
- 状态:未确认
2.4 实现IUpdateable接口方法
[2025-12-01 17:36:45]
- 已修改:ScriptableObject.cs (添加新的partial class,约35行,Line 916-950)
- 更改:实现IUpdateable的3个接口方法(ExecuteUpdate、GetUpdateName、IsUpdateReady)
- 原因:提供扁平化Update入口,复用原有UpdateTicks逻辑
- 阻碍因素:无
- 状态:未确认
2.4.1 重命名方法为FlatOptimizationUpdate(更具描述性)
[2025-12-01 17:40:00]
- 已修改:IUpdateable.cs、UpdateScheduler.cs、ScriptableObject.cs
- 更改:将方法重命名为FlatOptimizationUpdate,明确表达"扁平化优化Update"的含义
- 原因:提高代码可读性,方法名清晰表达优化目的
- 阻碍因素:无
- 状态:未确认
2.5 阶段2整体测试
[2025-12-01 17:38:00]
- 已修改:阶段2所有代码完成,等待测试
- 更改:ScriptableObject完整实现IUpdateable接口(添加3个字段、2个虚方法、3个接口方法)
- 原因:验证接口实现无破坏性影响
- 测试项目:
- 编译项目 - ✅ 已通过(无linter错误)
- 运行编辑器 - ✅ 用户确认成功
- 打开项目 - ✅ 用户确认成功
- 检查Inspector中新增字段是否可见 - ✅ 用户确认成功
- 验证游戏功能不受影响 - ✅ 用户确认成功
- 阻碍因素:无
- 状态:✅ 成功
- Commit: 用户已提交
阶段3:可选注册机制
3.1 添加注册控制字段
[2025-12-01 17:45:00]
- 已修改:ScriptableObject.cs (添加1个字段,Line 120)
- 更改:添加_useUpdateScheduler控制字段,默认false
- 原因:提供可选的调度器注册开关,默认不影响现有逻辑
- 阻碍因素:无
- 状态:未确认
3.2 添加脚本方法启用调度器
[2025-12-01 17:47:15]
- 已修改:ScriptableObject.cs (添加EnableUpdateScheduler方法,Line 178-183)
- 更改:添加RScript可调用的启用方法
- 原因:允许脚本中手动启用调度器
- 阻碍因素:无
- 状态:未确认
3.3 修改ApplyScript添加注册逻辑
[2025-12-01 17:49:30]
- 已修改:ScriptableObject.cs ApplyScript方法 (添加5行,Line 678-682)
- 更改:在ApplyScript中添加可选注册调用
- 原因:对象应用时自动注册到调度器(如果启用)
- 阻碍因素:无
- 状态:未确认
3.4 实现RegisterToScheduler方法
[2025-12-01 17:51:45]
- 已修改:ScriptableObject.cs (添加RegisterToScheduler私有方法,约25行,Line 684-708)
- 更改:实现调度器注册逻辑,包含错误处理和日志
- 原因:封装注册流程,自动获取Update模式和时间范围
- 阻碍因素:无
- 状态:未确认
3.5 阶段3整体测试
[2025-12-01 17:53:00]
- 已修改:阶段3所有代码完成,等待测试
- 更改:实现可选注册机制(添加1个字段、1个公开方法、1个私有方法、修改ApplyScript)
- 原因:验证注册机制正确工作
- 测试项目:
- 编译项目 - ✅ 已通过(无linter错误)
- 运行编辑器 - 待测试
- 在测试脚本中调用EnableUpdateScheduler() - 待测试
- 检查控制台日志,确认对象被注册 - 待测试
- 验证默认行为不变(_useUpdateScheduler=false) - 待测试
- 阻碍因素:无
- 状态:未确认 → 等待用户确认:成功/不成功?
阶段4:双轨运行和实际调用
4.1 GameController添加控制字段
[2025-12-01 18:00:00]
- 已修改:GameController.cs (添加1个字段,Line 22)
- 更改:添加_enableSchedulerUpdate控制字段,默认false
- 原因:控制GameController使用新旧Update方式
- 阻碍因素:无
- 状态:未确认
4.2 修改GameController主Update循环
[2025-12-01 18:02:15]
- 已修改:GameController.cs Update方法主循环 (修改8行,Line 318-329)
- 更改:添加新旧Update方式分支,支持调用FlatOptimizationUpdate或ScriptUpdate
- 原因:允许运行时切换Update方式进行对比
- 阻碍因素:无
- 状态:未确认
4.3 修改GameController滚动时间轴Update调用
[2025-12-01 18:04:30]
- 已修改:GameController.cs Update方法滚动逻辑 (修改4行,Line 373-376)
- 更改:滚动时间轴也支持新旧方式切换
- 原因:确保所有Update调用点一致
- 阻碍因素:无
- 状态:未确认
4.4 修改GameController滚动Reset逻辑
[2025-12-01 18:05:45]
- 已修改:GameController.cs Update方法滚动Reset (修改10行,Line 378-388)
- 更改:滚动重置也支持新旧方式切换
- 原因:确保Reset调用点一致
- 阻碍因素:无
- 状态:未确认
4.5 修改ForceScriptUpdate方法
[2025-12-01 18:07:00]
- 已修改:GameController.cs ForceScriptUpdate方法 (修改5行,Line 294-299)
- 更改:强制更新也支持新旧方式切换
- 原因:确保所有Update入口一致
- 阻碍因素:无
- 状态:未确认
4.6 修改Stop/Pause/Play方法
[2025-12-01 18:08:15]
- 已修改:GameController.cs Stop/Pause/Play方法 (修改15行,Line 263-292)
- 更改:播放控制方法也支持新旧方式切换
- 原因:确保播放控制的Update调用一致
- 阻碍因素:无
- 状态:未确认
4.7 RootObject.UpdateTicks调用调度器(关键)
[2025-12-01 18:10:30]
- 已修改:RootObject.cs UpdateTicks方法 (添加10行,Line 111-120)
- 更改:RootObject调用Scheduler.DoUpdate()进行扁平化Update,移除base.UpdateTicks()调用
- 原因:这是关键步骤,让Scheduler实际被调用,建立完整调用链
- 阻碍因素:无
- 状态:未确认
4.7.1 修复崩溃问题 - 条件调用Scheduler
[2025-12-01 18:15:00]
- 已修改:RootObject.cs UpdateTicks方法 (修改逻辑,Line 112-124)
- 更改:添加条件判断,仅在Scheduler有注册对象时调用Scheduler,否则调用base.UpdateTicks()保持旧行为
- 原因:修复旧方式下的崩溃问题,避免双重更新和冲突
- 阻碍因素:无
- 状态:未确认
4.8 阶段4整体测试
[2025-12-01 18:12:00]
- 已修改:阶段4所有代码完成,等待测试
- 更改:实现双轨Update,支持新旧方式切换(GameController 6处修改,RootObject 1处关键修改)
- 原因:验证新方式功能正确且性能提升,Scheduler实际被调用
- 测试项目:
- 编译项目 - ✅ 已通过(无linter错误)
- 旧方式测试(_enableSchedulerUpdate=false) - 待测试
- 新方式测试(_enableSchedulerUpdate=true,部分对象启用调度器) - 待测试
- 功能验证(播放/暂停/停止/滚动时间轴) - 待测试
- 性能对比(Tracy Profiler数据) - 待测试
- 阻碍因素:无
- 状态:未确认 → 等待用户确认:成功/不成功?
- 调用链验证:
- 旧方式:GameController → MainObject.ScriptUpdate() → 递归
- 新方式:GameController → MainObject.FlatOptimizationUpdate() → RootObject.UpdateTicks() → Scheduler.DoUpdate() → 扁平化
最终审查
[待完成]