C#统计耗时的几种方式

Intro

在我们的日常使用中经常会遇到统计某一段代码或者某一个过程的耗时时间,那么常用的方式有哪些呢?

DateTime

在一些代码里有看到这种方式,最简单的使用方式,也是不太精准的一种方式,想要相对准确一些的时间不建议使用,大概代码如下:.

var start = DateTime.Now;

DoWork(); //...

var stop = DateTime.Now;
return stop-start;

Stopwatch

Stopwatch 的使用应该是绝大多数的选择,大多数类库都是使用这种方式,使用起来也比较简单,示例如下:

var watch = Stopwatch.StartNew();

// var watch = new Stopwatch();
// watch.Start();

DoWork();

watch.Stop();
return watch.Elapsed;

它还有一些别的方法比如 Restart/Reset 等,详细可以参考文档:https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.stopwatch?view=net-6.0#methods

ValueStopwatch

.NET Core 内部定义了一个 ValueStopwatch 的结构体来减少使用 Stopwatch 带来的内存分配从而提高性能

ValueStopwatch 定义如下,可以参考:https://github.com/dotnet/aspnetcore/blob/main/src/Shared/ValueStopwatch/ValueStopwatch.cs

internal struct ValueStopwatch
{
    private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;

    private readonly long _startTimestamp;

    public bool IsActive => _startTimestamp != 0;

    private ValueStopwatch(long startTimestamp)
    {
        _startTimestamp = startTimestamp;
    }

    public static ValueStopwatch StartNew() => new ValueStopwatch(Stopwatch.GetTimestamp());

    public TimeSpan GetElapsedTime()
    {
        // Start timestamp can't be zero in an initialized ValueStopwatch. It would have to be literally the first thing executed when the machine boots to be 0.
        // So it being 0 is a clear indication of default(ValueStopwatch)
        if (!IsActive)
        {
            throw new InvalidOperationException("An uninitialized, or 'default', ValueStopwatch cannot be used to get elapsed time.");
        }

        var end = Stopwatch.GetTimestamp();
        var timestampDelta = end - _startTimestamp;
        var ticks = (long)(TimestampToTicks * timestampDelta);
        return new TimeSpan(ticks);
    }
}

使用方式如下:

var watch = ValueStopwatch.StartNew();

DoWork();

return watch.GetElapsedTime();

Stopwatch.GetTimestamp

从前面 ValueStopwatch 的实现代码可以看的出来,主要使用的代码是 Stopwatch.GetTimestamp(),在这个方法的基础上进行的实现,在 .NET 7 中会增加两个 GetElapsedTime 的方法来比较高效地获取耗时时间,使用方式如下:

var start = Stopwatch.GetTimestamp();

DoWork();

return Stopwatch.GetElapsedTime(start);

或者

var start = Stopwatch.GetTimestamp();

DoWork();

var stop = Stopwatch.GetTimestamp();
return Stopwatch.GetElapsedTime(start, stop);

它的实现类似于 ValueStopwatch 的做法,只是我们不需要声明一个结构体,可以直接使用方法即可

详细可以参考 PR:https://github.com/dotnet/runtime/pull/66372

More

目前这个 API 已经合并进入了主分支,应该在 .NET 7 的下一个 preview 我们就可以使用了

如果你现在就想用或者要在低版本代码中使用也可以在自己代码里实现一个帮助类来实现这一功能

public static readonly double TicksPerTimestamp = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;

/// <summary>
/// GetElapsedTime
/// </summary>
/// <param name="startTimestamp">startTimestamp, get by Stopwatch.GetTimestamp()</param>
/// <returns>elapsed timespan</returns>
public static TimeSpan GetElapsedTime(long startTimestamp) =>
    GetElapsedTime(startTimestamp, Stopwatch.GetTimestamp());

/// <summary>
/// GetElapsedTime
/// </summary>
/// <param name="startTimestamp">startTimestamp, get by Stopwatch.GetTimestamp()</param>
/// <param name="endTimestamp">endTimestamp, get by Stopwatch.GetTimestamp</param>
/// <returns>elapsed timespan</returns>
public static TimeSpan GetElapsedTime(long startTimestamp, long endTimestamp)
{
    var ticks = (long)((endTimestamp - startTimestamp) * TicksPerTimestamp);
    return new TimeSpan(ticks);
}