Skip to main content

ProfilerMarkers

Profiler markers in one line, with no fields or names to keep up by hand.

Quick start​

Before — Unity APIAfter — FastTools
private static readonly
ProfilerMarker _marker =
new("FlockSimulation.Step");

public void Step()
{
using var _ = _marker.Auto();
Integrate();
}
public void Step()
{
using var _ = this.Marker();
Integrate();
}

Marker()​

The marker name is built from the type, the method and the call's line number:

Where this.Marker() is calledMarker name
void Step()FlockSimulation.Step (line)
FlockSimulation()FlockSimulation.Ctor (line)
float Speed { get; }FlockSimulation.Speed (line)
Agent this[int i] { get; }FlockSimulation.Indexer (line)
event Action ChangedFlockSimulation.Changed (line)
class FlockSimulation.Agent { void Move() }Agent.Move (line)
class Worker<T> { void Run() }Worker<Int32>.Run (line)
Worker<Single>.Run (line)
struct Job<T> { void Execute() }Job<T>.Execute (line) for any T
void Run<T>()FlockSimulation.Run (line) for any T

WithName()​

.WithName("Steering") replaces the method in the marker name with its own text: FlockSimulation.Step (5) → FlockSimulation.Steering (5).

public void Step()
{
using var _ = this.Marker();

using (this.Marker().WithName("Steering"))
{
foreach (var agent in _agents)
{
using (this.Marker().WithName("Steering.Agent"))
ComputeSteering(agent);
}
}

using (this.Marker().WithName("Integrate"))
Integrate();
}
note

Only a string literal works: the generator reads the name from the source. With a variable, const, nameof or $"Agent {index}" the method name stays, and the argument is still evaluated on every call.

In the Profiler​

The generator creates one static field per call site, so measuring allocates nothing.

HierarchyFrame 1284

Limitations​

  • The line number in the name changes when the call moves: compare captures from before and after an edit by the name without it.
  • Calls without a marker — on an object of another type (other.Marker()), in a static class, in a private or protected nested type: analyzer AFT0010 warns about it.
  • A discarded scope. this.Marker(); without using begins a sample that never ends — analyzer AFT0011 warns about it.
warning

Measurements land in someone else's Profiler row when:

  • this.Marker() calls in different partial files of one type sit on the same line — they share the first one's marker;
  • a call without a marker sits on the same line as a marked call in the type it binds to: for other.Marker() that is the type of other if it is declared in the caller's namespace or an enclosing one, for a call in a nested type — the base type unless it is in the global namespace.

Package sample​

The markers from WithName() run in the ProfilerMarkers scene.

A flock of agents in the ProfilerMarkers sceneA flock of agents in the ProfilerMarkers scene