aspnetcore
aspnetcore copied to clipboard
Support STJ Polymorphism
Overview
Contributes to #44852
This PR change RDF
and JsonOutputFormatter
to, when a JsonPolymorphismOptions
is detected, uses the declared type's JsonTypeInfo to call the serializer.
⚠️ I will create a follow up PR to add STJ Polymorphism support for Http Results. ⚠️ AOT/Linker-friendly support is not cover in this PR
Benchmark results
MapXXX
Summary:
- Similar or slight better RPS
- ~ 6% less Total Allocated bytes (when using
JsonTypeInfo
) -
JsonDerived
faster than runtime type usage.
Fast path
Sample
app.MapGet("/json", () => new { Text = "teste" });
Results
application | main | PR | |
---|---|---|---|
CPU Usage (%) | 83 | 83 | 0.00% |
Cores usage (%) | 998 | 997 | -0.10% |
Working Set (MB) | 193 | 195 | +1.04% |
Private Memory (MB) | 577 | 595 | +3.12% |
Start Time (ms) | 89 | 88 | -1.12% |
Max CPU Usage (%) | 83 | 83 | +0.35% |
Max Working Set (MB) | 202 | 204 | +0.56% |
Max GC Heap Size (MB) | 96 | 94 | -1.72% |
Size of committed memory by the GC (MB) | 127 | 128 | +1.18% |
Max Number of Gen 0 GCs / sec | 2.00 | 2.00 | 0.00% |
Max Number of Gen 1 GCs / sec | 1.00 | 1.00 | 0.00% |
Max Number of Gen 2 GCs / sec | 1.00 | 1.00 | 0.00% |
Max Time in GC (%) | 1.00 | 0.00 | |
Max Gen 0 Size (B) | 584 | 584 | 0.00% |
Max Gen 1 Size (B) | 3,661,672 | 3,715,352 | +1.47% |
Max Gen 2 Size (B) | 3,667,440 | 3,729,760 | +1.70% |
Max LOH Size (B) | 321,472 | 321,472 | 0.00% |
Max POH Size (B) | 1,186,016 | 1,363,176 | +14.94% |
Total Allocated Bytes | 4,411,891,376 | 4,116,792,736 | -6.69% |
Max GC Heap Fragmentation | 4 | 4 | -4.43% |
# of Assemblies Loaded | 86 | 86 | 0.00% |
Max Exceptions (#/s) | 298 | 352 | +18.12% |
Max Lock Contention (#/s) | 26 | 27 | +3.85% |
Max ThreadPool Threads Count | 23 | 25 | +8.70% |
Max ThreadPool Queue Length | 152 | 233 | +53.29% |
Max ThreadPool Items (#/s) | 767,649 | 772,454 | +0.63% |
Max Active Timers | 1 | 1 | 0.00% |
IL Jitted (B) | 222,856 | 223,357 | +0.22% |
Methods Jitted | 2,344 | 2,346 | +0.09% |
load | main | PR | |
---|---|---|---|
CPU Usage (%) | 91 | 91 | 0.00% |
Cores usage (%) | 1,097 | 1,096 | -0.09% |
Working Set (MB) | 119 | 130 | +9.24% |
Private Memory (MB) | 358 | 358 | 0.00% |
Start Time (ms) | 0 | 0 | |
First Request (ms) | 141 | 137 | -2.84% |
Requests/sec | 583,933 | 583,921 | -0.00% |
Requests | 8,814,217 | 8,815,645 | +0.02% |
Mean latency (ms) | 0.42 | 0.42 | -0.19% |
Max latency (ms) | 14.94 | 15.20 | +1.74% |
Bad responses | 0 | 0 | |
Socket errors | 0 | 0 | |
Read throughput (MB/s) | 114.72 | 114.72 | 0.00% |
Latency 50th (ms) | 0.37 | 0.37 | 0.00% |
Latency 75th (ms) | 0.48 | 0.48 | 0.00% |
Latency 90th (ms) | 0.65 | 0.65 | +0.15% |
Latency 99th (ms) | 1.46 | 1.46 | 0.00% |
Runtime type check + TypeInfo serializer
Sample
app.MapGet("/json", () => new Message { Text = "teste" });
class Message : BaseMessage { }
class BaseMessage
{
public string Text { get; set; }
}
Results
application | main | PR | |
---|---|---|---|
CPU Usage (%) | 83 | 84 | +1.20% |
Cores usage (%) | 998 | 1,008 | +1.00% |
Working Set (MB) | 193 | 195 | +1.04% |
Private Memory (MB) | 585 | 579 | -1.03% |
Start Time (ms) | 88 | 88 | 0.00% |
Max CPU Usage (%) | 83 | 84 | +0.65% |
Max Working Set (MB) | 205 | 204 | -0.56% |
Max GC Heap Size (MB) | 95 | 94 | -0.25% |
Size of committed memory by the GC (MB) | 130 | 131 | +1.33% |
Max Number of Gen 0 GCs / sec | 2.00 | 2.00 | 0.00% |
Max Number of Gen 1 GCs / sec | 1.00 | 1.00 | 0.00% |
Max Number of Gen 2 GCs / sec | 1.00 | 1.00 | 0.00% |
Max Time in GC (%) | 0.00 | 0.00 | |
Max Gen 0 Size (B) | 26,712 | 584 | -97.81% |
Max Gen 1 Size (B) | 3,450,208 | 3,651,432 | +5.83% |
Max Gen 2 Size (B) | 3,744,864 | 3,623,080 | -3.25% |
Max LOH Size (B) | 321,472 | 321,472 | 0.00% |
Max POH Size (B) | 1,186,016 | 1,186,016 | 0.00% |
Total Allocated Bytes | 4,398,211,880 | 4,126,395,960 | -6.18% |
Max GC Heap Fragmentation | 4 | 4 | +1.91% |
# of Assemblies Loaded | 86 | 86 | 0.00% |
Max Exceptions (#/s) | 348 | 372 | +6.90% |
Max Lock Contention (#/s) | 22 | 31 | +40.91% |
Max ThreadPool Threads Count | 24 | 23 | -4.17% |
Max ThreadPool Queue Length | 181 | 174 | -3.87% |
Max ThreadPool Items (#/s) | 770,007 | 784,509 | +1.88% |
Max Active Timers | 1 | 1 | 0.00% |
IL Jitted (B) | 222,571 | 223,284 | +0.32% |
Methods Jitted | 2,348 | 2,349 | +0.04% |
load | main | PR | |
---|---|---|---|
CPU Usage (%) | 91 | 92 | +1.10% |
Cores usage (%) | 1,097 | 1,102 | +0.46% |
Working Set (MB) | 120 | 119 | -0.83% |
Private Memory (MB) | 358 | 358 | 0.00% |
Start Time (ms) | 0 | 0 | |
First Request (ms) | 138 | 144 | +4.35% |
Requests/sec | 583,081 | 584,599 | +0.26% |
Requests | 8,804,383 | 8,826,007 | +0.25% |
Mean latency (ms) | 0.42 | 0.42 | +0.66% |
Max latency (ms) | 13.72 | 14.22 | +3.64% |
Bad responses | 0 | 0 | |
Socket errors | 0 | 0 | |
Read throughput (MB/s) | 114.55 | 114.85 | +0.26% |
Latency 50th (ms) | 0.37 | 0.37 | -0.81% |
Latency 75th (ms) | 0.48 | 0.48 | +0.21% |
Latency 90th (ms) | 0.64 | 0.66 | +2.34% |
Latency 99th (ms) | 1.41 | 1.42 | +0.71% |
Polymorphic
Sample
app.MapGet("/json", BaseMessage () => new Message { Text = "teste" });
class Message : BaseMessage { }
[JsonDerivedType(typeof(Message))]
class BaseMessage
{
public string Text { get; set; }
}
Results
application | main | PR | |
---|---|---|---|
CPU Usage (%) | 84 | 84 | 0.00% |
Cores usage (%) | 1,003 | 1,003 | 0.00% |
Working Set (MB) | 193 | 196 | +1.55% |
Private Memory (MB) | 576 | 588 | +2.08% |
Start Time (ms) | 89 | 88 | -1.12% |
Max CPU Usage (%) | 83 | 84 | +0.85% |
Max Working Set (MB) | 201 | 205 | +2.22% |
Max GC Heap Size (MB) | 96 | 93 | -2.86% |
Size of committed memory by the GC (MB) | 128 | 134 | +4.79% |
Max Number of Gen 0 GCs / sec | 2.00 | 2.00 | 0.00% |
Max Number of Gen 1 GCs / sec | 1.00 | 1.00 | 0.00% |
Max Number of Gen 2 GCs / sec | 1.00 | 1.00 | 0.00% |
Max Time in GC (%) | 0.00 | 0.00 | |
Max Gen 0 Size (B) | 584 | 584 | 0.00% |
Max Gen 1 Size (B) | 3,572,664 | 3,662,304 | +2.51% |
Max Gen 2 Size (B) | 3,660,960 | 3,667,616 | +0.18% |
Max LOH Size (B) | 321,472 | 321,472 | 0.00% |
Max POH Size (B) | 1,186,016 | 1,186,016 | 0.00% |
Total Allocated Bytes | 4,381,664,640 | 4,132,810,160 | -5.68% |
Max GC Heap Fragmentation | 4 | 4 | +0.31% |
# of Assemblies Loaded | 86 | 86 | 0.00% |
Max Exceptions (#/s) | 275 | 314 | +14.18% |
Max Lock Contention (#/s) | 24 | 24 | 0.00% |
Max ThreadPool Threads Count | 23 | 24 | +4.35% |
Max ThreadPool Queue Length | 113 | 254 | +124.78% |
Max ThreadPool Items (#/s) | 782,049 | 786,959 | +0.63% |
Max Active Timers | 1 | 1 | 0.00% |
IL Jitted (B) | 222,943 | 223,189 | +0.11% |
Methods Jitted | 2,350 | 2,347 | -0.13% |
load | main | PR | |
---|---|---|---|
CPU Usage (%) | 92 | 92 | 0.00% |
Cores usage (%) | 1,103 | 1,099 | -0.36% |
Working Set (MB) | 120 | 119 | -0.83% |
Private Memory (MB) | 358 | 358 | 0.00% |
Start Time (ms) | 0 | 0 | |
First Request (ms) | 137 | 144 | +5.11% |
Requests/sec | 583,935 | 585,208 | +0.22% |
Requests | 8,817,410 | 8,836,006 | +0.21% |
Mean latency (ms) | 0.42 | 0.42 | +1.02% |
Max latency (ms) | 13.96 | 14.25 | +2.08% |
Bad responses | 0 | 0 | |
Socket errors | 0 | 0 | |
Read throughput (MB/s) | 114.72 | 114.97 | +0.22% |
Latency 50th (ms) | 0.36 | 0.36 | -0.27% |
Latency 75th (ms) | 0.49 | 0.49 | 0.00% |
Latency 90th (ms) | 0.66 | 0.67 | +0.45% |
Latency 99th (ms) | 1.40 | 1.45 | +3.57% |
Controllers
Summary:
- Improvements depends on untyped
JsonTypeInfo
new apis - Additional call to
GetTypeInfo
will happen.
Controllers
Sample
public BaseMessage Json()
{
return new Message { Text = "Hello, World!" };
}
public class BaseMessage { public string Text { get; set; } }
public class Message : BaseMessage {}
Results
application | main | PR | |
---|---|---|---|
CPU Usage (%) | 95 | 93 | -2.11% |
Cores usage (%) | 1,137 | 1,118 | -1.67% |
Working Set (MB) | 178 | 173 | -2.81% |
Private Memory (MB) | 166 | 163 | -1.81% |
Start Time (ms) | 224 | 228 | +1.79% |
Max CPU Usage (%) | 99 | 96 | -3.63% |
Max Working Set (MB) | 188 | 181 | -3.86% |
Max GC Heap Size (MB) | 101 | 95 | -6.32% |
Size of committed memory by the GC (MB) | 122 | 115 | -5.52% |
Max Number of Gen 0 GCs / sec | 9.00 | 9.00 | 0.00% |
Max Number of Gen 1 GCs / sec | 7.00 | 2.00 | -71.43% |
Max Number of Gen 2 GCs / sec | 1.00 | 1.00 | 0.00% |
Max Time in GC (%) | 1.00 | 1.00 | 0.00% |
Max Gen 0 Size (B) | 3,422,168 | 3,605,624 | +5.36% |
Max Gen 1 Size (B) | 6,647,576 | 6,570,280 | -1.16% |
Max Gen 2 Size (B) | 4,542,904 | 5,088,000 | +12.00% |
Max LOH Size (B) | 424,440 | 424,440 | 0.00% |
Max POH Size (B) | 1,251,320 | 1,251,320 | 0.00% |
Total Allocated Bytes | 19,079,922,016 | 19,006,488,720 | -0.38% |
Max GC Heap Fragmentation | 46 | 44 | -4.13% |
# of Assemblies Loaded | 111 | 111 | 0.00% |
Max Exceptions (#/s) | 403 | 370 | -8.19% |
Max Lock Contention (#/s) | 19 | 9 | -52.63% |
Max ThreadPool Threads Count | 23 | 23 | 0.00% |
Max ThreadPool Queue Length | 204 | 205 | +0.49% |
Max ThreadPool Items (#/s) | 641,942 | 631,171 | -1.68% |
Max Active Timers | 0 | 0 | |
IL Jitted (B) | 268,073 | 267,888 | -0.07% |
Methods Jitted | 3,389 | 3,387 | -0.06% |
load | main | PR | |
---|---|---|---|
CPU Usage (%) | 50 | 50 | 0.00% |
Cores usage (%) | 600 | 597 | -0.50% |
Working Set (MB) | 119 | 119 | 0.00% |
Private Memory (MB) | 358 | 358 | 0.00% |
Start Time (ms) | 0 | 0 | |
First Request (ms) | 141 | 141 | 0.00% |
Requests/sec | 239,383 | 237,996 | -0.58% |
Requests | 3,614,549 | 3,593,534 | -0.58% |
Mean latency (ms) | 1.47 | 1.43 | -2.72% |
Max latency (ms) | 185.26 | 195.49 | +5.52% |
Bad responses | 0 | 0 | |
Socket errors | 0 | 0 | |
Read throughput (MB/s) | 41.78 | 41.54 | -0.57% |
Latency 50th (ms) | 1.02 | 1.03 | +0.98% |
Latency 75th (ms) | 1.07 | 1.08 | +0.93% |
Latency 90th (ms) | 1.14 | 1.14 | 0.00% |
Latency 99th (ms) | 14.45 | 6.97 | -51.76% |
/benchmark
Crank Pull Request Bot
/benchmark <benchmark[,...]> <profile[,...]> <component,[...]> <arguments>
Benchmarks:
-
plaintext
: TechEmpower Plaintext Scenario - ASP.NET Platform implementation -
json
: TechEmpower JSON Scenario - ASP.NET Platform implementation -
fortunes
: TechEmpower Fortunes Scenario - ASP.NET Platform implementation -
yarp
: YARP - http-http with 10 bytes -
mvcjsoninput2k
: Sends 2Kb Json Body to an MVC controller
Profiles:
-
aspnet-perf-lin
: INTEL/Linux 12 Cores -
aspnet-perf-win
: INTEL/Windows 12 Cores -
aspnet-citrine-lin
: INTEL/Linux 28 Cores -
aspnet-citrine-win
: INTEL/Windows 28 Cores -
aspnet-citrine-arm
: ARM/Linux 32 Cores -
aspnet-citrine-amd
: AMD/Linux 48 Cores
Components:
-
kestrel
-
mvc
Arguments: any additional arguments to pass through to crank, e.g. --variable name=value
/benchmark json aspnet-perf-win mvc
Benchmark started for json on aspnet-perf-win with mvc. Logs: link
json - aspnet-perf-win
application | json.base | json.pr | |
---|---|---|---|
CPU Usage (%) | 77 | 77 | 0.00% |
Cores usage (%) | 928 | 928 | 0.00% |
Working Set (MB) | 70 | 71 | +1.43% |
Private Memory (MB) | 65 | 66 | +1.54% |
Build Time (ms) | 1,882 | 3,660 | +94.47% |
Start Time (ms) | 148 | 133 | -10.14% |
Published Size (KB) | 96,997 | 96,997 | 0.00% |
.NET Core SDK Version | 8.0.100-alpha.1.22601.15 | 8.0.100-alpha.1.22601.15 |
load | json.base | json.pr | |
---|---|---|---|
CPU Usage (%) | 100 | 100 | 0.00% |
Cores usage (%) | 1,198 | 1,199 | +0.08% |
Working Set (MB) | 119 | 119 | 0.00% |
Private Memory (MB) | 363 | 363 | 0.00% |
Start Time (ms) | 1 | 0 | |
First Request (ms) | 67 | 68 | +1.49% |
Requests/sec | 680,412 | 666,439 | -2.05% |
Requests | 10,272,871 | 10,060,915 | -2.06% |
Mean latency (ms) | 1.33 | 1.73 | +30.08% |
Max latency (ms) | 36.49 | 81.64 | +123.73% |
Bad responses | 0 | 0 | |
Socket errors | 0 | 0 | |
Read throughput (MB/s) | 94.74 | 92.79 | -2.06% |
Latency 50th (ms) | 0.34 | 0.32 | -5.65% |
Latency 75th (ms) | 1.55 | 1.91 | +23.23% |
Latency 90th (ms) | 3.77 | 5.14 | +36.34% |
Latency 99th (ms) | 10.01 | 14.46 | +44.46% |
Are we calling a new API that isn't public? The source generator needs to copy this pattern as well.
cc @captainsafia
Are we calling a new API that isn't public? The source generator needs to copy this pattern as well.
cc @captainsafia
I just merged #45593 that makes the APIs public.
@eiriktsarpalis I believe I covered almost all the feedback. Do you have any additional concern before I merge it?