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/

相关文章:

mysql - 如何查找 mysql 上每秒的写入次数?

c# - 关于 Facebook SDK 的问题。 Facebook 图片 URL 每次登录都会更改吗?如何在 Unity C# 中从 Facebook SDK 7.9 获取图像 URL 和 Facebook ID?

c# - 使用 SQL Server Express 在 SqlCmd 中运行 SQL 脚本

c# - 在 JsonConvert.SerializeObject 中自定义标识参数

c# - SSL 连接异常

java - 如何用 Java 编写正确的微基准测试?

c# - 如何为 Winforms RichTextBox 设置交替的线条颜色?

c# - 如何在负载平衡的 Azure 虚拟机上使用 SessionState

c# - .Net 习语是通过经验学到的?

swift - 循环和范围运算符的基准