c# - 尝试使用 BenchmarkDotNet 测试评估两个类

标签 c# .net benchmarking benchmarkdotnet

我试图找出下面这些类中哪一个更快,因此我决定编写一个 BenchmarkDotNet 测试。

public class RequestSignerBenchmark
{
    [Benchmark]
    public void First()
    {
        var signer = RequestSigner2.FromSecret("hidden");

        var timestamp = TimestampProvider.Default.CurrentTimestamp();
        signer.Sign(timestamp, "GET", "products", "");
    }

    [Benchmark]
    public void Second()
    {
        var signer = RequestSigner.FromSecret("hidden");

        var timestamp = TimestampProvider.Default.CurrentTimestamp();
        signer.Sign(timestamp, "GET", "products", "");
    }
}

// Program.cs
BenchmarkRunner.Run<RequestSignerBenchmark>();

我不确定我所做的测试是否正确。从代码来看,First() 不是应该更快吗?如果错误,适当的测试会是什么样子?

| Method |     Mean |     Error |    StdDev |
|------- |---------:|----------:|----------:|
|  First | 3.234 us | 0.0646 us | 0.1769 us |
| Second | 2.659 us | 0.0516 us | 0.0614 us |

要评估的类

public sealed class RequestSigner
{
    private readonly byte[] _base64Secret;

    public RequestSigner(byte[] base64Secret)
    {
        _base64Secret = base64Secret;
    }

    public static RequestSigner FromSecret(string secret)
    {
        return new RequestSigner(Convert.FromBase64String(secret));
    }

    public string Sign(string timestamp, string method, string requestPath, string body)
    {
        var orig = Encoding.UTF8.GetBytes(timestamp + method.ToUpperInvariant() + requestPath + body);
        using var hmac = new HMACSHA256(_base64Secret);
        return Convert.ToBase64String(hmac.ComputeHash(orig));
    }
}

public class RequestSigner2 : IDisposable
{
    private readonly HMACSHA256 _hmac;

    public RequestSigner2(byte[] base64Secret)
    {
        _hmac = new HMACSHA256(base64Secret);
    }

    public static RequestSigner2 FromSecret(string secret)
    {
        return new RequestSigner2(Convert.FromBase64String(secret));
    }

    public string Sign(string timestamp, string method, string requestPath, string body)
    {
        DoDisposeChecks();

        if (string.IsNullOrWhiteSpace(method) || string.IsNullOrWhiteSpace(requestPath))
        {
            return string.Empty;
        }

        var orig = Encoding.UTF8.GetBytes(timestamp + method.ToUpperInvariant() + requestPath + body);
        return Convert.ToBase64String(_hmac.ComputeHash(orig));
    }

    #region Disposable

    private bool _isDisposed;

    /// <summary>
    ///     Checks if this object has been disposed.
    /// </summary>
    /// <exception cref="ObjectDisposedException">Thrown if the object has been disposed.</exception>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    protected void DoDisposeChecks()
    {
        if (_isDisposed)
        {
            throw new ObjectDisposedException(nameof(RequestSigner2));
        }
    }

    /// <inheritdoc />
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    /// <summary>
    ///     Disposes of managed and unmanaged resources.
    /// </summary>
    /// <param name="disposing">A value indicating whether or not to dispose of managed resources.</param>
    protected virtual void Dispose(bool disposing)
    {
        if (_isDisposed)
        {
            return;
        }

        if (disposing)
        {
            _hmac.Dispose();
        }

        _isDisposed = true;
    }

    #endregion
}

最佳答案

正如我在上面的评论中提到的,我相信你的断言 RequestSigner2实现应该更快“可能”是不正确的,具体取决于您当前的测试结构。

您的 RequestSigner2 中有两个额外的 if 检查如果您不重复使用 HMACSHA256 ,则会对您的性能产​​生影响(我们在这里讨论的是纳秒......)实例正确,但目前您还没有。

通过删除这两项检查,并包装 RequestSigner2 的初始化在 using 语句中,

[Benchmark]
public void First()
{
    using var signer = RequestSigner2.FromSecret("aGlkZGVu");

    var timestamp = DateTime.UtcNow.ToString();
    signer.Sign(timestamp, "GET", "products", "");
}

我能够实现以下目标。

benchmark

请注意,您的“优化”版本实际上多分配了 8 个字节。

我猜测您的假设是重用 HMACSHA256实例比每次调用都初始化它要便宜。但有趣的是,这在您的测试中成本更高。

我相信您打算在实际应用程序代码中重用签名者,但这并未反射(reflect)在您的基准代码中。

您应该考虑初始化 RequestSigner2一旦在 GlobalSetup 步骤中,然后在 GlobalCleanup 步骤中将其丢弃。

[MemoryDiagnoser]
public class RequestSignerBenchmark
{
    private RequestSigner2 _requestSigner2;

    [GlobalSetup]
    public void Setup()
    {
        _requestSigner2 = RequestSigner2.FromSecret("aGlkZGVu");
    }

    [GlobalCleanup]
    public void Cleanup()
    {
        _requestSigner2.Dispose();
    }

    [Benchmark]
    public void First()
    { 
        var timestamp = DateTime.UtcNow.ToString();
        _requestSigner2.Sign(timestamp, "GET", "products", "");
    }

    [Benchmark]
    public void Second()
    {
        var signer = RequestSigner.FromSecret("aGlkZGVu");

        var timestamp = DateTime.UtcNow.ToString();
        signer.Sign(timestamp, "GET", "products", "");
    }
}

(请注意,由于没有时区实现,我不得不更改基准实现的一些部分)。

现在我们得到了您所期望的性能改进,即使重新添加了两个 if 检查!

enter image description here

关于c# - 尝试使用 BenchmarkDotNet 测试评估两个类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73026907/

相关文章:

performance - 如何从 JavaFX 场景中获取 FPS?

java - Guava ImmutableMap 的访问速度明显比 HashMap 慢

c# - 如何将字典的值复制到 .Net 2.0 中的 IList 对象中?

.net - 什么插件/工作台框架是 Eclipse RCP 的最佳 .NET 替代品?

c# - 如何在 C# 中从 SQL Server 验证 DPFP 指纹

c# - HttpContext 和 HttpRequest 之间的区别?

c# - 如何使用 exe 文件附加资源(例如图像)?

php - 如何在通过 PHP 函数连接到 FTP 时测量 PHP 中的 FTP 传输速度

c# - 如何在 EmguCV 中保存具有透明背景的图像

c# - 基类继承另一个基类?