Profiler
Marking your own code so it shows up by name in the profiler, and watching per-frame allocation. For the panel itself, see The Profiler.
Development builds only
Everything on this page compiles out of a Release build. You can leave the calls in shipping code — they cost nothing there. Memory is the exception: those counters work in every build.
Profiler.Zone(string name)
Marks a block of code so it appears by name in the flame graph, the call tree and the statistics view.
protected override void OnUpdate(float ts)
{
using (Profiler.Zone("EnemyAI"))
{
UpdateAI(ts);
}
}Zones nest, so wrapping an outer method and a couple of inner ones gives you a readable tree rather than one opaque bar.
using (Profiler.Zone("Wave"))
{
using (Profiler.Zone("Wave.Spawn")) SpawnEnemies();
using (Profiler.Zone("Wave.Pathing")) UpdatePaths();
}It has to be a using block
Profiler.Zone returns a scope that ends the zone when it's disposed. Calling it without using opens a zone that never closes, which corrupts the rest of the frame's timings rather than failing visibly.
Profiler.AllocatedBytesThisFrame
Bytes the garbage-collected heap grew by during the last completed frame. The number to drive to zero in steady-state gameplay.
if (Profiler.AllocatedBytesThisFrame > 0)
Log.Warn($"allocating {Profiler.AllocatedBytesThisFrame} bytes/frame");A steady non-zero value means something allocates every frame. The usual suspects, in order:
- String building in
OnUpdate—$"{score}". UseTextComponent.SetInt. - An API that returns a fresh array or list per call —
Touchscreen.Touches,Gestures.Active,Loc.Available. Each has a buffer-filling counterpart. foreachover something whose enumerator boxes.- Lambdas that capture a local.
Measured on the main thread, where scripts run. Work you've deliberately moved to another thread isn't counted.
Profiler.GCCount
Collections since startup. The absolute number doesn't matter; how fast it climbs during gameplay does. A counter that's stable while the player stands still and jumps every few seconds during combat tells you exactly where to look.
Profiler.AllocationBudgetBytesPerFrame
Set it, and any frame that allocates more logs a warning naming the worst-offending script. 0 (the default) means no budget is checked.
Profiler.AllocationBudgetBytesPerFrame = 4096; // warn above 4 KB/frameA warning, never an error, and never enforcement — the same discipline Memory.Budget keeps. Naming the responsible script class is usually enough to find the offending line immediately.
Profiler.SampleManagedMemory(int which)
The engine's own per-frame sampler. Not for game code — it's public only because the native side calls it. Use the properties above instead.
A diagnostic you can leave in
using JoystickEngine;
public class AllocationWatch : GameEntity
{
private ulong m_LastGC;
protected override void OnCreate()
{
Profiler.AllocationBudgetBytesPerFrame = 2048;
m_LastGC = Profiler.GCCount;
}
protected override void OnUpdate(float ts)
{
if (Profiler.GCCount != m_LastGC)
{
Log.Warn($"GC during gameplay (frame {Time.FrameCount})");
m_LastGC = Profiler.GCCount;
}
}
}In a Release build every line of that costs nothing.
See also
- The Profiler — the panel, its ten views, and captures
- Performance — the five habits that get you to zero
- Memory — total memory, in every build
- Log — where warnings appear