I'm trying to figure out which one of these classes below is faster, so I decided to write a BenchmarkDotNet test.
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>();
I'm not sure that I'm running that the test I made is correct. Isn't the First()
supposed to be faster judging by the code? If it's wrong, what would an appropriate test would look like?
| Method | Mean | Error | StdDev |
|------- |---------:|----------:|----------:|
| First | 3.234 us | 0.0646 us | 0.1769 us |
| Second | 2.659 us | 0.0516 us | 0.0614 us |
Classes to evaluate
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
}