Skip to content

The Profiler

Where your frame time and memory actually go. The profiler is part of the engine — nothing to install, and it works on a phone as well as on your desktop.

Placeholder: the Profiler panel's Timeline view, with the frame graph and per-frame breakdown

Guessing at optimisation is how you spend a week making the wrong thing faster. Open this first.

Getting started

Open the Profiler panel — Windows → Panels → Logging → Profiler. It records continuously; the toolbar button toggles between Recording and Paused — pause it the moment you see the hitch you're chasing, so it doesn't scroll away.

The rest of the toolbar is about keeping results:

ButtonWhat it does
SaveWrites the current capture to a .jcap file
LoadOpens a capture you saved earlier
CompareOpens a capture alongside the current one, so you can see whether a change helped

Compare is the one people underuse. Capture before your change, capture after, compare — that's the difference between knowing and believing.

The views

Pick a view from the dropdown (or the View menu). You won't need all ten; here's what each is for.

Timeline

The default. Frame time over time, with the frame's work broken down. Spikes here are the hitches a player feels. Click one to select that frame and look at it in the other views.

This is also where CPU versus GPU gets settled. If GPU time is the number pinning your frame rate, no amount of script optimisation will help — you need fewer draw calls, fewer or cheaper pixels. If CPU time dominates, the opposite.

Flame Graph

For a selected frame: every function that ran, stacked by who called whom, with width proportional to time. The widest bar at the bottom that you recognise is usually your answer. Click to zoom into a subtree.

Call Tree

The same data as a tree you can expand, and it can be inverted — "which functions cost the most in total", rather than "what did this frame do in order". Inverted call tree is the fastest way to find a cheap function being called far too often.

Statistics

Per-zone totals, averages, and p95/p99 times. Averages hide hitches by design; the 99th percentile is where a stutter that happens once a second lives.

Memory

Usage broken down by category — textures, audio, meshes, scenes, scripts, shaders, UI — with current and peak figures, plus entity pool occupancy. Peak is the number that matters on a phone: it's what the OS reacts to.

Assets

An engine-event track: scene loads, asset packs, texture uploads, file I/O, on the same time axis as everything else. This is the view that answers "why did the frame after the door opened take 40 ms" — because an external profiler doesn't know what an asset is, but this one does.

Histogram

The distribution of frame times. A tall narrow spike at your target frame time is a healthy game; a long tail to the right is a game that mostly runs well and occasionally doesn't.

Sampling

The instrumented views only show code someone marked. The sampler periodically records what's actually running, so it finds hot code nobody thought to instrument — including inside libraries and your own scripts.

Comparison

Two captures side by side. This is what Load and Compare feed.

Analysis

Automatic findings — the profiler pointing at things that look wrong before you've gone looking.

Marking your own code

Wrap anything you want to see by name:

csharp
protected override void OnUpdate(float ts)
{
    using (Profiler.Zone("EnemyAI"))
    {
        UpdateAI(ts);
    }
}

EnemyAI now appears in the flame graph, the call tree and the statistics view. Zones compile out of a Release build, so you can leave them in shipping code at no cost.

Watching allocations

Garbage collection is the classic mobile hitch. The counters are readable from script:

csharp
if (Profiler.AllocatedBytesThisFrame > 0)
    Log.Warn($"allocating {Profiler.AllocatedBytesThisFrame} bytes/frame");

Profiler.AllocationBudgetBytesPerFrame = 4096;   // warn, naming the worst offender

Profiler.GCCount tells you how many collections have happened; how fast it climbs during gameplay is what matters, not the number itself. See Performance for the five habits that get steady-state gameplay to zero bytes per frame.

On the actual device

Desktop numbers do not predict phone numbers. Export a Development build to the device and use the on-screen overlay to watch frame time and memory on the hardware your players have.

Development builds only

Zones, allocation counters and the budget warning are all compiled out of a Release build — which is why you profile a Development build. The Memory counters are the exception: those work everywhere.

A workflow that works

  1. Play until you see the problem, then pause.
  2. Timeline — click the spike. Is it CPU or GPU?
  3. Flame Graph on that frame, or Call Tree inverted if the cost is spread out rather than in one place.
  4. Save the capture before you change anything.
  5. Make one change.
  6. Compare against the saved capture.

One change at a time. Two changes and a faster frame tells you nothing about which one did it.

See also

  • Performance — texture compression, pooling, and allocation discipline
  • Scenes — what a scene costs, and watching it come back on unload