mcpField
P
performance-optimization

performance-optimization

github.com/gamedev-skills开发5.4k8.2k 次安装1 天前更新
代码仓库

关于 performance-optimization

面向游戏开发者的跨引擎性能优化技能,提供先分析瓶颈、再针对性修复并复测的方法。内容包括对象池、批处理、减少分配和资源预算。

主要功能

  1. 01先分析性能并识别瓶颈
  2. 02区分 CPU 与 GPU 开销
  3. 03使用对象池和批处理
  4. 04减少每帧内存分配
  5. 05设定帧时间与资源预算

适用场景

  1. 01排查帧率低、卡顿或帧时间波动
  2. 02确定优化 CPU 还是 GPU 工作
  3. 03减少频繁生成对象带来的开销
  4. 04为项目建立性能与资源检查

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/gamedev-skills/awesome-gamedev-agent-skills --skill performance-optimization

安装后在 Claude Code 中输入/performance-optimization确认已加载。

附带文件references/profiling-and-budgets.md

详细介绍

性能优化

性能优化是一门测量学科,而不是一堆技巧。方法始终相同:分析性能 → 找出唯一的瓶颈 → 修复瓶颈 → 再次测量。这项技能介绍这一循环和最有效的优化方法(对象池、批处理、控制内存分配、资源预算),并指引你使用各个引擎的分析器。它与 physics-tuning 配合使用,可优化模拟开销。

适用场景

  • 帧率低或不稳定、游戏卡顿,或者游戏必须达到目标帧率(桌面端 60 FPS、移动端 30/60 FPS)但目前未能达到时使用。
  • 决定优化什么时使用:先分析性能、查看帧预算,并在修改代码前确定瓶颈在 CPU 还是 GPU。
  • 应用具体优化时使用:对象池、减少绘制调用/批次、移除逐帧内存分配和 GC 峰值,以及设置资源预算。

**不适用场景:**针对物理抖动、物体穿透或时间步长问题,请使用 physics-tuning。如需了解引擎的具体分析器界面和渲染设置,请使用对应的引擎技能(godot-export 涵盖部分构建设置;其余内容由引擎核心技能涵盖)。这项技能介绍跨引擎通用的方法和优化手段。

黄金法则:先测量,切勿猜测

许多未经性能分析就实施的“优化”都针对了错误的问题,徒增复杂度却没有收益。不要优化未经测量的代码。打开性能分析器,在具有代表性的硬件上运行有代表性的场景,找出最大的单项开销并加以修复。之后重新测量,确认优化有效,再继续下一步。对于重要场景,请在发布版/优化构建中进行性能分析——编辑器和调试构建的结果并不准确(有编辑器开销,且没有编译器优化)。

核心工作流程

  1. **确定目标并重现问题。**说明目标(例如 60 FPS = 每帧 16.67 毫秒),并找出可以重复运行的最坏情况场景。“有时会变慢”无法修复;可复现的峰值则可以修复。
  2. **修改代码前先进行性能分析。**运行引擎分析器并查看帧数据:总帧时间,以及 CPU(游戏逻辑、物理、脚本)和 GPU(渲染)之间的占比。
  3. **找出瓶颈——CPU 还是 GPU。**如果 GPU 时间 ≫ CPU 时间,就着手减少绘制调用、过度绘制、着色器开销或分辨率。如果 CPU 时间占主导,就着手优化脚本、物理计算和内存分配。优化错误的一侧毫无作用。
  4. 修复最大的单项开销。优先考虑算法层面的改进(减少工作量、缓存、空间分区、降低运行频率),而不是微优化热点代码行。采用匹配的通用优化方法(对象池、批处理、移除内存分配)。
  5. **在相同场景和硬件上重新测量。**确认数据有所改善。依据数据而非直觉决定保留还是回退。
  6. **设定预算,确保问题不会复发。**为每个子系统设定逐帧毫秒预算,并设定资源预算(纹理尺寸、三角形数量、绘制调用上限);将性能检查纳入验证流程。
  7. **报告实测数据。**说明优化前后的帧时间、发现的瓶颈和所做的修复——不要说“应该会更快”。如果只能在编辑器中测量,请明确说明。

模式

1. 帧预算计算(把“感觉很慢”转化为数字)
target FPS → frame budget:   60 FPS = 16.67 ms   |   30 FPS = 33.3 ms   |   120 FPS = 8.33 ms
The WHOLE frame (CPU sim + render submit + GPU) must fit the budget; the GPU runs in parallel,
so the slower of CPU-frame and GPU-frame sets your FPS. Allocate sub-budgets, e.g. @60 FPS:
  gameplay/scripts ~5 ms · physics ~3 ms · rendering(CPU submit) ~4 ms · UI/other ~2 ms · slack.
If one subsystem blows its slice, that's your target — not whatever you assumed.
2. 使用引擎分析器测量(实施任何优化前都要先做)
Godot 4.7 : Debugger ▸ Profiler (script/physics time) and Monitors tab (FPS, draw calls, memory).
            In code: Performance.get_monitor(Performance.TIME_PROCESS) and
            Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME).
Unity 6.3 LTS   : Profiler window (CPU/GPU/Memory/Rendering modules) + Frame Debugger for draw calls.
            In code: a ProfilerRecorder tracking "CPU Main Thread Frame Time" for a HUD/log.
Unreal 5  : `stat unit` (Frame/Game/Draw/GPU ms), `stat fps`, `stat scenerendering` (draw calls);
            Unreal Insights for deep traces.
# Read the split: is the Draw/GPU line the biggest, or the Game/CPU line? That decides the fix.
3. 对象池(避免在热点循环中反复分配和释放)
# Bullets, particles, enemies, damage numbers: reuse a fixed set instead of instantiate()/free()
# every frame — that thrashes memory and (in C#) feeds the GC.
var _pool: Array[Node] = []
func acquire() -> Node:
    var n: Node = _pool.pop_back() if not _pool.is_empty() else bullet_scene.instantiate()
    n.set_process(true); n.visible = true
    return n
func release(n: Node) -> void:
    n.set_process(false); n.visible = false       # disable + hide; DON'T free
    _pool.append(n)                                # back to the pool for reuse
# RIGHT: pre-warm the pool at load; reuse. WRONG: instantiate()/queue_free() per shot.
4. 减少绘制调用(最常见的 GPU 侧优化)
Each unique material/texture/state change is roughly a draw call; thousands of them stall the GPU.
- Atlas textures and share materials so sprites/meshes batch into one call.
- Identical meshes → GPU instancing (Unity), MultiMesh / MultiMeshInstance (Godot), Instanced
  Static Mesh (Unreal).
- Static geometry → static batching / baking; mark non-moving objects static.
- Reduce overdraw: limit large overlapping transparent/particle layers (they re-shade pixels).
- Fewer real-time lights/shadows; bake lighting where it doesn't move.
Measure draw calls before and after — the count should drop, and so should GPU frame time.
5. 消除逐帧内存分配(GC 峰值会导致卡顿)
// Unity 6.3 LTS (C#). Allocating every frame fills the managed heap; the GC then stalls a frame.
// WRONG (allocates each call): foreach (var e in FindObjectsOfType<Enemy>()) ...  // + LINQ, new[]
// RIGHT: cache references once, reuse buffers, avoid LINQ/boxing in Update.
void Update() {
    _hits = Physics.RaycastNonAlloc(ray, _hitBuffer);   // reuse a preallocated array
    for (int i = 0; i < _hits; i++) { /* ... */ }       // no per-frame allocation
}
// Godot/GDScript: avoid building new arrays/dictionaries every frame in _process; reuse them.

常见陷阱

  • 未进行性能分析就优化。 直觉认为的问题通常并非真正的瓶颈。每次都应先测量。
  • 对编辑器或调试版本进行性能分析。 编辑器开销和未优化的代码会造成误导。应在目标硬件上对发布版本进行性能分析,获取真实数据。
  • 优化了错误的一端。 当瓶颈在 GPU 时微优化 CPU 代码(反之亦然),不会有任何改变。先检查 CPU 和 GPU 的负载分布。
  • 微优化胜过算法优化。 当真正的成本来自 O(n²) 循环或每帧进行的全场景查询时,却只优化某个函数。应减少工作量,而不是打磨代码。
  • 在高频循环中实例化和释放对象。 每帧生成并销毁子弹/粒子会导致内存碎片和 GC 峰值。应使用对象池。
  • 在 Update 中进行每帧分配/使用 LINQ/装箱(C#),会增加 GC 负担,导致周期性卡顿。应缓存并复用。
  • 唯一材质以及未批处理的精灵/网格会导致绘制调用激增。 使用图集、共享材质、实例化和批处理。
  • 过度绘制:堆叠的透明对象/粒子/全屏效果会对像素反复着色。
  • 没有预算。 如果没有针对各子系统的毫秒数和资源上限,性能就会悄然退化;应在构建/CI 检查中强制执行这些限制。
  • 过早优化。 在原型还不好玩或尚未测量性能之前,不要为了性能扭曲设计。

参考资料

  • 有关各引擎的性能分析器使用指南、CPU 与 GPU 瓶颈排查流程图、完整的对象池管理器、各引擎的批处理/实例化规则、分配/GC 指南、LOD/剔除以及资源预算(纹理尺寸、三角形数量、音频、移动设备散热),请阅读 references/profiling-and-budgets.md。

相关技能

  • physics-tuning — 仿真开销、固定步长预算、休眠刚体、宽相位层。
  • godot-export — 会影响测量性能的发布/构建设置。
  • procedural-gen、game-ai — 常见的 CPU 热点(生成、寻路),应纳入预算并延后处理。
  • roguelike、tower-defense、survival-crafting — 需要对象池/预算的实体密集型游戏类型。

评论

支持 Markdown