Skip to content

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.

csharp
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.

csharp
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.

csharp
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:

  1. String building in OnUpdate$"{score}". Use TextComponent.SetInt.
  2. An API that returns a fresh array or list per callTouchscreen.Touches, Gestures.Active, Loc.Available. Each has a buffer-filling counterpart.
  3. foreach over something whose enumerator boxes.
  4. 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.

csharp
Profiler.AllocationBudgetBytesPerFrame = 4096;   // warn above 4 KB/frame

A 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

csharp
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