Skip to content

Comments

Cache MessagePack bytes rather than just the bytes#8175

Open
pierotibou wants to merge 9 commits intopierre/tagsfrom
pierre/mpack
Open

Cache MessagePack bytes rather than just the bytes#8175
pierotibou wants to merge 9 commits intopierre/tagsfrom
pierre/mpack

Conversation

@pierotibou
Copy link
Contributor

@pierotibou pierotibou commented Feb 7, 2026

Summary of changes

In SpanMessagePackFormatter cache Serialized MessagePack bytes (thus with the header) instead of just the Bytes. Take it a step further and generate the byte arrays at compile time with source generators. Also remove AAS tags from MessagePackStringCache as they never change.

Reason for change

Mostly to make the code consistent with MessagePackStringCache where we were using that method. I chose that solution instead of impacting MessagePackStringCache as It's a micro optim.
Also the cache should contain only values that can change.

Implementation details

  • Moved AAS Tags from MessagePackStringCache to SpanMessagePackFormatter.
  • Use MessagePackSerializer.Serialize and WriteRaw instead of StringEncoding.UTF8.GetBytes and WriteStringBytes
  • Implement source generators.
  • There's one caveat that I duplicated the dotnet constant as TracerConstants is linked in dd-dotnet whereas sourcegenerators aren't. I didn't know if we want to have an almost empty shared project to share, or a extra dependency for dd_dotnet so I kept things simple for now.

Test coverage

Asked an agent to add a unit test that is testing all values to make sure I didn't forget one case. I didn't plan to commit it as I'm not sure it's valuable in other cases, but I did as a separate commit so it's easy to drop if we don't want to keep it.

Other details

Benchmark results (ran them 3 times as there was a good variance between runs, after was always better).

Before:

Method Mean Error StdDev Allocated
'Serialize simple span (minimal tags)' 293.7 ns 3.88 ns 3.44 ns -
'Serialize span with some tags' 363.2 ns 4.36 ns 3.41 ns -
'Serialize span with many tags' 942.5 ns 3.41 ns 3.02 ns -

After:

Method Mean Error StdDev Allocated
'Serialize simple span (minimal tags)' 238.4 ns 3.69 ns 3.50 ns -
'Serialize span with some tags' 316.0 ns 6.56 ns 6.43 ns -
'Serialize span with many tags' 887.7 ns 17.71 ns 20.06 ns -

pierotibou and others added 4 commits February 6, 2026 21:36
ProcessTags are now computed lazily.
They are also called in the relevant way from the settings.
…gePackFormatter

Changed from StringEncoding.UTF8.GetBytes() + WriteStringBytes() to
MessagePackSerializer.Serialize() + WriteRaw() for pre-encoded constant strings.

Benchmark results show 4-16% performance improvement with zero allocations maintained:
- Simple span: 267.9ns → 231.5ns (13.6% faster)
- Span with tags: 353.6ns → 297.7ns (15.8% faster)
- Span with many tags: 901.1ns → 863.4ns (4.2% faster)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@pierotibou pierotibou requested a review from a team as a code owner February 7, 2026 08:00
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 098a5dbbb6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@pr-commenter
Copy link

pr-commenter bot commented Feb 7, 2026

Benchmarks

Benchmark execution time: 2026-02-18 22:31:21

Comparing candidate commit c91512b in PR branch pierre/mpack with baseline commit e0e1029 in branch master.

Found 8 performance improvements and 16 performance regressions! Performance is the same for 156 metrics, 12 unstable metrics.

scenario:Benchmarks.Trace.AgentWriterBenchmark.WriteAndFlushEnrichedTraces net472

  • 🟥 throughput [-43.260op/s; -41.587op/s] or [-7.372%; -7.087%]
  • 🟩 execution_time [-12.705ms; -12.081ms] or [-5.824%; -5.538%]

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

  • 🟩 execution_time [-28.906ms; -28.682ms] or [-24.021%; -23.835%]
  • 🟩 throughput [+75.370op/s; +76.922op/s] or [+9.448%; +9.642%]

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

  • 🟥 execution_time [+85.544ms; +85.724ms] or [+77.566%; +77.729%]

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

  • 🟩 execution_time [-27.805ms; -23.373ms] or [-12.686%; -10.664%]

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

  • 🟥 execution_time [+15.882ms; +21.799ms] or [+8.103%; +11.122%]

scenario:Benchmarks.Trace.Asm.AppSecEncoderBenchmark.EncodeLegacyArgs netcoreapp3.1

  • 🟥 execution_time [+26.913ms; +27.744ms] or [+15.341%; +15.815%]

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

  • 🟩 execution_time [-138.963µs; -132.543µs] or [-8.827%; -8.419%]
  • 🟩 throughput [+58.485op/s; +61.418op/s] or [+9.207%; +9.669%]

scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearch net472

  • 🟥 throughput [-43659.807op/s; -41776.156op/s] or [-13.704%; -13.113%]

scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearch net6.0

  • 🟥 throughput [-68560.593op/s; -62186.905op/s] or [-10.677%; -9.684%]

scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearch netcoreapp3.1

  • 🟥 throughput [-57584.030op/s; -49155.241op/s] or [-11.760%; -10.039%]

scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearchAsync net472

  • 🟥 throughput [-51330.243op/s; -48957.432op/s] or [-16.523%; -15.759%]

scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearchAsync net6.0

  • 🟥 throughput [-67280.039op/s; -55905.826op/s] or [-10.978%; -9.122%]

scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearchAsync netcoreapp3.1

  • 🟥 throughput [-42805.116op/s; -33440.044op/s] or [-9.036%; -7.059%]

scenario:Benchmarks.Trace.HttpClientBenchmark.SendAsync net472

  • 🟥 throughput [-6302.197op/s; -5919.911op/s] or [-7.093%; -6.662%]

scenario:Benchmarks.Trace.HttpClientBenchmark.SendAsync netcoreapp3.1

  • 🟥 throughput [-7505.753op/s; -6103.404op/s] or [-6.167%; -5.015%]

scenario:Benchmarks.Trace.RedisBenchmark.SendReceive net472

  • 🟥 throughput [-36728.495op/s; -34317.708op/s] or [-10.384%; -9.703%]

scenario:Benchmarks.Trace.RedisBenchmark.SendReceive net6.0

  • 🟥 throughput [-66277.662op/s; -54127.746op/s] or [-11.762%; -9.606%]

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

  • 🟥 throughput [-38908.410op/s; -29885.287op/s] or [-9.218%; -7.080%]

scenario:Benchmarks.Trace.SingleSpanAspNetCoreBenchmark.SingleSpanAspNetCore net6.0

  • 🟩 execution_time [-36.215ms; -33.226ms] or [-18.335%; -16.823%]

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

  • 🟥 execution_time [+15.020ms; +20.904ms] or [+7.665%; +10.668%]

scenario:Benchmarks.Trace.SpanBenchmark.StartFinishSpan netcoreapp3.1

  • 🟩 execution_time [-16.085ms; -12.114ms] or [-7.484%; -5.637%]

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

dd-trace-dotnet-ci-bot bot commented Feb 7, 2026

Execution-Time Benchmarks Report ⏱️

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

⚠️ Potential regressions detected

HttpMessageHandler

Metric Master (Mean ± 95% CI) Current (Mean ± 95% CI) Change Status
.NET Framework 4.8 - CallTarget+Inlining+NGEN
duration1155.35 ± (1155.24 - 1161.34) ms1231.76 ± (1231.89 - 1238.73) ms+6.6%❌⬆️
Full Metrics Comparison

FakeDbCommand

Metric Master (Mean ± 95% CI) Current (Mean ± 95% CI) Change Status
.NET Framework 4.8 - Baseline
duration68.20 ± (68.19 - 68.40) ms68.19 ± (68.17 - 68.46) ms-0.0%
.NET Framework 4.8 - Bailout
duration71.97 ± (71.88 - 72.15) ms71.83 ± (71.76 - 72.03) ms-0.2%
.NET Framework 4.8 - CallTarget+Inlining+NGEN
duration1029.67 ± (1032.55 - 1041.04) ms1022.70 ± (1025.29 - 1033.24) ms-0.7%
.NET Core 3.1 - Baseline
process.internal_duration_ms22.43 ± (22.38 - 22.48) ms22.52 ± (22.49 - 22.55) ms+0.4%✅⬆️
process.time_to_main_ms86.38 ± (86.19 - 86.56) ms86.26 ± (86.11 - 86.41) ms-0.1%
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed15.50 ± (15.49 - 15.50) MB15.51 ± (15.51 - 15.52) MB+0.1%✅⬆️
runtime.dotnet.threads.count12 ± (12 - 12)12 ± (12 - 12)+0.0%
.NET Core 3.1 - Bailout
process.internal_duration_ms22.24 ± (22.22 - 22.27) ms22.39 ± (22.37 - 22.42) ms+0.7%✅⬆️
process.time_to_main_ms87.39 ± (87.29 - 87.48) ms87.55 ± (87.43 - 87.68) ms+0.2%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed15.53 ± (15.53 - 15.54) MB15.56 ± (15.55 - 15.56) MB+0.2%✅⬆️
runtime.dotnet.threads.count13 ± (13 - 13)13 ± (13 - 13)+0.0%
.NET Core 3.1 - CallTarget+Inlining+NGEN
process.internal_duration_ms258.30 ± (255.23 - 261.37) ms245.67 ± (244.19 - 247.15) ms-4.9%
process.time_to_main_ms504.18 ± (503.58 - 504.79) ms524.97 ± (524.21 - 525.72) ms+4.1%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed53.28 ± (53.26 - 53.31) MB53.65 ± (53.62 - 53.68) MB+0.7%✅⬆️
runtime.dotnet.threads.count28 ± (28 - 28)28 ± (28 - 28)+1.8%✅⬆️
.NET 6 - Baseline
process.internal_duration_ms20.99 ± (20.97 - 21.02) ms20.94 ± (20.91 - 20.96) ms-0.3%
process.time_to_main_ms74.34 ± (74.24 - 74.45) ms74.42 ± (74.29 - 74.54) ms+0.1%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed15.22 ± (15.22 - 15.22) MB15.23 ± (15.23 - 15.24) MB+0.1%✅⬆️
runtime.dotnet.threads.count10 ± (10 - 10)10 ± (10 - 10)+0.0%
.NET 6 - Bailout
process.internal_duration_ms20.95 ± (20.93 - 20.97) ms20.89 ± (20.87 - 20.92) ms-0.3%
process.time_to_main_ms75.40 ± (75.33 - 75.47) ms75.68 ± (75.62 - 75.74) ms+0.4%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed15.33 ± (15.33 - 15.33) MB15.33 ± (15.33 - 15.34) MB+0.0%✅⬆️
runtime.dotnet.threads.count11 ± (11 - 11)11 ± (11 - 11)+0.0%
.NET 6 - CallTarget+Inlining+NGEN
process.internal_duration_ms253.58 ± (252.73 - 254.44) ms241.98 ± (240.84 - 243.11) ms-4.6%
process.time_to_main_ms477.69 ± (477.11 - 478.27) ms496.49 ± (496.00 - 496.98) ms+3.9%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed53.97 ± (53.94 - 54.00) MB54.52 ± (54.49 - 54.55) MB+1.0%✅⬆️
runtime.dotnet.threads.count28 ± (28 - 28)28 ± (28 - 28)+0.3%✅⬆️
.NET 8 - Baseline
process.internal_duration_ms19.23 ± (19.20 - 19.25) ms19.25 ± (19.22 - 19.28) ms+0.1%✅⬆️
process.time_to_main_ms73.68 ± (73.57 - 73.78) ms73.78 ± (73.68 - 73.89) ms+0.1%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed12.27 ± (12.26 - 12.27) MB12.26 ± (12.25 - 12.26) MB-0.1%
runtime.dotnet.threads.count10 ± (10 - 10)10 ± (10 - 10)+0.0%
.NET 8 - Bailout
process.internal_duration_ms19.20 ± (19.17 - 19.22) ms19.16 ± (19.14 - 19.18) ms-0.2%
process.time_to_main_ms75.02 ± (74.95 - 75.09) ms74.88 ± (74.81 - 74.94) ms-0.2%
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed12.31 ± (12.30 - 12.32) MB12.30 ± (12.29 - 12.31) MB-0.1%
runtime.dotnet.threads.count11 ± (11 - 11)11 ± (11 - 11)+0.0%
.NET 8 - CallTarget+Inlining+NGEN
process.internal_duration_ms183.35 ± (182.57 - 184.13) ms168.30 ± (167.42 - 169.19) ms-8.2%
process.time_to_main_ms462.07 ± (461.57 - 462.58) ms478.12 ± (477.54 - 478.71) ms+3.5%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed41.23 ± (41.21 - 41.26) MB41.71 ± (41.67 - 41.74) MB+1.2%✅⬆️
runtime.dotnet.threads.count27 ± (26 - 27)27 ± (27 - 27)+0.5%✅⬆️

HttpMessageHandler

Metric Master (Mean ± 95% CI) Current (Mean ± 95% CI) Change Status
.NET Framework 4.8 - Baseline
duration196.14 ± (195.88 - 196.85) ms197.00 ± (196.95 - 197.83) ms+0.4%✅⬆️
.NET Framework 4.8 - Bailout
duration208.26 ± (211.40 - 215.30) ms199.96 ± (199.54 - 200.48) ms-4.0%
.NET Framework 4.8 - CallTarget+Inlining+NGEN
duration1155.35 ± (1155.24 - 1161.34) ms1231.76 ± (1231.89 - 1238.73) ms+6.6%❌⬆️
.NET Core 3.1 - Baseline
process.internal_duration_ms195.88 ± (195.49 - 196.27) ms195.31 ± (194.89 - 195.73) ms-0.3%
process.time_to_main_ms90.88 ± (90.59 - 91.18) ms90.59 ± (90.33 - 90.85) ms-0.3%
runtime.dotnet.exceptions.count3 ± (3 - 3)3 ± (3 - 3)+0.0%
runtime.dotnet.mem.committed20.80 ± (20.78 - 20.82) MB20.67 ± (20.65 - 20.69) MB-0.6%
runtime.dotnet.threads.count20 ± (20 - 20)20 ± (20 - 20)+0.1%✅⬆️
.NET Core 3.1 - Bailout
process.internal_duration_ms195.65 ± (195.20 - 196.11) ms194.26 ± (193.80 - 194.71) ms-0.7%
process.time_to_main_ms92.04 ± (91.78 - 92.31) ms91.75 ± (91.55 - 91.95) ms-0.3%
runtime.dotnet.exceptions.count3 ± (3 - 3)3 ± (3 - 3)+0.0%
runtime.dotnet.mem.committed20.72 ± (20.69 - 20.74) MB20.77 ± (20.75 - 20.80) MB+0.3%✅⬆️
runtime.dotnet.threads.count21 ± (21 - 21)21 ± (21 - 21)-1.0%
.NET Core 3.1 - CallTarget+Inlining+NGEN
process.internal_duration_ms446.29 ± (443.81 - 448.76) ms426.13 ± (424.24 - 428.02) ms-4.5%
process.time_to_main_ms516.56 ± (515.83 - 517.30) ms535.00 ± (534.13 - 535.86) ms+3.6%✅⬆️
runtime.dotnet.exceptions.count3 ± (3 - 3)3 ± (3 - 3)+0.0%
runtime.dotnet.mem.committed63.45 ± (63.34 - 63.57) MB63.33 ± (63.23 - 63.43) MB-0.2%
runtime.dotnet.threads.count29 ± (29 - 29)29 ± (29 - 29)+0.2%✅⬆️
.NET 6 - Baseline
process.internal_duration_ms199.60 ± (199.09 - 200.11) ms199.87 ± (199.39 - 200.36) ms+0.1%✅⬆️
process.time_to_main_ms78.99 ± (78.73 - 79.24) ms78.30 ± (78.14 - 78.45) ms-0.9%
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed20.95 ± (20.93 - 20.97) MB20.87 ± (20.85 - 20.89) MB-0.4%
runtime.dotnet.threads.count19 ± (19 - 19)19 ± (19 - 19)-0.1%
.NET 6 - Bailout
process.internal_duration_ms198.97 ± (198.53 - 199.40) ms198.91 ± (198.45 - 199.37) ms-0.0%
process.time_to_main_ms79.55 ± (79.35 - 79.76) ms79.73 ± (79.56 - 79.90) ms+0.2%✅⬆️
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed21.00 ± (20.98 - 21.03) MB21.02 ± (20.99 - 21.05) MB+0.1%✅⬆️
runtime.dotnet.threads.count20 ± (20 - 21)20 ± (20 - 20)-0.1%
.NET 6 - CallTarget+Inlining+NGEN
process.internal_duration_ms466.46 ± (464.76 - 468.17) ms479.50 ± (472.41 - 486.59) ms+2.8%✅⬆️
process.time_to_main_ms491.33 ± (490.65 - 492.00) ms508.99 ± (508.24 - 509.74) ms+3.6%✅⬆️
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed63.14 ± (63.04 - 63.25) MB63.44 ± (63.34 - 63.54) MB+0.5%✅⬆️
runtime.dotnet.threads.count30 ± (30 - 30)30 ± (30 - 30)+0.0%
.NET 8 - Baseline
process.internal_duration_ms197.37 ± (196.90 - 197.84) ms195.75 ± (195.31 - 196.19) ms-0.8%
process.time_to_main_ms77.92 ± (77.73 - 78.11) ms77.61 ± (77.40 - 77.81) ms-0.4%
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed16.32 ± (16.30 - 16.34) MB16.34 ± (16.31 - 16.36) MB+0.1%✅⬆️
runtime.dotnet.threads.count19 ± (19 - 19)19 ± (19 - 19)-0.7%
.NET 8 - Bailout
process.internal_duration_ms196.62 ± (196.14 - 197.10) ms196.73 ± (196.26 - 197.20) ms+0.1%✅⬆️
process.time_to_main_ms79.21 ± (79.01 - 79.41) ms78.70 ± (78.51 - 78.90) ms-0.6%
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed16.40 ± (16.38 - 16.42) MB16.35 ± (16.33 - 16.38) MB-0.3%
runtime.dotnet.threads.count20 ± (20 - 20)20 ± (20 - 20)-0.2%
.NET 8 - CallTarget+Inlining+NGEN
process.internal_duration_ms374.82 ± (373.35 - 376.28) ms459.07 ± (457.30 - 460.84) ms+22.5%✅⬆️
process.time_to_main_ms475.48 ± (474.87 - 476.09) ms489.04 ± (488.08 - 490.00) ms+2.9%✅⬆️
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed52.99 ± (52.95 - 53.03) MB55.34 ± (55.31 - 55.38) MB+4.4%✅⬆️
runtime.dotnet.threads.count29 ± (29 - 29)29 ± (29 - 29)+1.3%✅⬆️
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 (8175) - mean (68ms)  : 67, 70
    master - mean (68ms)  : 67, 70

    section Bailout
    This PR (8175) - mean (72ms)  : 71, 73
    master - mean (72ms)  : 71, 73

    section CallTarget+Inlining+NGEN
    This PR (8175) - mean (1,029ms)  : 971, 1088
    master - mean (1,037ms)  : 976, 1098

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 (8175) - mean (115ms)  : 111, 119
    master - mean (115ms)  : 112, 117

    section Bailout
    This PR (8175) - mean (116ms)  : 114, 117
    master - mean (115ms)  : 114, 116

    section CallTarget+Inlining+NGEN
    This PR (8175) - mean (806ms)  : 775, 838
    master - mean (796ms)  : 746, 846

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

    section Bailout
    This PR (8175) - mean (102ms)  : 101, 103
    master - mean (101ms)  : 100, 103

    section CallTarget+Inlining+NGEN
    This PR (8175) - mean (773ms)  : 741, 805
    master - mean (770ms)  : 744, 795

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

    section Bailout
    This PR (8175) - mean (100ms)  : 99, 102
    master - mean (101ms)  : 100, 102

    section CallTarget+Inlining+NGEN
    This PR (8175) - mean (679ms)  : 657, 700
    master - mean (681ms)  : 657, 704

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 (8175) - mean (197ms)  : 192, 202
    master - mean (196ms)  : 191, 202

    section Bailout
    This PR (8175) - mean (200ms)  : 195, 205
    master - mean (213ms)  : 183, 244

    section CallTarget+Inlining+NGEN
    This PR (8175) - mean (1,235ms)  : crit, 1186, 1284
    master - mean (1,158ms)  : 1114, 1203

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 (8175) - mean (295ms)  : 288, 302
    master - mean (296ms)  : 290, 302

    section Bailout
    This PR (8175) - mean (296ms)  : 290, 302
    master - mean (297ms)  : 290, 304

    section CallTarget+Inlining+NGEN
    This PR (8175) - mean (1,001ms)  : 961, 1040
    master - mean (1,001ms)  : 953, 1049

Loading
HttpMessageHandler (.NET 6)
gantt
    title Execution time (ms) HttpMessageHandler (.NET 6)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8175) - mean (287ms)  : 279, 295
    master - mean (288ms)  : 278, 299

    section Bailout
    This PR (8175) - mean (288ms)  : 280, 296
    master - mean (288ms)  : 282, 293

    section CallTarget+Inlining+NGEN
    This PR (8175) - mean (1,026ms)  : 912, 1140
    master - mean (997ms)  : 948, 1046

Loading
HttpMessageHandler (.NET 8)
gantt
    title Execution time (ms) HttpMessageHandler (.NET 8)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8175) - mean (284ms)  : 279, 290
    master - mean (287ms)  : 278, 296

    section Bailout
    This PR (8175) - mean (286ms)  : 281, 292
    master - mean (286ms)  : 279, 294

    section CallTarget+Inlining+NGEN
    This PR (8175) - mean (982ms)  : crit, 957, 1007
    master - mean (889ms)  : 859, 919

Loading

@pierotibou pierotibou changed the title Cache MessagePack strings rather than bytes Cache MessagePack bytes rather than just the bytes Feb 7, 2026
@pierotibou pierotibou added area:tracer The core tracer library (Datadog.Trace, does not include OpenTracing, native code, or integrations) AI Generated Largely based on code generated by an AI or LLM. This label is the same across all dd-trace-* repos labels Feb 7, 2026
Copy link
Member

@andrewlock andrewlock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems reasonable to me, thanks!

private readonly byte[] _parentIdBytes = StringEncoding.UTF8.GetBytes("parent_id");
private readonly byte[] _errorBytes = StringEncoding.UTF8.GetBytes("error");
private readonly byte[] _metaStructBytes = StringEncoding.UTF8.GetBytes("meta_struct");
private readonly byte[] _traceIdBytes = MessagePackSerializer.Serialize("trace_id");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no reason we can't do this at compile time, right, very similar to what we do for TagList bytes, by using a small source generator 🤔 Would remove a small amount of startup time theoretically... Also theoretically we could remove some of those allocations in .NET Core+ by using ReadOnlySpan<byte> too, but I'm not sure the messagepackserializer.WriteRaw would support that, so maybe not worth it...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, I thought about it after reading a related comment on SLack. But I've shied away from it. Can give it a try through in a next PR though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 34f7afe

span.Metrics[Datadog.Trace.Metrics.TopLevelSpan].Should().Be(1.0, "root span should have _dd.top_level=1.0");

// ===== Verify span fields that use cached names =====
span.Service.Should().NotBeNullOrEmpty("service name should be present");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tiny nit: probably over-asserting a bit in this test IMO - this test is liable to break in the future if we change/refactor things, but it's not too bad.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I'll simplify it a bit before merging then. Thanks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in c91512b but I haven't simplified so much, I felt the rest was relevant.

@pierotibou
Copy link
Contributor Author

pierotibou commented Feb 9, 2026

I was surprised to see the degradation on the benchmark. After looking, IIUC those bench run the Sample app each time so it is actually possible to have a degradation of startup time and overall time (even though given the improvement I'm still surprised of). So TLDR, I can ignore this particular feedback even though it advocates for pushing this work at compile time. Right @andrewlock ?

@andrewlock
Copy link
Member

I was surprised to see the degradation on the benchmark. After looking, IIUC those bench run the Sample app each time so it is actually possible to have a degradation of startup time and overall time (even though given the improvement I'm still surprised of). So TLDR, I can ignore this particular feedback even though it advocates for pushing this work at compile time. Right @andrewlock ?

Yeah, those are explicitly trying to look at the impact on startup time, by running a relatively short-lived app repeatedly. There is naturally some variance though (which has recently got worse even in the baseline scenario) and so given that the other .NET Framework app and all the other TFMs didn't see a significant hit, I would not be too worried about it I think.

To be safe, you could try re-running the execution time benchmarks to see if you get the same result. If so, maybe there's something else going on there we need to look into 🤷‍♂️

@pierotibou
Copy link
Contributor Author

To be safe, you could try re-running the execution time benchmarks to see if you get the same result. If so, maybe there's something else going on there we need to look into 🤷‍♂️

It ran twice as I forced push. Not sure it was the same test, but IIRC it was indeed raising an issue on NET FX the first time as well. Anyway, this PR sits on top of the ProcessTags one, so maybe I'll have time to investigate compilation changes by the time I can merge this one

- Replicated what was done for TagsList but it seems weird to have the #if NETCOREAPP in a .net 6 generated file
- There are still twice the type field, it's iso with what we had but I need to remove it as it's useless.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI Generated Largely based on code generated by an AI or LLM. This label is the same across all dd-trace-* repos area:tracer The core tracer library (Datadog.Trace, does not include OpenTracing, native code, or integrations)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants