Low-latency latency measurement

Spread the love

Measuring latency is easy with .NET. Just use a Stopwatch!

Stopwatch stopwatch = Stopwatch.StartNew();
// . . . do The Operation(s) here
TimeSpan elapsed = stopwatch.Elapsed;
// elapsed == how long it took to do The Operation(s)

This is certainly the most commonly recommended way to measure operational timings. But is it the fastest way?

It seems almost absurd to ask for a low-latency latency measurement, but good performance discipline requires us to measure. As they saying goes, “one test is worth a thousand expert opinions.” So let’s come up with a benchmark:

[SimpleJob(RuntimeMoniker.NetCoreApp31)]
[MemoryDiagnoser]
public class Benchmarks
{
    [Benchmark]
    public long Watch()
    {
        Stopwatch stopwatch = Stopwatch.StartNew();
        return stopwatch.Elapsed.Ticks;
    }
}

The result:

| Method |     Mean |    Error |   StdDev |  Gen 0 | Gen 1 | Gen 2 | Allocated |
|------- |---------:|---------:|---------:|-------:|------:|------:|----------:|
|  Watch | 55.01 ns | 1.702 ns | 1.421 ns | 0.0051 |     - |     - |      40 B |

It’s pretty fast! There is one problem, though — what are we even supposed to compare this with? Is there another way to measure time that could actually be faster? I see only one realistic optimization opportunity here which is to get rid of the memory allocation penalty. Yes, Stopwatch is reference type (it is stateful after all) and thus requires heap allocation.

The key realization is that calculating elapsed time merely requires subtracting a start time and an end time — two 64-bit integers if we are using ticks. We should in principle be able to do this without allocating any additional memory. It just so happens that Stopwatch is still our friend in this journey, as it provides a GetTimestamp static method. The only problem is that it returns a value in units of timer ticks. Ah, but look, it also has a Frequency field which gives the conversion between timer ticks and seconds. These insights give us the blueprint for a new struct type which marks points in time:

public readonly struct TimePoint
{
    private static readonly double TicksPerTimerTick = 10000000.0 / Stopwatch.Frequency;

    private readonly long timerTicks;

    private TimePoint(long timerTicks)
    {
        this.timerTicks = timerTicks;
    }

    public static TimeSpan operator -(TimePoint x, TimePoint y)
    {
        long deltaTimerTicks = x.timerTicks - y.timerTicks;
        double deltaTicks = TicksPerTimerTick * deltaTimerTicks;
        return new TimeSpan((long)deltaTicks);
    }

    public static TimePoint Now() => new TimePoint(Stopwatch.GetTimestamp());

    public TimeSpan Elapsed() => Now() - this;
}

At last, we have a comparison benchmark for a maybe-faster latency tracker:

[Benchmark]
public long Point()
{
    TimePoint start = TimePoint.Now();
    return start.Elapsed().Ticks;
}

The results:

| Method |     Mean |    Error |   StdDev |  Gen 0 | Gen 1 | Gen 2 | Allocated |
|------- |---------:|---------:|---------:|-------:|------:|------:|----------:|
|  Watch | 54.18 ns | 1.512 ns | 1.263 ns | 0.0051 |     - |     - |      40 B |
|  Point | 40.20 ns | 0.417 ns | 0.325 ns |      - |     - |     - |         - |

We managed to get a 25% performance improvement! For now, I’m comfortable calling TimePoint the lowest latency low-latency latency measurement approach. Can you come up with a better way in .NET?

One thought on “Low-latency latency measurement

  1. Pingback: Let’s do DHCP: more diagnostic events – WriteAsync .NET

Leave a Reply

Your email address will not be published. Required fields are marked *