Profiling & optimization
Optimize by measurement, not guesswork: capture data, find the actual hotspot, fix it, re-measure. The three tool layers are Unreal Insights (deep trace-based profiling), the in-game stat commands (quick triage), and instrumentation macros for your own code. Start with stat commands to identify which thread owns the frame budget, then open Insights for a precise call-by-call view.
When to use this skill
- Frame rate is low or hitching; determining whether the game thread, render thread, or GPU is the bottleneck.
- Adding per-system timing so it appears by name in Insights and
statoutput. - Memory growth, out-of-memory events, or finding which system owns an allocation.
- Deciding which optimization lever to pull after identifying the hotspot.
Triage first: stat commands
In the console (PIE or standalone game):
| Command | What it shows |
|---|---|
stat unit |
Frame / Game / Draw / GPU / RHIT split — the first command to run |
stat fps |
Raw frame rate |
stat game |
Game-thread breakdown by system |
stat gpu |
GPU pass costs (requires -trace=gpu or GPU channel) |
stat scenerendering |
Draw-call counts, visible sections, render overhead |
stat memory |
Per-subsystem memory counters |
stat streaming |
Streaming texture and asset memory |
stat initviews |
Visibility culling cost and visible section count |
stat dumphitches |
Log any hitch above t.HitchFrameTimeThreshold |
stat startfile / stat stopfile |
Capture a .uestats file for legacy Session Frontend |
stat llm |
Low Level Memory Tracker totals |
stat lllmfull |
Expanded LLM counters per tag |
Read stat unit first:
- Game dominates → optimize C++ / Blueprint / tick overhead.
- Draw dominates → reduce draw calls, draw-thread CPU cost.
- GPU dominates → rendering budget, resolution, overdraw, shadows, post-process.
- RHIT (RHI Thread) high → command-list heavy; reduce batching or RHI overhead.
See references/stat-commands.md for the full command table and per-command interpretation guidance.
Unreal Insights — the full profiler
Trace-based, low overhead, works in PIE and packaged builds. Produces .utrace files
opened in Engine\Binaries\Win64\UnrealInsights.exe.
Starting a trace
From the Editor bottom toolbar — click Trace to open the Trace widget; select channels and click Start Trace. From the command line:
MyGame.exe -trace=cpu,gpu,frame,bookmark,log -tracehost=127.0.0.1
Channels of interest:
| Channel | Captures |
|---|---|
cpu |
Named CPU timers (TRACE_CPUPROFILER_EVENT_SCOPE, SCOPE_CYCLE_COUNTER) |
gpu |
Named GPU pass timers |
frame |
Game/render frame markers |
memalloc |
Every allocation + callstack (heavy — Development build only) |
memtag |
LLM tag snapshots per frame (lighter) |
bookmark |
TRACE_BOOKMARK markers |
stats |
Stats-system counters |
log |
UE_LOG output |
Timing Insights window
Opens from the Session Browser. Key views:
- Frames panel — bar chart of per-frame cost; click a spike to zoom in.
- Timing panel — per-thread timeline of named CPU/GPU scopes, nested call hierarchy.
- Timers tab — aggregated total/avg/max per named scope for the selected time range; sort by Inclusive Time to find the heaviest leaf callers.
- Callers/Callees — trace who calls a selected scope and what it calls.
Memory Insights window
Opened from Menu > Memory Insights inside a trace that has memalloc or memtag active.
- Timeline shows total allocated bytes, live allocation count, and LLM-tagged graphs.
- Investigation panel runs queries: Active Alloc, Growth (A→B), Memory Leaks (A→B→C), Short Living, Long Living.
- Results break down by LLM tag, callstack, asset name, or class name.
Full workflow: references/unreal-insights-and-trace.md.
Instrumentation — stat groups and cycle counters
The stat system shows named timers in stat <group> output and in Insights.
// In a .cpp (file scope — single translation unit):
DECLARE_STATS_GROUP(TEXT("MySystem"), STATGROUP_MySystem, STATCAT_Advanced);
DECLARE_CYCLE_STAT(TEXT("MySystem Update"), STAT_MySystemUpdate, STATGROUP_MySystem);
// In the function:
void UMySystem::Update()
{
SCOPE_CYCLE_COUNTER(STAT_MySystemUpdate);
// ... work ...
}
For a stat accessible across multiple files, declare with DECLARE_CYCLE_STAT_EXTERN in
the header and DEFINE_STAT in the .cpp.
QUICK_SCOPE_CYCLE_COUNTER is a one-liner for temporary instrumentation — it creates and
uses a cycle counter in STATGROUP_Quick with no prior declaration:
void AMyActor::Tick(float DeltaTime)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_MyActor_Tick);
// ... work ...
}
Instrumentation — CPU profiler trace scopes
TRACE_CPUPROFILER_EVENT_SCOPE writes directly to the Trace system (cpu channel) and
is visible in the Timing Insights timeline. Lower overhead than the stat system; preferred
for high-frequency scopes.
#include "ProfilingDebugging/CpuProfilerTrace.h"
void AMySystem::Step()
{
TRACE_CPUPROFILER_EVENT_SCOPE(AMySystem::Step);
// ... work ...
}
For a dynamic (runtime-determined) scope name use TRACE_CPUPROFILER_EVENT_SCOPE_TEXT:
TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*DynamicName);
Scope variants (all in CpuProfilerTrace.h:453):
TRACE_CPUPROFILER_EVENT_SCOPE(Name)— literal token, lowest overhead.TRACE_CPUPROFILER_EVENT_SCOPE_STR(NameStr)— const string pointer.TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(Name)— dynamicTCHAR*/FName.TRACE_CPUPROFILER_EVENT_SCOPE_ON_CHANNEL(Name, Channel)— gated on a custom channel.
CPUPROFILERTRACE_ENABLED is 1 in Development/Test builds and 0 in Shipping.
Instrumentation — CSV profiler
CSV_SCOPED_TIMING_STAT records per-frame timings to a CSV file, usable in Shipping and
Test builds. Useful for automated performance regression tests.
#include "ProfilingDebugging/CsvProfiler.h"
CSV_DEFINE_CATEGORY(MySystem, true);
void UMySubsystem::Tick(float DeltaTime)
{
CSV_SCOPED_TIMING_STAT(MySystem, MySubsystem_Tick);
// ... work ...
}
Full reference: references/instrumenting-cpp.md.
Memory profiling
Quick snapshot
memreport -full
Dumps a detailed memory breakdown to Saved/Profiling/MemReports/. Covers pool allocators,
UObject counts, asset memory, texture streaming, etc.
LLM (Low Level Memory Tracker)
LLM instruments every allocation with a tag. Enable with -llm on the command line; view
with stat llm, stat llmfull, or via Insights MemTag channel.
LLM tags are defined in LowLevelMemTracker.h
(Runtime/Core/Public/HAL/LowLevelMemTracker.h). Tags relevant to games: UObject,
RenderTargets, Shaders, Meshes, Audio, Animation.
Insights Memory Insights
Run with -trace=memalloc,memtag,callstack,module (Development build). Open Memory
Insights from the Menu inside a loaded trace. Use "Memory Leaks" query to find allocations
alive across a level transition, or "Growth" to find what grew between two moments.
Full workflow: references/memory-profiling.md.
GPU profiling
stat gpu— per-pass totals in the console.ProfileGPU(console command) / GPU Visualizer (Editor menu) — one-frame GPU breakdown, shows pass tree with ms costs.- Insights GPU track — timeline view of GPU passes alongside CPU work; requires
-trace=gpu.
Common GPU costs to investigate: shadow passes (stat shadowrendering), translucency
overdraw, post-process stack, screen percentage, too many dynamic lights.
Common optimization levers (after measuring)
| Symptom | Lever |
|---|---|
| Game thread high, many ticks | Disable unnecessary ticking; use timers/events (ue-timers-and-async) |
| Blueprint hot path | Move heavy per-frame logic to C++ |
| Draw thread high | Reduce draw calls: ISM/HISM instancing, fewer unique materials, merge meshes (ue-materials-and-shaders, ue-nanite-and-rendering) |
| GPU overdraw / fill rate | Reduce translucency layers, cull small objects, lower screen percentage |
| GC spike | Reduce UObject churn; pool objects; tune GC settings (ue-memory-and-gc) |
| Memory growth | Avoid hard references pulling large assets; stream assets async (ue-asset-management) |
| Streaming hitches | Prestream assets before they're needed; adjust streaming budget |
Gotchas
- Profiling in Development/Editor misleads on absolute timing; use Shipping-config packaged builds for final numbers. Use Development PIE for iteration.
- Confusing CPU- and GPU-bound — fix the wrong side. Always read
stat unitfirst. TRACE_CPUPROFILER_EVENT_SCOPEwith a string literal — use_STRvariant instead; passing a quoted string to the plain macro adds extra quotation marks in the name.DECLARE_CYCLE_STATin a header — thestatic DEFINE_STATit emits causes a linker duplicate if included in multiple TUs. Use the_EXTERN+DEFINE_STATpair.- memalloc tracing at scale — capturing every allocation produces enormous traces;
prefer
memtagfor long sessions and usememalloconly for targeted leak hunts. - Optimizing without measuring — always profile first; fixing a non-bottleneck wastes time and can obscure the real hotspot.
- Stats stripped in Shipping —
STATSis 0 in Shipping; useTRACE_CPUPROFILER_EVENT_SCOPE(disabled byCPUPROFILERTRACE_ENABLED) orCSV_SCOPED_TIMING_STAT(available in Test/Shipping) for production-visible metrics.
Version notes
Stats2.his deprecated in 5.6 and redirects toStats.h; includeStats/Stats.h.CPUPROFILERTRACE_ENABLEDevaluates to 0 in Shipping builds (5.8:CpuProfilerTrace.h:21).- Memory Insights Android callstack support was added in 5.4; available in 5.8.
- Timing Insights gained a Verse Sampling track in 5.7.
References & source material
Engine source (UE 5.8, under Engine/Source/Runtime/Core/Public/):
Stats/Stats.h—DECLARE_STATS_GROUP:209,DECLARE_CYCLE_STAT:130,SCOPE_CYCLE_COUNTER:229,QUICK_SCOPE_CYCLE_COUNTER:226,DECLARE_SCOPE_CYCLE_COUNTER:221,CONDITIONAL_SCOPE_CYCLE_COUNTER:235.Stats/Stats2.h— deprecated header; redirects toStats/Stats.hsince 5.6.ProfilingDebugging/CpuProfilerTrace.h—TRACE_CPUPROFILER_EVENT_SCOPE:453,TRACE_CPUPROFILER_EVENT_SCOPE_STR:408,TRACE_CPUPROFILER_EVENT_SCOPE_TEXT:489,CPUPROFILERTRACE_ENABLED:21,FCpuProfilerTrace::FEventScope:185.ProfilingDebugging/CsvProfiler.h—CSV_SCOPED_TIMING_STAT:120,CSV_DEFINE_CATEGORY:50,CSV_CUSTOM_STAT:155.ProfilingDebugging/ScopedTimers.h—FDurationTimer:31,FScopedDurationTimer:65,FScopedDurationTimeLogger:193.HAL/LowLevelMemTracker.h—ENABLE_LOW_LEVEL_MEM_TRACKER:19, LLM tag scopes.
Official docs (UE 5.8, fetched and confirmed live):
- Unreal Insights — https://dev.epicgames.com/documentation/unreal-engine/unreal-insights-in-unreal-engine
- Trace — https://dev.epicgames.com/documentation/unreal-engine/trace-in-unreal-engine-5
- Timing Insights — https://dev.epicgames.com/documentation/unreal-engine/timing-insights-in-unreal-engine-5
- Memory Insights — https://dev.epicgames.com/documentation/unreal-engine/memory-insights-in-unreal-engine
- Stat Commands — https://dev.epicgames.com/documentation/unreal-engine/stat-commands-in-unreal-engine
- Stats System Overview — https://dev.epicgames.com/documentation/unreal-engine/unreal-engine-stats-system-overview
- Testing and Optimizing — https://dev.epicgames.com/documentation/unreal-engine/testing-and-optimizing-your-content
Deep-dive references in this skill:
- references/unreal-insights-and-trace.md — trace channels, session workflow, Timing Insights anatomy, GPU track.
- references/stat-commands.md — full stat command table,
reading
stat unit, stat groups for custom subsystems. - references/instrumenting-cpp.md — all instrumentation
macros, CSV profiler, cross-file stat patterns,
DEFINE_STATpitfalls. - references/memory-profiling.md — LLM, memreport, Insights Memory Insights workflow, query rules, leak hunting.
Related skills: ue-nanite-and-rendering, ue-materials-and-shaders, ue-timers-and-async,
ue-memory-and-gc, ue-debugging-techniques.