Not really a bug per say, but the new GetFromJsonAsync method is ~20% slower than the GetJsonAsync method in Blazor WASM in my (admittedly _extremely_ primitive) perf testing. I was gonna write up a blog post on the perf improvements after 3.2 Preview 3 dropped, but was surprised to see the significant drop in perf. Granted, does it matter most of the time? Probably not, but thought I'd share.

dotnet --info:.NET Core SDK (reflecting any global.json):
Version: 3.1.300-preview-015048
Commit: 13f19b4682
Runtime Environment:
OS Name: Windows
OS Version: 10.0.18362
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\3.1.300-preview-015048\
Host (useful for support):
Version: 3.1.2
Commit: 916b5cba26
.NET Core SDKs installed:
2.1.403 [C:\Program Files\dotnet\sdk]
2.1.802 [C:\Program Files\dotnet\sdk]
2.2.101 [C:\Program Files\dotnet\sdk]
2.2.202 [C:\Program Files\dotnet\sdk]
2.2.300 [C:\Program Files\dotnet\sdk]
2.2.401 [C:\Program Files\dotnet\sdk]
2.2.402 [C:\Program Files\dotnet\sdk]
3.0.100-preview7-012821 [C:\Program Files\dotnet\sdk]
3.0.100 [C:\Program Files\dotnet\sdk]
3.1.200 [C:\Program Files\dotnet\sdk]
3.1.300-preview-015048 [C:\Program Files\dotnet\sdk]
.NET Core runtimes installed:
Microsoft.AspNetCore.All 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.16 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.16 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.0.0-preview7.19365.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.16 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.3 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.8 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.0.0-preview7-27912-14 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.0.0-preview7-27912-14 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.2 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
/cc @Jozkee
As of now, for each call to GetFromJsonAsync, we are creating a new instance of JsonSerializerOptions, now this is bad because internally we cache several things into it.
The first thing that I noticed is the amount of allocations is ridiculously high when calling GetFromJsonAsync compared to the old method, GetJsonAsync, and this is aliviated when you pass an options instance.
private static readonly System.Text.Json.JsonSerializerOptions s_options =
new System.Text.Json.JsonSerializerOptions()
{
PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
};
private async Task RunNewJsonMethod()
{
for (int i = 0; i < iterations; i++)
{
var stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast", s_options);
stopwatch.Stop();
// throw away the first result because it's usually inherently slower than subsequent requests
if (i != 0)
{
newTimes.Add(stopwatch.ElapsedMilliseconds);
}
}
}
@scottsauber can you try above snippet and tell me if you see improvement?
Second thing that I noticed is that when you change the order in that you call the methods, the former called performs slightly worse. I am unaware of the reason of such behavior.
protected override async Task OnInitializedAsync()
{
await RunOldJsonMethod(); // slower
await RunNewJsonMethod(); // faster
loading = false;
}
I will prepare a PR to correct the default JsonSerializerOptions used on the System.Net.Http.Json methods, that will aliviate the allocations and potentially the preformance decrease as well.
Thanks for reporting this.
@Jokzee - Thanks for the really quick reply!
The first snippet didn't seem to noticeably change performance.
I did see the same behavior as you though when I changed the order of the methods, that the 2nd method is always faster.
Even if I change the implementation of the Old and New method to be identical and use the same Json method (doesn't matter if it's GetJsonAsync or GetFromJsonAsync), the second one is always faster.
@adamsitnik might be able to provide some advice for better creating reliable benchmarks in blazor. When I hear things like "second one is always faster" it sounds to me like there isn't control for JIT / warmup in the benchmark.
@ericstj I recently discussed this issue with @sebastienros, and yes, its probably the JIT optimizing out stuff due lack of warmup on the benchmark provided by @scottsauber.
Sebastien walk me through the process of creating benchmarks for this using the https://github.com/aspnet/Benchmarks/tree/master/src/BenchmarksDriver2 suite and there is indeed a regression caused by the lack of a persistent JsonSerializerOptions as described in https://github.com/dotnet/runtime/issues/34440#issuecomment-608316775.
Now I am looking if further optimizations can be made.
Most helpful comment
@ericstj I recently discussed this issue with @sebastienros, and yes, its probably the JIT optimizing out stuff due lack of warmup on the benchmark provided by @scottsauber.
Sebastien walk me through the process of creating benchmarks for this using the https://github.com/aspnet/Benchmarks/tree/master/src/BenchmarksDriver2 suite and there is indeed a regression caused by the lack of a persistent
JsonSerializerOptionsas described in https://github.com/dotnet/runtime/issues/34440#issuecomment-608316775.Now I am looking if further optimizations can be made.