Skip to content

Make ValueStringBuilder available in .NET Core 3.1#8167

Merged
andrewlock merged 1 commit intomasterfrom
andrew/aspnetcore-perf/valuestringbuilder
Feb 11, 2026
Merged

Make ValueStringBuilder available in .NET Core 3.1#8167
andrewlock merged 1 commit intomasterfrom
andrew/aspnetcore-perf/valuestringbuilder

Conversation

@andrewlock
Copy link
Member

@andrewlock andrewlock commented Feb 6, 2026

Summary of changes

Makes ValueStringBuilder usable in .NET Core 3.1+, instead of just .NET 6

Reason for change

I want to use it in some aspnetcore improvements, but currently it's only exposed in .NET 6

Implementation details

Update the #if. Note that I originally made this available in all TFMs, using our vendored spans, but benchmarking showed that this was actively harmful compared to just using StringBuilderCache, so to avoid incorrect use, just restricting it for now.

Test coverage

Updated the tests to cover .NET Core 3.1 too.

Other details

Part of a stack
https://datadoghq.atlassian.net/browse/LANGPLAT-842

@andrewlock andrewlock requested a review from a team as a code owner February 6, 2026 13:46
@andrewlock andrewlock added the type:performance Performance, speed, latency, resource usage (CPU, memory) label Feb 6, 2026
@pr-commenter
Copy link

pr-commenter bot commented Feb 6, 2026

Benchmarks

Benchmark execution time: 2026-02-06 14:28:34

Comparing candidate commit 5c729ed in PR branch andrew/aspnetcore-perf/valuestringbuilder with baseline commit c0dad86 in branch master.

Found 16 performance improvements and 6 performance regressions! Performance is the same for 151 metrics, 19 unstable metrics.

scenario:Benchmarks.Trace.ActivityBenchmark.StartStopWithChild netcoreapp3.1

  • 🟥 throughput [-8000.422op/s; -6090.194op/s] or [-8.096%; -6.163%]

scenario:Benchmarks.Trace.AgentWriterBenchmark.WriteAndFlushEnrichedTraces net6.0

  • 🟥 execution_time [+102.099ms; +102.313ms] or [+100.988%; +101.201%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleSimpleBody netcoreapp3.1

  • 🟥 execution_time [+16.454ms; +21.580ms] or [+8.305%; +10.893%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorMoreComplexBody net6.0

  • 🟩 execution_time [-19.073ms; -13.158ms] or [-8.937%; -6.165%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorSimpleBody netcoreapp3.1

  • 🟥 execution_time [+18.319ms; +24.589ms] or [+9.481%; +12.726%]

scenario:Benchmarks.Trace.CIVisibilityProtocolWriterBenchmark.WriteAndFlushEnrichedTraces net472

  • 🟩 execution_time [-33.962ms; -29.008ms] or [-15.405%; -13.158%]
  • 🟩 throughput [+72.028op/s; +97.058op/s] or [+7.087%; +9.549%]

scenario:Benchmarks.Trace.CIVisibilityProtocolWriterBenchmark.WriteAndFlushEnrichedTraces net6.0

  • 🟥 execution_time [+40.010ms; +42.664ms] or [+21.617%; +23.050%]

scenario:Benchmarks.Trace.CIVisibilityProtocolWriterBenchmark.WriteAndFlushEnrichedTraces netcoreapp3.1

  • 🟩 execution_time [-84.996ms; -83.846ms] or [-38.740%; -38.215%]
  • 🟩 throughput [+268.964op/s; +278.851op/s] or [+19.411%; +20.125%]

scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSlice net472

  • 🟩 execution_time [-139.788µs; -133.132µs] or [-6.854%; -6.527%]
  • 🟩 throughput [+34.249op/s; +36.071op/s] or [+6.986%; +7.357%]

scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSlice netcoreapp3.1

  • 🟩 execution_time [-1004.711µs; -743.899µs] or [-35.044%; -25.947%]

scenario:Benchmarks.Trace.CharSliceBenchmark.OriginalCharSlice net472

  • 🟩 execution_time [-190.605µs; -182.555µs] or [-6.794%; -6.507%]
  • 🟩 throughput [+24.820op/s; +25.981op/s] or [+6.963%; +7.289%]

scenario:Benchmarks.Trace.CharSliceBenchmark.OriginalCharSlice net6.0

  • 🟩 execution_time [-295.279µs; -287.361µs] or [-13.329%; -12.971%]
  • 🟩 throughput [+67.510op/s; +69.170op/s] or [+14.956%; +15.324%]

scenario:Benchmarks.Trace.GraphQLBenchmark.ExecuteAsync net6.0

  • 🟩 throughput [+28829.766op/s; +41470.451op/s] or [+5.742%; +8.260%]

scenario:Benchmarks.Trace.ILoggerBenchmark.EnrichedLog net6.0

  • 🟩 execution_time [-16.433ms; -12.597ms] or [-7.682%; -5.889%]

scenario:Benchmarks.Trace.RedisBenchmark.SendReceive netcoreapp3.1

  • 🟥 throughput [-42222.380op/s; -32243.836op/s] or [-10.410%; -7.950%]

scenario:Benchmarks.Trace.SingleSpanAspNetCoreBenchmark.SingleSpanAspNetCore netcoreapp3.1

  • 🟩 throughput [+15332843.893op/s; +16607836.488op/s] or [+6.811%; +7.377%]

scenario:Benchmarks.Trace.SpanBenchmark.StartFinishTwoScopes net6.0

  • 🟩 execution_time [-21.012ms; -17.035ms] or [-9.648%; -7.822%]

@dd-trace-dotnet-ci-bot
Copy link

Execution-Time Benchmarks Report ⏱️

Execution-time results for samples comparing This PR (8167) and master.

⚠️ Potential regressions detected

FakeDbCommand

Metric Master (Mean ± 95% CI) Current (Mean ± 95% CI) Change Status
.NET Framework 4.8 - Baseline
duration68.30 ± (68.33 - 68.58) ms77.35 ± (77.38 - 77.78) ms+13.2%❌⬆️
.NET Framework 4.8 - Bailout
duration72.05 ± (71.98 - 72.20) ms81.92 ± (81.71 - 82.17) ms+13.7%❌⬆️
.NET Framework 4.8 - CallTarget+Inlining+NGEN
duration1025.15 ± (1027.07 - 1033.25) ms1112.88 ± (1112.39 - 1120.18) ms+8.6%❌⬆️

HttpMessageHandler

Metric Master (Mean ± 95% CI) Current (Mean ± 95% CI) Change Status
.NET Framework 4.8 - Baseline
duration193.79 ± (193.64 - 194.38) ms205.47 ± (205.57 - 206.81) ms+6.0%❌⬆️
.NET Framework 4.8 - Bailout
duration197.11 ± (197.02 - 197.59) ms210.64 ± (210.27 - 211.71) ms+6.9%❌⬆️
Full Metrics Comparison

FakeDbCommand

Metric Master (Mean ± 95% CI) Current (Mean ± 95% CI) Change Status
.NET Framework 4.8 - Baseline
duration68.30 ± (68.33 - 68.58) ms77.35 ± (77.38 - 77.78) ms+13.2%❌⬆️
.NET Framework 4.8 - Bailout
duration72.05 ± (71.98 - 72.20) ms81.92 ± (81.71 - 82.17) ms+13.7%❌⬆️
.NET Framework 4.8 - CallTarget+Inlining+NGEN
duration1025.15 ± (1027.07 - 1033.25) ms1112.88 ± (1112.39 - 1120.18) ms+8.6%❌⬆️
.NET Core 3.1 - Baseline
process.internal_duration_ms22.46 ± (22.44 - 22.48) ms24.67 ± (24.61 - 24.73) ms+9.8%✅⬆️
process.time_to_main_ms86.82 ± (86.67 - 86.96) ms102.04 ± (101.79 - 102.29) ms+17.5%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed15.51 ± (15.51 - 15.52) MB15.51 ± (15.50 - 15.51) MB-0.0%
runtime.dotnet.threads.count12 ± (12 - 12)12 ± (12 - 12)+0.0%
.NET Core 3.1 - Bailout
process.internal_duration_ms22.40 ± (22.37 - 22.43) ms24.49 ± (24.43 - 24.54) ms+9.3%✅⬆️
process.time_to_main_ms87.84 ± (87.73 - 87.95) ms102.53 ± (102.28 - 102.78) ms+16.7%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed15.55 ± (15.55 - 15.56) MB15.55 ± (15.55 - 15.55) MB-0.0%
runtime.dotnet.threads.count13 ± (13 - 13)13 ± (13 - 13)+0.0%
.NET Core 3.1 - CallTarget+Inlining+NGEN
process.internal_duration_ms255.49 ± (251.86 - 259.12) ms284.69 ± (281.38 - 287.99) ms+11.4%✅⬆️
process.time_to_main_ms503.66 ± (503.12 - 504.20) ms558.12 ± (557.29 - 558.95) ms+10.8%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed53.23 ± (53.20 - 53.25) MB53.16 ± (53.14 - 53.18) MB-0.1%
runtime.dotnet.threads.count28 ± (28 - 28)28 ± (28 - 28)+0.1%✅⬆️
.NET 6 - Baseline
process.internal_duration_ms21.04 ± (21.01 - 21.06) ms22.94 ± (22.88 - 23.00) ms+9.0%✅⬆️
process.time_to_main_ms75.06 ± (74.92 - 75.21) ms87.23 ± (87.00 - 87.47) ms+16.2%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed15.23 ± (15.23 - 15.23) MB15.24 ± (15.24 - 15.24) MB+0.1%✅⬆️
runtime.dotnet.threads.count10 ± (10 - 10)10 ± (10 - 10)+0.0%
.NET 6 - Bailout
process.internal_duration_ms20.87 ± (20.84 - 20.90) ms23.15 ± (23.09 - 23.22) ms+10.9%✅⬆️
process.time_to_main_ms75.77 ± (75.68 - 75.86) ms90.31 ± (90.09 - 90.54) ms+19.2%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed15.36 ± (15.36 - 15.37) MB15.35 ± (15.34 - 15.35) MB-0.1%
runtime.dotnet.threads.count11 ± (11 - 11)11 ± (11 - 11)+0.0%
.NET 6 - CallTarget+Inlining+NGEN
process.internal_duration_ms253.80 ± (252.89 - 254.71) ms278.15 ± (276.89 - 279.41) ms+9.6%✅⬆️
process.time_to_main_ms481.12 ± (480.50 - 481.75) ms530.38 ± (529.63 - 531.13) ms+10.2%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed53.98 ± (53.95 - 54.01) MB53.89 ± (53.87 - 53.92) MB-0.2%
runtime.dotnet.threads.count28 ± (28 - 28)28 ± (28 - 28)+0.0%
.NET 8 - Baseline
process.internal_duration_ms19.24 ± (19.21 - 19.26) ms21.09 ± (21.03 - 21.14) ms+9.6%✅⬆️
process.time_to_main_ms73.87 ± (73.76 - 73.98) ms86.11 ± (85.89 - 86.33) ms+16.6%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed12.27 ± (12.27 - 12.28) MB12.27 ± (12.26 - 12.28) MB-0.0%
runtime.dotnet.threads.count10 ± (10 - 10)10 ± (10 - 10)+0.0%
.NET 8 - Bailout
process.internal_duration_ms19.20 ± (19.17 - 19.23) ms20.94 ± (20.88 - 21.01) ms+9.1%✅⬆️
process.time_to_main_ms75.08 ± (75.00 - 75.16) ms86.77 ± (86.56 - 86.97) ms+15.6%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed12.34 ± (12.33 - 12.35) MB12.32 ± (12.31 - 12.33) MB-0.2%
runtime.dotnet.threads.count11 ± (11 - 11)11 ± (11 - 11)+0.0%
.NET 8 - CallTarget+Inlining+NGEN
process.internal_duration_ms181.20 ± (180.26 - 182.14) ms202.54 ± (201.81 - 203.27) ms+11.8%✅⬆️
process.time_to_main_ms461.65 ± (461.02 - 462.29) ms510.52 ± (509.56 - 511.48) ms+10.6%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed41.23 ± (41.20 - 41.25) MB41.71 ± (41.67 - 41.75) MB+1.2%✅⬆️
runtime.dotnet.threads.count27 ± (27 - 27)27 ± (27 - 27)+0.3%✅⬆️

HttpMessageHandler

Metric Master (Mean ± 95% CI) Current (Mean ± 95% CI) Change Status
.NET Framework 4.8 - Baseline
duration193.79 ± (193.64 - 194.38) ms205.47 ± (205.57 - 206.81) ms+6.0%❌⬆️
.NET Framework 4.8 - Bailout
duration197.11 ± (197.02 - 197.59) ms210.64 ± (210.27 - 211.71) ms+6.9%❌⬆️
.NET Framework 4.8 - CallTarget+Inlining+NGEN
duration1145.42 ± (1145.20 - 1150.97) ms1203.02 ± (1202.23 - 1209.30) ms+5.0%✅⬆️
.NET Core 3.1 - Baseline
process.internal_duration_ms193.39 ± (192.93 - 193.85) ms204.63 ± (203.86 - 205.41) ms+5.8%✅⬆️
process.time_to_main_ms89.41 ± (89.12 - 89.70) ms94.58 ± (94.18 - 94.98) ms+5.8%✅⬆️
runtime.dotnet.exceptions.count3 ± (3 - 3)3 ± (3 - 3)+0.0%
runtime.dotnet.mem.committed20.85 ± (20.83 - 20.88) MB20.66 ± (20.65 - 20.68) MB-0.9%
runtime.dotnet.threads.count20 ± (20 - 20)20 ± (20 - 20)+1.1%✅⬆️
.NET Core 3.1 - Bailout
process.internal_duration_ms191.60 ± (191.32 - 191.88) ms208.92 ± (207.94 - 209.90) ms+9.0%✅⬆️
process.time_to_main_ms90.28 ± (90.12 - 90.45) ms98.97 ± (98.38 - 99.56) ms+9.6%✅⬆️
runtime.dotnet.exceptions.count3 ± (3 - 3)3 ± (3 - 3)+0.0%
runtime.dotnet.mem.committed20.78 ± (20.76 - 20.81) MB20.65 ± (20.64 - 20.67) MB-0.6%
runtime.dotnet.threads.count21 ± (21 - 21)21 ± (21 - 21)+1.6%✅⬆️
.NET Core 3.1 - CallTarget+Inlining+NGEN
process.internal_duration_ms443.88 ± (441.58 - 446.18) ms460.27 ± (457.11 - 463.44) ms+3.7%✅⬆️
process.time_to_main_ms509.20 ± (508.55 - 509.84) ms542.33 ± (541.37 - 543.29) ms+6.5%✅⬆️
runtime.dotnet.exceptions.count3 ± (3 - 3)3 ± (3 - 3)+0.0%
runtime.dotnet.mem.committed63.62 ± (63.51 - 63.72) MB63.04 ± (62.88 - 63.20) MB-0.9%
runtime.dotnet.threads.count29 ± (29 - 29)29 ± (29 - 30)+0.2%✅⬆️
.NET 6 - Baseline
process.internal_duration_ms196.37 ± (196.07 - 196.67) ms210.84 ± (210.03 - 211.65) ms+7.4%✅⬆️
process.time_to_main_ms77.04 ± (76.81 - 77.26) ms83.23 ± (82.92 - 83.54) ms+8.0%✅⬆️
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed20.96 ± (20.93 - 20.99) MB20.76 ± (20.74 - 20.78) MB-0.9%
runtime.dotnet.threads.count19 ± (19 - 19)20 ± (20 - 20)+0.8%✅⬆️
.NET 6 - Bailout
process.internal_duration_ms195.41 ± (195.11 - 195.70) ms210.56 ± (209.85 - 211.27) ms+7.8%✅⬆️
process.time_to_main_ms78.00 ± (77.86 - 78.14) ms83.69 ± (83.37 - 84.00) ms+7.3%✅⬆️
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed21.02 ± (20.99 - 21.05) MB20.88 ± (20.86 - 20.90) MB-0.7%
runtime.dotnet.threads.count20 ± (20 - 21)21 ± (21 - 21)+0.9%✅⬆️
.NET 6 - CallTarget+Inlining+NGEN
process.internal_duration_ms463.92 ± (462.32 - 465.51) ms487.85 ± (485.94 - 489.76) ms+5.2%✅⬆️
process.time_to_main_ms487.27 ± (486.58 - 487.96) ms520.32 ± (519.31 - 521.34) ms+6.8%✅⬆️
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed63.13 ± (63.02 - 63.24) MB62.27 ± (62.20 - 62.35) MB-1.4%
runtime.dotnet.threads.count30 ± (30 - 30)30 ± (30 - 30)+0.6%✅⬆️
.NET 8 - Baseline
process.internal_duration_ms194.33 ± (194.00 - 194.66) ms210.30 ± (209.47 - 211.13) ms+8.2%✅⬆️
process.time_to_main_ms77.18 ± (76.98 - 77.38) ms82.78 ± (82.40 - 83.16) ms+7.3%✅⬆️
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed16.32 ± (16.30 - 16.34) MB16.19 ± (16.17 - 16.21) MB-0.8%
runtime.dotnet.threads.count19 ± (19 - 19)19 ± (19 - 19)+1.0%✅⬆️
.NET 8 - Bailout
process.internal_duration_ms193.13 ± (192.82 - 193.45) ms211.45 ± (210.60 - 212.30) ms+9.5%✅⬆️
process.time_to_main_ms77.91 ± (77.76 - 78.06) ms85.14 ± (84.79 - 85.49) ms+9.3%✅⬆️
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed16.42 ± (16.39 - 16.45) MB16.25 ± (16.23 - 16.27) MB-1.0%
runtime.dotnet.threads.count20 ± (20 - 20)20 ± (20 - 20)+1.2%✅⬆️
.NET 8 - CallTarget+Inlining+NGEN
process.internal_duration_ms371.48 ± (370.24 - 372.72) ms441.74 ± (434.10 - 449.39) ms+18.9%✅⬆️
process.time_to_main_ms468.84 ± (468.24 - 469.44) ms503.72 ± (502.45 - 505.00) ms+7.4%✅⬆️
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed52.98 ± (52.94 - 53.01) MB55.08 ± (55.05 - 55.12) MB+4.0%✅⬆️
runtime.dotnet.threads.count29 ± (29 - 29)29 ± (29 - 29)-1.1%
Comparison explanation

Execution-time benchmarks measure the whole time it takes to execute a program, and are intended to measure the one-off costs. Cases where the execution time results for the PR are worse than latest master results are highlighted in **red**. The following thresholds were used for comparing the execution times:

  • Welch test with statistical test for significance of 5%
  • Only results indicating a difference greater than 5% and 5 ms are considered.

Note that these results are based on a single point-in-time result for each branch. For full results, see the dashboard.

Graphs show the p99 interval based on the mean and StdDev of the test run, as well as the mean value of the run (shown as a diamond below the graph).

Duration charts
FakeDbCommand (.NET Framework 4.8)
gantt
    title Execution time (ms) FakeDbCommand (.NET Framework 4.8)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8167) - mean (78ms)  : 75, 80
    master - mean (68ms)  : 67, 70

    section Bailout
    This PR (8167) - mean (82ms)  : crit, 79, 84
    master - mean (72ms)  : 71, 73

    section CallTarget+Inlining+NGEN
    This PR (8167) - mean (1,116ms)  : crit, 1058, 1175
    master - mean (1,030ms)  : 986, 1074

Loading
FakeDbCommand (.NET Core 3.1)
gantt
    title Execution time (ms) FakeDbCommand (.NET Core 3.1)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8167) - mean (135ms)  : 130, 139
    master - mean (115ms)  : 113, 118

    section Bailout
    This PR (8167) - mean (135ms)  : crit, 130, 140
    master - mean (116ms)  : 114, 118

    section CallTarget+Inlining+NGEN
    This PR (8167) - mean (885ms)  : crit, 839, 931
    master - mean (797ms)  : 742, 852

Loading
FakeDbCommand (.NET 6)
gantt
    title Execution time (ms) FakeDbCommand (.NET 6)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8167) - mean (118ms)  : 113, 123
    master - mean (101ms)  : 99, 104

    section Bailout
    This PR (8167) - mean (121ms)  : crit, 117, 125
    master - mean (102ms)  : 101, 103

    section CallTarget+Inlining+NGEN
    This PR (8167) - mean (846ms)  : crit, 821, 871
    master - mean (776ms)  : 755, 797

Loading
FakeDbCommand (.NET 8)
gantt
    title Execution time (ms) FakeDbCommand (.NET 8)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8167) - mean (116ms)  : 112, 121
    master - mean (100ms)  : 97, 102

    section Bailout
    This PR (8167) - mean (117ms)  : crit, 113, 120
    master - mean (101ms)  : 99, 102

    section CallTarget+Inlining+NGEN
    This PR (8167) - mean (757ms)  : crit, 737, 778
    master - mean (686ms)  : 673, 698

Loading
HttpMessageHandler (.NET Framework 4.8)
gantt
    title Execution time (ms) HttpMessageHandler (.NET Framework 4.8)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8167) - mean (206ms)  : 197, 215
    master - mean (194ms)  : 190, 198

    section Bailout
    This PR (8167) - mean (211ms)  : crit, 201, 221
    master - mean (197ms)  : 195, 200

    section CallTarget+Inlining+NGEN
    This PR (8167) - mean (1,206ms)  : 1155, 1257
    master - mean (1,148ms)  : 1107, 1189

Loading
HttpMessageHandler (.NET Core 3.1)
gantt
    title Execution time (ms) HttpMessageHandler (.NET Core 3.1)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8167) - mean (310ms)  : 294, 327
    master - mean (292ms)  : 284, 300

    section Bailout
    This PR (8167) - mean (319ms)  : crit, 293, 345
    master - mean (291ms)  : 287, 295

    section CallTarget+Inlining+NGEN
    This PR (8167) - mean (1,044ms)  : 998, 1091
    master - mean (991ms)  : 953, 1029

Loading
HttpMessageHandler (.NET 6)
gantt
    title Execution time (ms) HttpMessageHandler (.NET 6)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8167) - mean (305ms)  : 289, 321
    master - mean (282ms)  : 278, 287

    section Bailout
    This PR (8167) - mean (305ms)  : crit, 291, 319
    master - mean (282ms)  : 279, 286

    section CallTarget+Inlining+NGEN
    This PR (8167) - mean (1,053ms)  : crit, 1011, 1096
    master - mean (988ms)  : 950, 1026

Loading
HttpMessageHandler (.NET 8)
gantt
    title Execution time (ms) HttpMessageHandler (.NET 8)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8167) - mean (306ms)  : 287, 326
    master - mean (282ms)  : 277, 287

    section Bailout
    This PR (8167) - mean (309ms)  : crit, 292, 326
    master - mean (281ms)  : 277, 286

    section CallTarget+Inlining+NGEN
    This PR (8167) - mean (993ms)  : crit, 884, 1103
    master - mean (873ms)  : 849, 896

Loading

@NachoEchevarria
Copy link
Collaborator

Summary

Clean, well-scoped PR. The approach of widening the outer #if to NETCOREAPP while adding an inner #if NET6_0_OR_GREATER guard around AppendSpanFormattable (which depends on ISpanFormattable) is correct. The removal of the unused using System.Diagnostics is a nice cleanup. The #if !NET blocks already present inside the file handle API differences for pre-.NET 5 targets, so the code is ready for the wider TFM support.

Only minor observation: the source file uses #if NETCOREAPP while the test file uses the more precise #if NETCOREAPP3_1_OR_GREATER — a small inconsistency, though functionally equivalent given the current TFMs (net461;netstandard2.0;netcoreapp3.1;net6.0). See inline comment for details.

Review by 🤖 Claude Code

@andrewlock andrewlock merged commit 53ab69a into master Feb 11, 2026
144 checks passed
@andrewlock andrewlock deleted the andrew/aspnetcore-perf/valuestringbuilder branch February 11, 2026 10:42
@github-actions github-actions bot added this to the vNext-v3 milestone Feb 11, 2026
andrewlock added a commit that referenced this pull request Feb 18, 2026
## Summary of changes

A few minor improvements to the standard `AspNetCoreDiagnosticObserver`

## Reason for change

Looking into obvious perf improvements for ASP.NET Core, but only a few
minor things stood out (apart from related PRs like #8199 and #8203).

## Implementation details

- Reduce size of MVC tags object by not deriving from `WebTags` (we
never set those tags anyway)
- Delay creating spanlinks collection if we don't need it
- HttpRoute always matches AspNetCoreRoute, so can make it readonly

## Test coverage

Covered by existing tests, benchmarks show (tiny) allocation gains

## Other details


Relates to https://datadoghq.atlassian.net/browse/LANGPLAT-842

Related PRs:
- #8167
- #8170
- #8180
- #8196
- #8199
- #8203
andrewlock added a commit that referenced this pull request Feb 18, 2026
…plate` (#8170)

## Summary of changes

Reduces the allocations produced in
`AspNetCoreResourceNameHelper.SimplifyRouteTemplate()`

## Reason for change

I'm working on perf for aspnetcore in general, and this was an easy
target point, as we know it allocates a bunch of intermediate `string`
currently.

Note that I believe this is generally only used in the pre-endpoint
routing flows, so it will not be commonly used. It doesn't hurt though,
and it was easier to work with, hence it's first up here 😄

## Implementation details

This is mostly inspired/based on the work I did in the single-span
optimized path, but accounts for the fact that
1. We have to explicitly extract `controller`, `action`, and `area` for
tags, so those params are already known
2. Need to not have any breaking changes in this code (that's a subtle
difference compared to the single-span version)

The high-level improvements are:

- Use `ValueStringBuilder` for building up the span (.NET Core 3.1+).
The main benefit that gives is the `AppendAsLowerInvariant` method
- Remove boxing of the enumerator by directly casting to `List<T>` (it's
always that type in all implementations, so this is an easy improvement)
- Use the provided `controller`, `action`, and `area` values
- This makes the code more complicated, so I toyed with ignoring them
and just always doing the route value dictionary lookup, but this
approach came out faster. Not massive though, so if there's objections,
we could simplify it.
- Add a check to `IsIdentifierSegment()` to check for common identifier
types that are always going to be ommited. Removes the need for calling
`ToString()` on `int` values (for example)

.NET Core 2.1/3.0 (where we can't use `ValueStringBuilder`) has the
following differences
- Use the `StringBuilderCache` instead of `ValueStringBuilder`
- Add a "dummy" `AppendAsLowerInvariant()` and call
`ToString().ToLowerInvariant()` at the end (as we do today)

## Test coverage

We already have unit tests for the behaviour, so I focused on the
benchmarks

For the first test, I tested against all the templates we test with
here:


https://github.com/DataDog/dd-trace-dotnet/blob/c0dad8658f35f35a1202036ff1afb247fd9b7d7d/tracer/test/Datadog.Trace.Tests/DiagnosticListeners/AspNetCoreResourceNameHelperTests.cs#L104-L122

and created "aggregated" results, testing against .NET Core 2.1, 3.1, 6,
and 10:


| Method | Runtime | Expand Param | Mean | Allocated | Alloc Ratio |
| ---------------------- | ------------- | ------------ | -------: |
--------: | ----------: |
| RouteTemplate_Original | .NET 10.0 | False | 3.002 us | 4.3 KB | 1.00
|
| RouteTemplate_Updated | .NET 10.0 | False | 2.452 us | 1.7 KB | 0.40 |
| RouteTemplate_Original | .NET 6.0 | False | 3.781 us | 4.3 KB | 1.00 |
| RouteTemplate_Updated | .NET 6.0 | False | 3.425 us | 1.7 KB | 0.40 |
| RouteTemplate_Original | .NET Core 3.1 | False | 5.117 us | 4.3 KB |
1.00 |
| RouteTemplate_Updated | .NET Core 3.1 | False | 5.111 us | 1.7 KB |
0.40 |
| RouteTemplate_Original | .NET Core 2.1 | False | 8.571 us | 4.48 KB |
1.00 |
| RouteTemplate_Updated | .NET Core 2.1 | False | 7.712 us | 3.58 KB |
0.80 |
| | | | | | |
| RouteTemplate_Original | .NET 10.0 | True | 2.957 us | 3.99 KB | 0.99
|
| RouteTemplate_Updated | .NET 10.0 | True | 2.728 us | 1.55 KB | 0.38 |
| RouteTemplate_Original | .NET 6.0 | True | 4.135 us | 4.02 KB | 1.00 |
| RouteTemplate_Updated | .NET 6.0 | True | 3.663 us | 1.55 KB | 0.38 |
| RouteTemplate_Original | .NET Core 3.1 | True | 5.455 us | 4.02 KB |
1.00 |
| RouteTemplate_Updated | .NET Core 3.1 | True | 5.593 us | 1.55 KB |
0.38 |
| RouteTemplate_Original | .NET Core 2.1 | True | 9.361 us | 4.29 KB |
1.00 |
| RouteTemplate_Updated | .NET Core 2.1 | True | 8.878 us | 3.36 KB |
0.78 |


In all cases, we allocate less now, and for all .NET Core 3.1+, a _lot_
less. There is one minor regression in .NET Core 3.1 in speed when we
expand params, but I think the allocation savings are still worth it.

To ensure there were no single degenerate cases, I also ran .NET 6 only
against each template independently.

<details><summary>Full single-template benchmark results</summary>
<p>



| Version | Expand | Template | Mean | Allocated | Alloc Ratio |
| -------- | ------ |
----------------------------------------------------------------------------------
| -------: | --------: | ----------: |
| Original | False | {controller}/{action}/{id} | 152.24ns | 152B | 1.00
|
| Updated | False | {controller}/{action}/{id} | 329.65ns | 168B | 1.11
|
| Original | False | {controller}/{action} | 183.02ns | 184B | 1.21 |
| Updated | False | {controller}/{action} | 257.93ns | 96B | 0.63 |
| Original | False | {area:exists}/{controller}/{action} | 154.99ns |
152B | 1.00 |
| Updated | False | {area:exists}/{controller}/{action} | 298.77ns |
120B | 0.79 |
| Original | False | prefix/{controller}/{action} | 178.64ns | 184B |
1.21 |
| Updated | False | prefix/{controller}/{action} | 131.74ns | 56B | 0.37
|
| Original | False | prefix-{controller}/{action}-suffix | 153.31ns |
136B | 0.89 |
| Updated | False | prefix-{controller}/{action}-suffix | 117.38ns | 72B
| 0.47 |
| Original | False |
prefix-{controller}-{action}-{nonid}-{id}-{FormValue}-suffix | 175.44ns
| 184B | 1.21 |
| Updated | False |
prefix-{controller}-{action}-{nonid}-{id}-{FormValue}-suffix | 107.70ns
| 64B | 0.42 |
| Original | False |
prefix-{controller}-{action}-{Area}-{id}-{FormValue}-suffix | 238.54ns |
184B | 1.21 |
| Updated | False |
prefix-{controller}-{action}-{Area}-{id}-{FormValue}-suffix | 119.70ns |
64B | 0.42 |
| Original | False |
prefix-{controller}-{action}-{id}-{FormValue}/{Area?} | 172.45ns | 184B
| 1.21 |
| Updated | False |
prefix-{controller}-{action}-{id}-{FormValue}/{Area?} | 133.16ns | 64B |
0.42 |
| Original | False |
standalone/prefix-{controller}-{action}-{nonid}-{id}-{FormValue}-suffix/standalone
| 172.03ns | 184B | 1.21 |
| Updated | False |
standalone/prefix-{controller}-{action}-{nonid}-{id}-{FormValue}-suffix/standalone
| 248.15ns | 128B | 0.84 |
| Original | False | {controller}/{action}/{nonid} | 273.13ns | 184B |
1.21 |
| Updated | False | {controller}/{action}/{nonid} | 135.59ns | 64B |
0.42 |
| Original | False | {controller}/{action}/{nonid?} | 168.27ns | 168B |
1.11 |
| Updated | False | {controller}/{action}/{nonid?} | 130.99ns | 72B |
0.47 |
| Original | False | {controller}/{action}/{nonid=2} | 174.51ns | 168B |
1.11 |
| Updated | False | {controller}/{action}/{nonid=2} | 125.19ns | 64B |
0.42 |
| Original | False | {controller}/{action}/{nonid:int} | 161.26ns | 168B
| 1.11 |
| Updated | False | {controller}/{action}/{nonid:int} | 136.26ns | 72B |
0.47 |
| Original | False | {controller}/{action}/{FormValue} | 160.54ns | 168B
| 1.11 |
| Updated | False | {controller}/{action}/{FormValue} | 136.97ns | 72B |
0.47 |
| Original | False | {controller}/{action}/{FormValue?} | 300.78ns |
376B | 2.47 |
| Updated | False | {controller}/{action}/{FormValue?} | 137.28ns | 72B
| 0.47 |
| Original | False | {controller}/{action}/{FormValue=Edit} | 219.27ns |
232B | 1.53 |
| Updated | False | {controller}/{action}/{FormValue=Edit} | 135.32ns |
72B | 0.47 |
| Original | False | {controller}/{action}/{FormValue:int} | 221.59ns |
280B | 1.84 |
| Updated | False | {controller}/{action}/{FormValue:int} | 121.39ns |
48B | 0.32 |
| Original | False | {controller}/{action}/{nonidentity} | 226.88ns |
296B | 1.95 |
| Updated | False | {controller}/{action}/{nonidentity} | 137.23ns | 72B
| 0.47 |
| Original | False | {controller}/{action}/{nonidentity?} | 131.69ns |
184B | 1.21 |
| Updated | False | {controller}/{action}/{nonidentity?} | 130.84ns |
72B | 0.47 |
| Original | False | {controller}/{action}/{nonidentity=2} | 127.93ns |
168B | 1.11 |
| Updated | False | {controller}/{action}/{nonidentity=2} | 94.41ns |
48B | 0.32 |
| Original | False | {controller}/{action}/{nonidentity:int} | 132.35ns
| 168B | 1.11 |
| Updated | False | {controller}/{action}/{nonidentity:int} | 127.66ns |
64B | 0.42 |
| Original | False | {controller}/{action}/{idlike} | 119.40ns | 136B |
0.89 |
| Updated | False | {controller}/{action}/{idlike} | 128.02ns | 64B |
0.42 |
| Original | False | {controller}/{action}/{id:int} | 155.92ns | 168B |
1.11 |
| Updated | False | {controller}/{action}/{id:int} | 119.59ns | 56B |
0.37 |
| | | | | | |
| Original | True | {controller}/{action}/{id} | 146.29ns | 152B | 1.00
|
| Updated | True | {controller}/{action}/{id} | 138.64ns | 56B | 0.37 |
| Original | True | {controller}/{action} | 144.60ns | 152B | 1.00 |
| Updated | True | {controller}/{action} | 133.27ns | 56B | 0.37 |
| Original | True | {area:exists}/{controller}/{action} | 108.50ns |
136B | 0.89 |
| Updated | True | {area:exists}/{controller}/{action} | 143.76ns | 56B
| 0.37 |
| Original | True | prefix/{controller}/{action} | 129.71ns | 168B |
1.11 |
| Updated | True | prefix/{controller}/{action} | 126.89ns | 72B | 0.47
|
| Original | True | prefix-{controller}/{action}-suffix | 126.21ns |
168B | 1.11 |
| Updated | True | prefix-{controller}/{action}-suffix | 142.42ns | 56B
| 0.37 |
| Original | True |
prefix-{controller}-{action}-{nonid}-{id}-{FormValue}-suffix | 127.19ns
| 184B | 1.21 |
| Updated | True |
prefix-{controller}-{action}-{nonid}-{id}-{FormValue}-suffix | 124.55ns
| 72B | 0.47 |
| Original | True |
prefix-{controller}-{action}-{Area}-{id}-{FormValue}-suffix | 232.61ns |
248B | 1.63 |
| Updated | True |
prefix-{controller}-{action}-{Area}-{id}-{FormValue}-suffix | 109.81ns |
48B | 0.32 |
| Original | True |
prefix-{controller}-{action}-{id}-{FormValue}/{Area?} | 218.34ns | 264B
| 1.74 |
| Updated | True | prefix-{controller}-{action}-{id}-{FormValue}/{Area?}
| 136.71ns | 56B | 0.37 |
| Original | True |
standalone/prefix-{controller}-{action}-{nonid}-{id}-{FormValue}-suffix/standalone
| 206.66ns | 200B | 1.32 |
| Updated | True |
standalone/prefix-{controller}-{action}-{nonid}-{id}-{FormValue}-suffix/standalone
| 141.68ns | 56B | 0.37 |
| Original | True | {controller}/{action}/{nonid} | 268.18ns | 344B |
2.26 |
| Updated | True | {controller}/{action}/{nonid} | 138.87ns | 56B | 0.37
|
| Original | True | {controller}/{action}/{nonid?} | 150.72ns | 152B |
1.00 |
| Updated | True | {controller}/{action}/{nonid?} | 112.45ns | 72B |
0.47 |
| Original | True | {controller}/{action}/{nonid=2} | 149.30ns | 152B |
1.00 |
| Updated | True | {controller}/{action}/{nonid=2} | 283.82ns | 152B |
1.00 |
| Original | True | {controller}/{action}/{nonid:int} | 178.65ns | 200B
| 1.32 |
| Updated | True | {controller}/{action}/{nonid:int} | 204.47ns | 80B |
0.53 |
| Original | True | {controller}/{action}/{FormValue} | 153.26ns | 152B
| 1.00 |
| Updated | True | {controller}/{action}/{FormValue} | 233.21ns | 112B |
0.74 |
| Original | True | {controller}/{action}/{FormValue?} | 163.13ns | 152B
| 1.00 |
| Updated | True | {controller}/{action}/{FormValue?} | 252.30ns | 104B
| 0.68 |
| Original | True | {controller}/{action}/{FormValue=Edit} | 155.70ns |
152B | 1.00 |
| Updated | True | {controller}/{action}/{FormValue=Edit} | 102.94ns |
64B | 0.42 |
| Original | True | {controller}/{action}/{FormValue:int} | 163.94ns |
152B | 1.00 |
| Updated | True | {controller}/{action}/{FormValue:int} | 102.48ns |
64B | 0.42 |
| Original | True | {controller}/{action}/{nonidentity} | 158.25ns |
152B | 1.00 |
| Updated | True | {controller}/{action}/{nonidentity} | 84.14ns | 48B |
0.32 |
| Original | True | {controller}/{action}/{nonidentity?} | 150.04ns |
184B | 1.21 |
| Updated | True | {controller}/{action}/{nonidentity?} | 123.77ns | 72B
| 0.47 |
| Original | True | {controller}/{action}/{nonidentity=2} | 135.92ns |
136B | 0.89 |
| Updated | True | {controller}/{action}/{nonidentity=2} | 120.00ns |
56B | 0.37 |
| Original | True | {controller}/{action}/{nonidentity:int} | 153.92ns |
184B | 1.21 |
| Updated | True | {controller}/{action}/{nonidentity:int} | 130.82ns |
64B | 0.42 |
| Original | True | {controller}/{action}/{idlike} | 152.99ns | 184B |
1.21 |
| Updated | True | {controller}/{action}/{idlike} | 128.73ns | 56B |
0.37 |
| Original | True | {controller}/{action}/{id:int} | 143.68ns | 152B |
1.00 |
| Updated | True | {controller}/{action}/{id:int} | 124.07ns | 56B |
0.37 |


</p>
</details> 

In almost all cases there were improvements in both memory and cpu (see
details collapsed above), but there were a few cases that stood out as
getting worse. The problematic templates (and their results) were:

| Route Template | Expand Templates | template | Mean | Allocated |
| -------------- | ---------------- |
----------------------------------------------------------------------------------
| --------: | --------: |
| Original | False | {controller}/{action}/{id} | 152.24ns | 152B | 1.00
|
| Updated | False | {controller}/{action}/{id} | 329.65ns | 168B | 1.11
|
| Original | False |
standalone/prefix-{controller}-{action}-{nonid}-{id}-{FormValue}-suffix/standalone
| 172.03ns | 184B | 1.21 |
| Updated | False |
standalone/prefix-{controller}-{action}-{nonid}-{id}-{FormValue}-suffix/standalone
| 248.15ns | 128B | 0.84 |
| Original | True | {controller}/{action}/{nonid=2} | 149.30ns | 152B |
1.00 |
| Original | True | {controller}/{action}/{nonid=2} | 149.30ns | 152B |
1.00 |

The first case is the most odd. I can't for the life of me see where the
_extra_ allocation is coming from 😕 The second case is similarly strange
that there's no reduction in allocation. The latter one shows a
degradation in time only, so is less concerning, but still strange! 😄

I am a bit concerned about the first case, but even putting dotmemmory
on it, the only allocations I see are the final `ToString()` so 🤷‍♂️

<details><summary>Benchmark code</summary>
<p>

```csharp
#if !NETFRAMEWORK

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using Datadog.Trace;
using Datadog.Trace.Agent.DiscoveryService;
using Datadog.Trace.AppSec;
using Datadog.Trace.ClrProfiler.AutoInstrumentation.Http.HttpClient.HttpClientHandler;
using Datadog.Trace.Configuration;
using Datadog.Trace.Configuration.Telemetry;
using Datadog.Trace.Debugger;
using Datadog.Trace.Debugger.SpanCodeOrigin;
using Datadog.Trace.DiagnosticListeners;
using Datadog.Trace.DuckTyping;
using Datadog.Trace.Iast.Settings;
using Datadog.Trace.RemoteConfigurationManagement;
using Datadog.Trace.Security.Unit.Tests.Iast;
using Datadog.Trace.Util;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.AspNetCore.Routing.Template;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
#if NETCOREAPP3_0_OR_GREATER
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing.Patterns;
#endif
namespace Benchmarks.Trace
{
    [MemoryDiagnoser, GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory), CategoriesColumn]
    public class AspNetCoreResourceNameBenchmark
    {
        private Datadog.Trace.DiagnosticListeners.RoutePattern[] _routePatterns;
        private Datadog.Trace.DiagnosticListeners.RoutePattern[] _routePatternsWithDefaults;
        private RouteValueDictionary _values;
        private RouteValueDictionary _defaults;
        private RouteValueDictionary _parameterPolicies;
        private string[] _results;
        private string[] _results2;
        private RouteTemplate[] _routeTemplates =
        [
            TemplateParser.Parse("{controller}/{action}/{id}"),
            TemplateParser.Parse("{controller}/{action}"),
            TemplateParser.Parse("{area:exists}/{controller}/{action}"),
            TemplateParser.Parse("prefix/{controller}/{action}"),
            TemplateParser.Parse("prefix-{controller}/{action}-suffix"),
            TemplateParser.Parse("prefix-{controller}-{action}-{nonid}-{id}-{FormValue}-suffix"),
            TemplateParser.Parse("prefix-{controller}-{action}-{Area}-{id}-{FormValue}-suffix"),
            TemplateParser.Parse("prefix-{controller}-{action}-{id}-{FormValue}/{Area?}"),
            TemplateParser.Parse("standalone/prefix-{controller}-{action}-{nonid}-{id}-{FormValue}-suffix/standalone"),
            TemplateParser.Parse("{controller}/{action}/{nonid}"),
            TemplateParser.Parse("{controller}/{action}/{nonid?}"),
            TemplateParser.Parse("{controller}/{action}/{nonid=2}"),
            TemplateParser.Parse("{controller}/{action}/{nonid:int}"),
            TemplateParser.Parse("{controller}/{action}/{FormValue}"),
            TemplateParser.Parse("{controller}/{action}/{FormValue?}"),
            TemplateParser.Parse("{controller}/{action}/{FormValue=Edit}"),
            TemplateParser.Parse("{controller}/{action}/{FormValue:int}"),
            TemplateParser.Parse("{controller}/{action}/{nonidentity}"),
            TemplateParser.Parse("{controller}/{action}/{nonidentity?}"),
            TemplateParser.Parse("{controller}/{action}/{nonidentity=2}"),
            TemplateParser.Parse("{controller}/{action}/{nonidentity:int}"),
            TemplateParser.Parse("{controller}/{action}/{idlike}"),
            TemplateParser.Parse("{controller}/{action}/{id:int}"),
        ];

        [GlobalSetup]
        public void GlobalSetup()
        {
#if NETCOREAPP3_0_OR_GREATER
            _routePatterns =
            [
                RoutePatternFactory.Parse("{controller}/{action}/{id}").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{area:exists}/{controller}/{action}").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("prefix/{controller}/{action}").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("prefix-{controller}/{action}-suffix").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("prefix-{controller}-{action}-{nonid}-{id}-{FormValue}-suffix").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("prefix-{controller}-{action}-{Area}-{id}-{FormValue}-suffix").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("prefix-{controller}-{action}-{id}-{FormValue}/{Area?}").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("standalone/prefix-{controller}-{action}-{nonid}-{id}-{FormValue}-suffix/standalone").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{nonid}").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{nonid?}").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{nonid=2}").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{nonid:int}").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{FormValue}").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{FormValue?}").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{FormValue=Edit}").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{FormValue:int}").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{nonidentity}").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{nonidentity?}").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{nonidentity=2}").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{nonidentity:int}").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{idlike}").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{id:int}").DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
            ];
            _routePatternsWithDefaults =
            [
                RoutePatternFactory.Parse("{controller}/{action}/{id}", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{area:exists}/{controller}/{action}", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("prefix/{controller}/{action}", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("prefix-{controller}/{action}-suffix", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("prefix-{controller}-{action}-{nonid}-{id}-{FormValue}-suffix", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("prefix-{controller}-{action}-{Area}-{id}-{FormValue}-suffix", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("prefix-{controller}-{action}-{id}-{FormValue}/{Area?}", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("standalone/prefix-{controller}-{action}-{nonid}-{id}-{FormValue}-suffix/standalone", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{nonid}", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{nonid?}", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{nonid=2}", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{nonid:int}", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{FormValue}", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{FormValue?}", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{FormValue=Edit}", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{FormValue:int}", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{nonidentity}", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{nonidentity?}", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{nonidentity=2}", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{nonidentity:int}", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{idlike}", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
                RoutePatternFactory.Parse("{controller}/{action}/{id:int}", _defaults, parameterPolicies: _parameterPolicies).DuckCast<Datadog.Trace.DiagnosticListeners.RoutePattern>(),
            ];
            _results = new string[_routePatterns.Length];
#endif

            _results2 = new string[_routeTemplates.Length];

            _values = new()
            {
                { "controller", "Home" },
                { "action", "Index" },
                { "nonid", "oops" },
                { "idlike", 123 },
                { "FormValue", "View" },
            };

            _defaults = new()
            {
                { "controller", "Home" },
                { "action", "Index" },
                { "FormValue", "View" }
            };

            _parameterPolicies = new()
            {
                { "id", new OptionalRouteConstraint(new IntRouteConstraint()) }
            };
        }

        [Params(true, false)]
        public bool ExpandTemplates { get; set; }

        public IEnumerable<object> RouteTemplates => _routeTemplates.Select(x => (object)x);

        [GlobalCleanup]
        public void GlobalCleanup()
        {
        }

        [Benchmark(Baseline = true)]
        [BenchmarkCategory("RoutePattern")]
        public string[] RoutePattern_Original()
        {
            for (var i = 0; i < _routePatterns.Length; i++)
            {
                _results[i] = OriginalAspNetCoreResourceNameHelper.SimplifyRoutePattern(
                    routePattern: _routePatterns[i],
                    routeValueDictionary: _values,
                    areaName: null,
                    controllerName: _values["controller"] as string,
                    actionName: _values["action"] as string,
                    ExpandTemplates);
            }

            return _results;
        }

        [Benchmark(Baseline = true)]
        [BenchmarkCategory("RoutePatternWithDefaults")]
        public string[] RoutePatternWithDefaults_Original()
        {
            for (var i = 0; i < _routePatternsWithDefaults.Length; i++)
            {
                _results[i] = OriginalAspNetCoreResourceNameHelper.SimplifyRoutePattern(
                    routePattern: _routePatternsWithDefaults[i],
                    routeValueDictionary: _values,
                    areaName: null,
                    controllerName: _values["controller"] as string,
                    actionName: _values["action"] as string,
                    ExpandTemplates);
            }

            return _results;
        }

        [Benchmark(Baseline = true)]
        [BenchmarkCategory("RouteTemplate")]
        public string[] RouteTemplate_Original()
        {
            for (var i = 0; i < _routeTemplates.Length; i++)
            {
                _results2[i] = OriginalAspNetCoreResourceNameHelper.SimplifyRouteTemplate(
                    routePattern: _routeTemplates[i],
                    routeValueDictionary: _values,
                    areaName: null,
                    controllerName: _values["controller"] as string,
                    actionName: _values["action"] as string,
                    ExpandTemplates);
            }

            return _results;
        }

        [Benchmark(Baseline = true)]
        [BenchmarkCategory("RouteTemplateSingle")]
        [ArgumentsSource(nameof(RouteTemplates))]
        public string RouteTemplateSingle_Original(RouteTemplate template)
        {
            return OriginalAspNetCoreResourceNameHelper.SimplifyRouteTemplate(
                routePattern: template,
                routeValueDictionary: _values,
                areaName: null,
                controllerName: _values["controller"] as string,
                actionName: _values["action"] as string,
                ExpandTemplates);
        }

        [Benchmark]
        [BenchmarkCategory("RoutePattern")]
        public string[] RoutePattern_Updated()
        {
            for (var i = 0; i < _routePatterns.Length; i++)
            {
                _results[i] = AspNetCoreResourceNameHelper.SimplifyRoutePattern(
                    routePattern: _routePatterns[i],
                    routeValueDictionary: _values,
                    areaName: null,
                    controllerName: _values["controller"] as string,
                    actionName: _values["action"] as string,
                    ExpandTemplates);
            }

            return _results;
        }

        [Benchmark]
        [BenchmarkCategory("RoutePatternWithDefaults")]
        public string[] RoutePatternWithDefaults_Updated()
        {
            for (var i = 0; i < _routePatternsWithDefaults.Length; i++)
            {
                _results[i] = AspNetCoreResourceNameHelper.SimplifyRoutePattern(
                    routePattern: _routePatternsWithDefaults[i],
                    routeValueDictionary: _values,
                    areaName: null,
                    controllerName: _values["controller"] as string,
                    actionName: _values["action"] as string,
                    ExpandTemplates);
            }

            return _results;
        }

        [Benchmark]
        [BenchmarkCategory("RouteTemplate")]
        public string[] RouteTemplate_Updated()
        {
            for (var i = 0; i < _routeTemplates.Length; i++)
            {
                _results2[i] = AspNetCoreResourceNameHelper.SimplifyRouteTemplate(
                    routePattern: _routeTemplates[i],
                    routeValueDictionary: _values,
                    areaName: null,
                    controllerName: _values["controller"] as string,
                    actionName: _values["action"] as string,
                    ExpandTemplates);
            }

            return _results;
        }

        [Benchmark]
        [BenchmarkCategory("RouteTemplateSingle")]
        [ArgumentsSource(nameof(RouteTemplates))]
        public string RouteTemplateSingle_Updated(RouteTemplate template)
        {
            return AspNetCoreResourceNameHelper.SimplifyRouteTemplate(
                routePattern: template,
                routeValueDictionary: _values,
                areaName: null,
                controllerName: _values["controller"] as string,
                actionName: _values["action"] as string,
                ExpandTemplates);
        }

        private class Startup
        {
            public void ConfigureServices(IServiceCollection services)
            {
                services.AddMvc();
            }

            public void Configure(IApplicationBuilder builder)
            {
#if NETCOREAPP2_1
                builder.UseMvcWithDefaultRoute();
#else
                builder.UseRouting();
                builder.UseEndpoints(endpoints =>
                {
                    endpoints.MapControllerRoute(
                        name: "default",
                        pattern: "{controller=Home}/{action=Index}/{id?}");
                });
#endif
            }
        }
    }
}
#else

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Web.Routing;
using BenchmarkDotNet.Attributes;
using Datadog.Trace.DuckTyping;
using Datadog.Trace.Util;

namespace Benchmarks.Trace
{
    [MemoryDiagnoser]
    public class AspNetCoreResourceNameBenchmark
    {
        [GlobalSetup]
        public void GlobalSetup()
        {
        }

        [GlobalCleanup]
        public void GlobalCleanup()
        {
        }

        [Benchmark]
        public string SendRequest()
        {
            return null;
        }
    }
}

#endif

#if !NETFRAMEWORK
internal static class OriginalAspNetCoreResourceNameHelper
{
    internal static string SimplifyRoutePattern(
        Datadog.Trace.DiagnosticListeners.RoutePattern routePattern,
        IReadOnlyDictionary<string, object> routeValueDictionary,
        string? areaName,
        string? controllerName,
        string? actionName,
        bool expandRouteParameters)
    {
        var maxSize = (routePattern.RawText?.Length ?? 0)
                    + (string.IsNullOrEmpty(areaName) ? 0 : Math.Max(areaName!.Length - 4, 0)) // "area".Length
                    + (string.IsNullOrEmpty(controllerName) ? 0 : Math.Max(controllerName!.Length - 10, 0)) // "controller".Length
                    + (string.IsNullOrEmpty(actionName) ? 0 : Math.Max(actionName!.Length - 6, 0)) // "action".Length
                    + 1; // '/' prefix

        var sb = StringBuilderCache.Acquire(maxSize);

        foreach (var pathSegment in routePattern.PathSegments)
        {
            var parts = 0;
            foreach (var part in pathSegment.DuckCast<AspNetCoreDiagnosticObserver.RoutePatternPathSegmentStruct>().Parts)
            {
                parts++;
                if (part.TryDuckCast(out AspNetCoreDiagnosticObserver.RoutePatternContentPartStruct contentPart))
                {
                    if (parts == 1)
                    {
                        sb.Append('/');
                    }

                    sb.Append(contentPart.Content);
                }
                else if (part.TryDuckCast(out AspNetCoreDiagnosticObserver.RoutePatternParameterPartStruct parameter))
                {
                    var parameterName = parameter.Name;
                    if (parameterName.Equals("area", StringComparison.OrdinalIgnoreCase))
                    {
                        if (areaName is null && parameter.IsOptional)
                        {
                            // don't append optional suffixes when no value is provided
                            continue;
                        }

                        if (parts == 1)
                        {
                            sb.Append('/');
                        }

                        sb.Append(areaName ?? "{area}");
                    }
                    else if (parameterName.Equals("controller", StringComparison.OrdinalIgnoreCase))
                    {
                        if (controllerName is null && parameter.IsOptional)
                        {
                            // don't append optional suffixes when no value is provided
                            continue;
                        }

                        if (parts == 1)
                        {
                            sb.Append('/');
                        }

                        sb.Append(controllerName ?? "{controller}");
                    }
                    else if (parameterName.Equals("action", StringComparison.OrdinalIgnoreCase))
                    {
                        if (actionName is null && parameter.IsOptional)
                        {
                            // don't append optional suffixes when no value is provided
                            continue;
                        }

                        if (parts == 1)
                        {
                            sb.Append('/');
                        }

                        sb.Append(actionName ?? "{action}");
                    }
                    else
                    {
                        var haveParameter = routeValueDictionary.TryGetValue(parameterName, out var value);
                        if (!parameter.IsOptional || haveParameter)
                        {
                            if (parts == 1)
                            {
                                sb.Append('/');
                            }

                            if (expandRouteParameters && haveParameter && !IsIdentifierSegment(value, out var valueAsString))
                            {
                                // write the expanded parameter value
                                sb.Append(valueAsString);
                            }
                            else
                            {
                                // write the route template value
                                sb.Append('{');
                                if (parameter.IsCatchAll)
                                {
                                    if (parameter.EncodeSlashes)
                                    {
                                        sb.Append("**");
                                    }
                                    else
                                    {
                                        sb.Append('*');
                                    }
                                }

                                sb.Append(parameterName);
                                if (parameter.IsOptional)
                                {
                                    sb.Append('?');
                                }

                                sb.Append('}');
                            }
                        }
                    }
                }
            }
        }

        var simplifiedRoute = StringBuilderCache.GetStringAndRelease(sb);

        return string.IsNullOrEmpty(simplifiedRoute) ? "/" : simplifiedRoute.ToLowerInvariant();
    }

    internal static string SimplifyRouteTemplate(
        RouteTemplate routePattern,
        RouteValueDictionary routeValueDictionary,
        string? areaName,
        string? controllerName,
        string? actionName,
        bool expandRouteParameters)
    {
        var maxSize = (routePattern.TemplateText?.Length ?? 0)
                    + (string.IsNullOrEmpty(areaName) ? 0 : Math.Max(areaName!.Length - 4, 0)) // "area".Length
                    + (string.IsNullOrEmpty(controllerName) ? 0 : Math.Max(controllerName!.Length - 10, 0)) // "controller".Length
                    + (string.IsNullOrEmpty(actionName) ? 0 : Math.Max(actionName!.Length - 6, 0)) // "action".Length
                    + 1; // '/' prefix

        var sb = StringBuilderCache.Acquire(maxSize);

        foreach (var pathSegment in routePattern.Segments)
        {
            var parts = 0;
            foreach (var part in pathSegment.Parts)
            {
                parts++;
                var partName = part.Name;

                if (!part.IsParameter)
                {
                    if (parts == 1)
                    {
                        sb.Append('/');
                    }

                    sb.Append(part.Text);
                }
                else if (partName.Equals("area", StringComparison.OrdinalIgnoreCase))
                {
                    if (areaName is null && part.IsOptional)
                    {
                        // don't append optional suffixes when no value is provided
                        continue;
                    }

                    if (parts == 1)
                    {
                        sb.Append('/');
                    }

                    sb.Append(areaName ?? "{area}");
                }
                else if (partName.Equals("controller", StringComparison.OrdinalIgnoreCase))
                {
                    if (controllerName is null && part.IsOptional)
                    {
                        // don't append optional suffixes when no value is provided
                        continue;
                    }

                    if (parts == 1)
                    {
                        sb.Append('/');
                    }

                    sb.Append(controllerName ?? "{controller}");
                }
                else if (partName.Equals("action", StringComparison.OrdinalIgnoreCase))
                {
                    if (actionName is null && part.IsOptional)
                    {
                        // don't append optional suffixes when no value is provided
                        continue;
                    }

                    if (parts == 1)
                    {
                        sb.Append('/');
                    }

                    sb.Append(actionName ?? "{action}");
                }
                else
                {
                    var haveParameter = routeValueDictionary.TryGetValue(partName, out var value);
                    if (!part.IsOptional || haveParameter)
                    {
                        if (parts == 1)
                        {
                            sb.Append('/');
                        }

                        if (expandRouteParameters && haveParameter && !IsIdentifierSegment(value, out var valueAsString))
                        {
                            // write the expanded parameter value
                            sb.Append(valueAsString);
                        }
                        else
                        {
                            // write the route template value
                            sb.Append('{');
                            if (part.IsCatchAll)
                            {
                                sb.Append('*');
                            }

                            sb.Append(partName);
                            if (part.IsOptional)
                            {
                                sb.Append('?');
                            }

                            sb.Append('}');
                        }
                    }
                }
            }
        }

        var simplifiedRoute = StringBuilderCache.GetStringAndRelease(sb);

        return string.IsNullOrEmpty(simplifiedRoute) ? "/" : simplifiedRoute.ToLowerInvariant();
    }

    private static bool IsIdentifierSegment(object value, out string valueAsString)
    {
        valueAsString = value as string ?? value?.ToString();
        if (valueAsString is null)
        {
            return false;
        }

        return UriHelpers.IsIdentifierSegment(valueAsString, 0, valueAsString.Length);
    }
}
#endif
```

</p>
</details> 

## Other details

Part of a stack
https://datadoghq.atlassian.net/browse/LANGPLAT-842

- #8167
- #8170 👈
- #8180
andrewlock added a commit that referenced this pull request Feb 18, 2026
…tern` (#8180)

## Summary of changes

Switches to using `ValueStringBuilder` and other minor perf improvements

## Reason for change

Same as #8170, we want to improve performance where we can.
Unfortunately, we can't easily avoid the enumeration allocation like we
did in that PR, so most of the benefits here are simply from using
`ValueStringBuilder` and other minor changes.

## Implementation details

Incorporated various changes based on the `SimplifyRoutePattern` that we
use in the single-span aspnetcore observer and the changes made in
#8170. The gains aren't as high here, because we can't reduce
enumeration allocation.

## Test coverage

Covered by existing tests.

Ran benchmarks using the values. In general, there's a slight regression
in duration for an improvement in Alloc Ratio. It's not very consistent
though, particularly in <.NET Core 3.1. I think it's probably still
worth the change, but open to thoughts


| Method | Runtime | Has Defaults | Expand Templates | Mean | Allocated
| Alloc Ratio |
| -------- | ------------- | ------------ | ---------------- | --------:
| --------: | ----------: |
| Original | .NET 10.0 | False | False | 6.605 us | 6.51 KB | 1.00 |
| Updated | .NET 10.0 | False | False | 8.559 us | 4.8 KB | 0.74 |
| Original | .NET 6.0 | False | False | 11.466 us | 6.51 KB | 1.00 |
| Updated | .NET 6.0 | False | False | 11.654 us | 4.8 KB | 0.74 |
| Original | .NET Core 3.1 | False | False | 13.808 us | 6.51 KB | 1.00
|
| Updated | .NET Core 3.1 | False | False | 15.266 us | 4.8 KB | 0.74 |
| Original | .NET Core 3.0 | False | False | 13.962 us | 6.51 KB | 1.00
|
| Updated | .NET Core 3.0 | False | False | 19.757 us | 6.51 KB | 1.00 |
| | | | | | | |
| Original | .NET 10.0 | False | True | 6.453 us | 6.2 KB | 0.99 |
| Updated | .NET 10.0 | False | True | 8.116 us | 4.65 KB | 0.75 |
| Original | .NET 6.0 | False | True | 11.121 us | 6.23 KB | 1.00 |
| Updated | .NET 6.0 | False | True | 11.375 us | 4.65 KB | 0.75 |
| Original | .NET Core 3.1 | False | True | 14.075 us | 6.23 KB | 1.00 |
| Updated | .NET Core 3.1 | False | True | 15.000 us | 4.65 KB | 0.75 |
| Original | .NET Core 3.0 | False | True | 14.027 us | 6.23 KB | 1.00 |
| Updated | .NET Core 3.0 | False | True | 13.687 us | 6.2 KB | 0.99 |
| | | | | | | |
| Original | .NET 10.0 | True | False | 6.348 us | 6.51 KB | 1.00 |
| Updated | .NET 10.0 | True | False | 8.310 us | 4.8 KB | 0.74 |
| Original | .NET 6.0 | True | False | 11.082 us | 6.51 KB | 1.00 |
| Updated | .NET 6.0 | True | False | 11.406 us | 4.8 KB | 0.74 |
| Original | .NET Core 3.1 | True | False | 9.853 us | 6.51 KB | 1.00 |
| Updated | .NET Core 3.1 | True | False | 10.865 us | 4.8 KB | 0.74 |
| Original | .NET Core 3.0 | True | False | 14.879 us | 6.51 KB | 1.00 |
| Updated | .NET Core 3.0 | True | False | 10.221 us | 6.51 KB | 1.00 |
| | | | | | | |
| Original | .NET 10.0 | True | True | 4.002 us | 6.2 KB | 0.99 |
| Updated | .NET 10.0 | True | True | 5.685 us | 4.65 KB | 0.75 |
| Original | .NET 6.0 | True | True | 7.964 us | 6.23 KB | 1.00 |
| Updated | .NET 6.0 | True | True | 8.385 us | 4.65 KB | 0.75 |
| Original | .NET Core 3.1 | True | True | 13.949 us | 6.23 KB | 1.00 |
| Updated | .NET Core 3.1 | True | True | 15.370 us | 4.65 KB | 0.75 |
| Original | .NET Core 3.0 | True | True | 10.116 us | 6.23 KB | 1.00 |
| Updated | .NET Core 3.0 | True | True | 10.241 us | 6.2 KB | 0.99 |


## Other details
Part of a stack
https://datadoghq.atlassian.net/browse/LANGPLAT-842

- #8167
- #8170
- #8180 👈
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type:performance Performance, speed, latency, resource usage (CPU, memory)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants