Skip to content

fix(perf): 使用 TypeCache 加速 Skill 扫描#48

Open
daixian wants to merge 1 commit into
Besty0728:betafrom
daixian:fix/typecache-skill-discovery
Open

fix(perf): 使用 TypeCache 加速 Skill 扫描#48
daixian wants to merge 1 commit into
Besty0728:betafrom
daixian:fix/typecache-skill-discovery

Conversation

@daixian

@daixian daixian commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

每次编译之后都会Domain重载然后做反射发现,这里理论上最优是应该使用 Unity 原生的 TypeCache 索引来做。让codex做了对比测试,之前的开销也只有200ms,性能瓶颈不是很大,也可以不改。

以下是codex的测试和审核报告,供参考。


性能优化:使用 TypeCache 加速 Skill 扫描

改动说明

本次改动使用 Unity 原生的 TypeCache 索引替代原有的全域反射扫描,用于发现带有 [UnitySkill] 特性的方法。

旧实现的扫描路径:

全部已加载程序集
→ Assembly.GetTypes()
→ Type.GetMethods(Public | Static)
→ MethodInfo.GetCustomAttribute<UnitySkillAttribute>()

新实现直接调用:

TypeCache.GetMethodsWithAttribute<UnitySkillAttribute>()

具体改动见 SkillRouter.cs

Unity 官方说明,TypeCache 使用原生侧建立的加速索引,与遍历整个 Domain 的 System.Reflection 相比,更适合编辑器工具中的类型和特性查找:

改动原因

原实现会遍历 Unity Domain 中所有已加载程序集、所有类型以及所有公开静态方法,扫描开销会随项目规模线性增长。

在本次测试项目中,运行时共发现 770 个 public static Skill 方法,其中 UnitySkills.Editor 提供 733 个,外部 xuexue.automation.editor 程序集提供 37 个。改用 TypeCache 后,托管层处理范围直接收敛到这些带有 [UnitySkill] 特性的候选方法,不再遍历 Unity Domain 中的其他类型和静态方法。

这对于 Domain Reload 后重建 Skill 路由缓存尤其有帮助。

新实现仍然会产生必要的处理成本,包括:

  • 获取候选方法的 MethodInfo
  • 读取并实例化 UnitySkillAttribute
  • 构建参数数组和名称数组
  • 创建 HashSetSkillInfo
  • 重建 Manifest 和 Schema 索引

因此该改动并非完全消除扫描成本,而是移除了开销最宽的全域类型和方法枚举。

性能测试

在相同 Unity 项目状态下,分别对 beta 旧实现和 fix/typecache-skill-discovery 新实现进行了对比测试。

测试方式:

  • 直接测量 SkillRouter.Refresh()
  • 两个分支均发现 770 个 Skill
  • 每轮测试先预热一次
  • 每个分支执行 3 轮
  • 每轮采集 9 个样本
  • 每个分支共计 27 个有效样本
指标 beta 反射扫描 fix/typecache-skill-discovery TypeCache 改善
中位数 250.817 ms 24.309 ms 降低 90.31%,约 10.32 倍
平均值 260.840 ms 27.531 ms 约 9.47 倍
P90 306.048 ms 45.178 ms 降低 85.24%,约 6.77 倍
最小值 241.264 ms 19.546 ms
最大值 319.751 ms 55.130 ms

测试结果表明,Skill 扫描和路由重建耗时稳定降低了约 90%。

测试环境

  • 设备:ASUS ROG Strix G16 G614JVR
  • CPU:Intel Core i9-14900HX,24 核 32 线程
  • 内存:96 GB DDR5-5200,Micron 48 GB × 2
  • 项目磁盘:Samsung MZVL22T0HBLB-00B00,2 TB NVMe SSD
  • 操作系统:Windows 11 专业版,Build 26200
  • Unity:6000.5.0f1

兼容性

  • 保留原有的 public static 限制。
  • 不会将私有方法或实例方法意外注册为 Skill。
  • 可以发现外部 asmdef 程序集中定义的 Skill。
  • 包最低支持 Unity 2022.3,TypeCache API 兼容性没有问题。
  • 两种实现均发现了相同的 770 个 Skill。

验证结果

  • Unity 编译通过,错误数为 0。
  • git diff --check 通过。
  • UnitySkills 核心 EditMode 测试通过:10/10。
  • SkillRouter.GetSchema() 测试通过。
  • 外部 automation_command_list 调用成功。
  • 成功发现 36 个外部 Command。

审查发现

本次改动没有阻断问题,但仍有两个低优先级隐患。

重复 Skill 名称

当前注册逻辑仍然使用:

skills[name] = new SkillInfo(...);

出现同名 Skill 时,后发现的方法会静默覆盖之前的结果。

Unity 官方明确说明,TypeCache.GetMethodsWithAttribute 返回结果的顺序未定义。因此,不同程序集存在同名 Skill 时,最终生效的方法可能不确定。

这个问题在旧实现中已经存在,并非本次改动引入。后续可以增加重复名称检测,并输出明确错误。

窗口刷新存在重复处理

Skills 窗口刷新时会依次调用:

_window.RefreshSkillsList();
SkillRouter.Refresh();

具体位置见 SkillsTabController.cs

这会执行两次 TypeCache 查询,并重复进行部分 Attribute 实例化、窗口列表和路由字典构建。

目前 TypeCache 查询本身已经很轻,这还不是性能瓶颈。后续可以让窗口直接使用 SkillRouter 的缓存快照,进一步消除重复处理。

结论

本次改动在保持原有行为和外部 Skill 扩展能力的前提下,移除了 Skill 发现阶段对全部程序集、类型和静态方法的遍历。

实际对比测试显示,Skill 扫描和路由重建耗时降低约 90%,建议合并。

@Besty0728

Copy link
Copy Markdown
Owner

感谢你的PR贡献,我在忙完这段时间后会连同你之前的PR一起评审合并

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants