Closed akamsteeg closed 4 years ago
This looks promising. Apparently we only need to change a single method in HaveIBeenPwnedClient
:
private async Task<T> GetAsync<T>(HttpRequestMessage requestMessage, CancellationToken cancellationToken)
{
Throw.ArgumentNull.WhenNull(requestMessage, nameof(requestMessage));
this.ThrowIfDisposed();
using (var data = await this.GetAsync(requestMessage, cancellationToken).ConfigureAwait(false))
{
var result = await JsonSerializer
.DeserializeAsync<T>(data, cancellationToken: cancellationToken)
.ConfigureAwait(false);
return result;
}
}
The performance gains are interesting. With NewtonSoft.Json:
Method | Toolchain | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
---|---|---|---|---|---|---|---|---|---|---|
GetAllBreachesAsync | .NET Core 2.2 | 74.722 us | 8.1300 us | 0.4456 us | 1.00 | 0.00 | 8.6670 | - | - | 40.18 KB |
GetAllBreachesAsync | .NET Core 3.0 | 66.253 us | 19.7596 us | 1.0831 us | 0.89 | 0.01 | 8.5449 | 0.4883 | - | 39.63 KB |
GetBreachesAsync | .NET Core 2.2 | 11.682 us | 5.4719 us | 0.2999 us | 1.00 | 0.00 | 2.1210 | - | - | 9.84 KB |
GetBreachesAsync | .NET Core 3.0 | 8.118 us | 0.9361 us | 0.0513 us | 0.70 | 0.01 | 2.1057 | - | - | 9.73 KB |
GetBreachesAsync_BreachMode | .NET Core 2.2 | 11.130 us | 4.2680 us | 0.2339 us | 1.00 | 0.00 | 2.1210 | - | - | 9.84 KB |
GetBreachesAsync_BreachMode | .NET Core 3.0 | 8.340 us | 0.1332 us | 0.0073 us | 0.75 | 0.02 | 2.1057 | 0.0153 | - | 9.73 KB |
GetPastesAsync | .NET Core 2.2 | 129.509 us | 114.5536 us | 6.2791 us | 1.00 | 0.00 | 8.3008 | 0.2441 | - | 39.03 KB |
GetPastesAsync | .NET Core 3.0 | 112.601 us | 16.0286 us | 0.8786 us | 0.87 | 0.05 | 8.1787 | 0.4883 | - | 37.92 KB |
IsPwnedPasswordAsync | .NET Core 2.2 | 137.335 us | 129.6631 us | 7.1073 us | 1.00 | 0.00 | 31.9824 | - | - | 148.14 KB |
IsPwnedPasswordAsync | .NET Core 3.0 | 122.137 us | 15.9686 us | 0.8753 us | 0.89 | 0.05 | 31.0059 | 2.9297 | - | 142.75 KB |
With System.Text.Json:
Method | Toolchain | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
---|---|---|---|---|---|---|---|---|---|---|
GetAllBreachesAsync | .NET Core 2.2 | 48.674 us | 1.845 us | 0.1011 us | 1.00 | 0.00 | 5.8594 | - | - | 27.07 KB |
GetAllBreachesAsync | .NET Core 3.0 | 41.674 us | 16.627 us | 0.9114 us | 0.86 | 0.02 | 5.6152 | 0.3052 | - | 25.93 KB |
GetBreachesAsync | .NET Core 2.2 | 10.619 us | 8.765 us | 0.4804 us | 1.00 | 0.00 | 0.9613 | - | - | 4.48 KB |
GetBreachesAsync | .NET Core 3.0 | 9.633 us | 21.701 us | 1.1895 us | 0.91 | 0.08 | 0.9155 | - | - | 4.22 KB |
GetBreachesAsync_BreachMode | .NET Core 2.2 | 10.150 us | 2.370 us | 0.1299 us | 1.00 | 0.00 | 0.9613 | - | - | 4.48 KB |
GetBreachesAsync_BreachMode | .NET Core 3.0 | 8.481 us | 9.697 us | 0.5315 us | 0.84 | 0.04 | 0.9155 | - | - | 4.22 KB |
GetPastesAsync | .NET Core 2.2 | 88.236 us | 27.455 us | 1.5049 us | 1.00 | 0.00 | 6.1035 | - | - | 28.49 KB |
GetPastesAsync | .NET Core 3.0 | 76.838 us | 39.508 us | 2.1655 us | 0.87 | 0.03 | 5.8594 | 0.2441 | - | 27.23 KB |
IsPwnedPasswordAsync | .NET Core 2.2 | 133.719 us | 89.381 us | 4.8993 us | 1.00 | 0.00 | 31.9824 | - | - | 148.14 KB |
IsPwnedPasswordAsync | .NET Core 3.0 | 118.261 us | 12.301 us | 0.6742 us | 0.89 | 0.03 | 31.0059 | 3.0518 | - | 142.75 KB |
The savings on especially memory usage are very impressive. The gains in performance are nice too, but since we're going off-box to a service saving 20 nanoseconds while the call to the service takes easily 100+ milliseconds feels like a bit unimportant. I'll take them though, faster is (almost) always better.
With System.Text.Json
4.7.0 the results are even more impressive:
Method | Toolchain | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
---|---|---|---|---|---|---|---|---|---|---|
GetAllBreachesAsync | .NET Core 2.1 | 42.410 us | 2.8998 us | 0.1589 us | 1.00 | 0.00 | 4.3335 | - | - | 20.17 KB |
GetAllBreachesAsync | .NET Core 3.0 | 34.890 us | 1.9815 us | 0.1086 us | 0.82 | 0.01 | 4.1504 | 0.1831 | - | 19.28 KB |
GetAllBreachesAsync | net472 | 64.528 us | 5.2056 us | 0.2853 us | 1.52 | 0.01 | 4.5166 | 0.1221 | - | 21.28 KB |
GetBreachesAsync | .NET Core 2.1 | 9.258 us | 1.3894 us | 0.0762 us | 1.00 | 0.00 | 0.9155 | - | - | 4.27 KB |
GetBreachesAsync | .NET Core 3.0 | 6.744 us | 0.6732 us | 0.0369 us | 0.73 | 0.01 | 0.8698 | - | - | 4.02 KB |
GetBreachesAsync | net472 | 12.661 us | 3.4387 us | 0.1885 us | 1.37 | 0.01 | 1.2360 | - | - | 5.73 KB |
GetBreachesAsync_BreachMode | .NET Core 2.1 | 11.026 us | 36.6618 us | 2.0096 us | 1.00 | 0.00 | 0.9155 | - | - | 4.27 KB |
GetBreachesAsync_BreachMode | .NET Core 3.0 | 6.693 us | 4.3693 us | 0.2395 us | 0.62 | 0.12 | 0.8698 | - | - | 4.02 KB |
GetBreachesAsync_BreachMode | net472 | 13.597 us | 8.9619 us | 0.4912 us | 1.26 | 0.25 | 1.2360 | - | - | 5.73 KB |
GetPastesAsync | .NET Core 2.1 | 70.643 us | 8.4793 us | 0.4648 us | 1.00 | 0.00 | 4.7607 | - | - | 22.23 KB |
GetPastesAsync | .NET Core 3.0 | 60.057 us | 11.7007 us | 0.6414 us | 0.85 | 0.01 | 4.5166 | 0.2441 | - | 20.97 KB |
GetPastesAsync | net472 | 111.125 us | 9.5911 us | 0.5257 us | 1.57 | 0.01 | 5.0049 | 0.2441 | - | 23.37 KB |
IsPwnedPasswordAsync | .NET Core 2.1 | 77.462 us | 5.3375 us | 0.2926 us | 1.00 | 0.00 | 25.6348 | - | - | 118.14 KB |
IsPwnedPasswordAsync | .NET Core 3.0 | 56.008 us | 3.6827 us | 0.2019 us | 0.72 | 0.00 | 25.2686 | 4.1504 | - | 116.58 KB |
IsPwnedPasswordAsync | net472 | 101.628 us | 17.0611 us | 0.9352 us | 1.31 | 0.02 | 26.6113 | 3.5400 | - | 123.41 KB |
In .NET Core 3.0.0, a new non-allocating JSON reader/writer system is introduced in the System.Text.Json namespace. We should investigate what it takes to replace NewtonSoft.Json with System.Text.Json and measure any performance impact.