performance-optimization
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePerformance optimization
性能优化
Performance work is a measurement discipline, not a bag of tricks. The method is always the
same: profile → find the one bottleneck → fix that → measure again. This skill teaches that
loop and the highest-leverage fixes (pooling, batching, allocation control, asset budgets), and
points you at each engine's profiler. It pairs with for simulation cost.
physics-tuning性能优化是一门基于测量的学科,而非一堆零散技巧。方法始终如一:分析(Profile)→ 找到核心瓶颈 → 修复问题 → 再次测量。本技能将教授这一循环流程以及最高效的修复方案(对象池、批处理、分配控制、资源预算),并指引你使用各引擎的分析器。它可与配合使用,以优化模拟成本。
physics-tuningWhen to use
使用场景
- Use when the frame rate is low or uneven, the game stutters/hitches, or it must hit a target (60 FPS desktop, 30/60 mobile) and currently doesn't.
- Use to decide what to optimize: profile, read the frame budget, and identify whether the CPU or GPU is the bottleneck before changing any code.
- Use to apply specific fixes: object pooling, draw-call/batch reduction, removing per-frame allocations and GC spikes, and setting asset budgets.
When not to use: for physics jitter/tunneling/timestep specifically, use .
For the engine's concrete profiler UI and rendering settings, use that
engine skill ( covers some build settings; engine cores cover the rest). This skill
is the cross-engine method and the shared fixes.
physics-tuninggodot-export- 当帧率较低或不稳定、游戏出现卡顿/停滞,或需要达到目标帧率(桌面端60 FPS,移动端30/60 FPS)但当前未达标时使用。
- 用于确定优化方向:在修改代码前,先通过分析器测量、查看帧预算,识别CPU或GPU哪个是瓶颈。
- 用于应用特定修复方案:对象池、减少绘制调用/批处理、消除每帧分配与GC峰值,以及设置资源预算。
不适用场景:若专门针对物理抖动/穿模/时间步长问题,请使用。若涉及引擎具体的分析器界面和渲染设置,请使用对应引擎的技能(涵盖部分构建设置;引擎核心技能涵盖其余内容)。本技能提供的是跨引擎的通用方法和共享修复方案。
physics-tuninggodot-exportThe golden rule: measure first, never guess
黄金法则:先测量,切勿猜测
Most performance "fixes" applied without profiling target the wrong thing and add complexity for
no gain. Do not optimize code you have not measured. Open the profiler, find the single
biggest cost in a representative scene on representative hardware, and fix that. Re-measure to
confirm the fix helped before moving on. Profile a release/optimized build where it matters —
editor and debug builds lie (editor overhead, no compiler optimization).
大多数未经过分析就实施的性能“修复”都针对了错误的目标,徒增复杂度却毫无收益。切勿优化未经过测量的代码。打开分析器,在代表性场景和硬件上找到最大的性能开销点,然后修复该问题。在进行下一步之前,重新测量以确认修复有效。在关键环境下分析发布/优化版本——编辑器和调试版本的数据并不准确(存在编辑器开销、无编译器优化)。
Core workflow
核心工作流程
- Define the target and reproduce. State the goal (e.g. 60 FPS = 16.67 ms/frame) and find a repeatable worst-case scene. "Sometimes slow" is unfixable; a reproducible spike is fixable.
- Profile before touching code. Run the engine profiler and read the frame: total frame time, and the split between CPU (game logic, physics, scripts) and GPU (rendering).
- Find the bottleneck — CPU or GPU. If GPU time ≫ CPU, attack draw calls/overdraw/shaders/ resolution. If CPU time dominates, attack scripts/physics/allocations. Fixing the wrong side does nothing.
- Fix the single biggest cost. Prefer an algorithmic win (do less work, cache, spatial partition, run less often) over micro-optimizing a hot line. Apply the matching shared fix (pooling, batching, allocation removal).
- Re-measure on the same scene/hardware. Confirm the number moved. Keep or revert based on data, not intuition.
- Set budgets so it stays fixed. Per-frame ms budgets per subsystem, plus asset budgets (texture sizes, triangle counts, draw-call ceilings); add a perf check to verification.
- Report measured numbers. State before/after frame time, the bottleneck found, and the fix — never "should be faster". If you could only measure in-editor, say so.
- 定义目标并复现问题。明确目标(例如60 FPS = 16.67毫秒/帧),找到可重复的最坏场景。“有时卡顿”无法修复;可复现的峰值问题才能够解决。
- 修改代码前先分析。运行引擎分析器,查看帧数据:总帧时间,以及CPU(游戏逻辑、物理、脚本)与GPU(渲染)的时间分配。
- 找到瓶颈——CPU还是GPU。如果GPU时间远大于CPU时间,重点优化绘制调用/过度绘制/着色器/分辨率。如果CPU时间占主导,重点优化脚本/物理/内存分配。修复错误的瓶颈不会带来任何改善。
- 修复最大的性能开销点。优先选择算法层面的优化(减少工作量、缓存、空间分区、降低运行频率),而非对热点代码进行微优化。应用对应的通用修复方案(对象池、批处理、消除分配)。
- 在同一场景/硬件上重新测量。确认数据有所改善。基于数据决定保留或回滚修复,而非凭直觉判断。
- 设置预算以维持性能。为每个子系统设置每帧毫秒预算,以及资源预算(纹理尺寸、三角形数量、绘制调用上限);在验证环节添加性能检查。
- 报告测量数据。说明修复前后的帧时间、找到的瓶颈以及修复方案——切勿只说“应该更快了”。如果只能在编辑器中测量,请注明这一点。
Patterns
模式
1. Frame budget math (turn "feels slow" into a number)
1. 帧预算计算(将“感觉卡顿”转化为具体数值)
text
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.text
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. Measure with the engine profiler (do this before any fix)
2. 使用引擎分析器进行测量(修复前必做)
text
Godot 4.x : 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 : 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.text
Godot 4.x : 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 : 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.
Read the split: is the Draw/GPU line the biggest, or the Game/CPU line? That decides the fix.
undefinedundefined3. Object pooling (stop allocating/freeing in hot loops)
3. 对象池(避免在热点循环中分配/释放内存)
gdscript
undefinedgdscript
undefinedBullets, particles, enemies, damage numbers: reuse a fixed set instead of instantiate()/free()
Bullets, particles, enemies, damage numbers: reuse a fixed set instead of instantiate()/free()
every frame — that thrashes memory and (in C#) feeds the GC.
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
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.
RIGHT: pre-warm the pool at load; reuse. WRONG: instantiate()/queue_free() per shot.
undefinedundefined4. Cut draw calls (the most common GPU-side win)
4. 减少绘制调用(最常见的GPU端优化)
text
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.text
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. Kill per-frame allocations (GC spikes = stutter)
5. 消除每帧内存分配(GC峰值 = 卡顿)
csharp
// Unity 6 (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.csharp
// Unity 6 (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.Pitfalls
常见陷阱
- Optimizing without profiling. The intuitive culprit is usually wrong. Measure first, every time.
- Profiling the editor / a debug build. Editor overhead and unoptimized code mislead. Profile a release build on target hardware for real numbers.
- Fixing the wrong side. Micro-optimizing CPU code when the GPU is the bottleneck (or vice versa) changes nothing. Check the CPU-vs-GPU split first.
- Micro-optimizing over algorithm. Shaving a function when an O(n²) loop or a per-frame full-scene query is the real cost. Reduce the work, don't polish it.
- Instantiate/free in hot loops. Spawning and destroying bullets/particles every frame causes fragmentation and GC spikes. Pool them.
- Per-frame allocations / LINQ / boxing in (C#) feed the GC → periodic hitches. Cache and reuse.
Update - Draw-call explosion from unique materials and unbatched sprites/meshes. Atlas, share materials, instance, batch.
- Overdraw from stacked transparents/particles/full-screen effects re-shading pixels.
- No budgets. Without per-subsystem ms and asset ceilings, performance silently regresses; enforce them in your build/CI checks.
- Optimizing too early. Don't contort a prototype for performance before it's fun or measured.
- 未分析就优化。直觉中的问题根源通常是错误的。每次优化前都要先测量。
- 分析编辑器/调试版本。编辑器开销和未优化的代码会误导结果。在目标硬件上分析发布版本以获取真实数据。
- 修复错误的瓶颈。当GPU是瓶颈时微优化CPU代码(反之亦然)不会有任何改变。先检查CPU与GPU的时间分配。
- 微优化优先于算法优化。当O(n²)循环或每帧全场景查询才是真正的性能开销时,对某个函数进行微调毫无意义。减少工作量,而非优化细节。
- 在热点循环中实例化/释放对象。每帧生成和销毁子弹/粒子会导致内存碎片化和GC峰值。使用对象池复用它们。
- 在中进行每帧分配 / 使用LINQ / 装箱操作(C#)会触发GC → 周期性卡顿。缓存并复用资源。
Update - 独特材质和未批处理的精灵/网格导致绘制调用激增。使用图集、共享材质、实例化、批处理。
- 堆叠的透明/粒子/全屏效果导致过度绘制,重复着色像素。
- 未设置预算。没有子系统毫秒预算和资源上限,性能会悄然退化;在构建/CI检查中强制执行预算。
- 过早优化。在原型验证其趣味性或进行性能测量前,不要为了性能而扭曲原型设计。
References
参考资料
- For per-engine profiler walkthroughs, the CPU-vs-GPU triage flowchart, a complete pooling
manager, batching/instancing rules per engine, allocation/GC guidance, LOD/culling, and asset
budgets (texture sizes, triangle counts, audio, mobile thermals), read
.
references/profiling-and-budgets.md
- 如需各引擎分析器的详细指南、CPU与GPU瓶颈排查流程图、完整的对象池管理器、各引擎的批处理/实例化规则、分配/GC指南、LOD/剔除以及资源预算(纹理尺寸、三角形数量、音频、移动端散热),请阅读。
references/profiling-and-budgets.md
Related skills
相关技能
- — simulation cost, fixed-step budget, sleeping bodies, broadphase layers.
physics-tuning - — release/build settings that affect measured performance.
godot-export - ,
procedural-gen— common CPU hotspots (generation, pathfinding) to budget and defer.game-ai - ,
roguelike,tower-defense— entity-heavy genres that need pooling/budgets.survival-crafting
- —— 模拟成本、固定步长预算、休眠物体、宽相位层。
physics-tuning - —— 影响测量性能的发布/构建设置。
godot-export - ,
procedural-gen—— 常见的CPU热点(生成、寻路),需要进行预算规划和延迟处理。game-ai - ,
roguelike,tower-defense—— 实体密集型游戏类型,需要使用对象池和预算控制。survival-crafting