Cache MessagePack bytes rather than just the bytes#8175
Cache MessagePack bytes rather than just the bytes#8175pierotibou wants to merge 9 commits intopierre/tagsfrom
Conversation
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>
There was a problem hiding this comment.
💡 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".
tracer/src/Datadog.Trace/Agent/MessagePack/SpanMessagePackFormatter.cs
Outdated
Show resolved
Hide resolved
BenchmarksBenchmark execution time: 2026-02-18 22:31:21 Comparing candidate commit c91512b in PR branch Found 8 performance improvements and 16 performance regressions! Performance is the same for 156 metrics, 12 unstable metrics. scenario:Benchmarks.Trace.AgentWriterBenchmark.WriteAndFlushEnrichedTraces net472
scenario:Benchmarks.Trace.AgentWriterBenchmark.WriteAndFlushEnrichedTraces net6.0
scenario:Benchmarks.Trace.AgentWriterBenchmark.WriteAndFlushEnrichedTraces netcoreapp3.1
scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleSimpleBody net6.0
scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleSimpleBody netcoreapp3.1
scenario:Benchmarks.Trace.Asm.AppSecEncoderBenchmark.EncodeLegacyArgs netcoreapp3.1
scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSlice net6.0
scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearch net472
scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearch net6.0
scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearch netcoreapp3.1
scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearchAsync net472
scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearchAsync net6.0
scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearchAsync netcoreapp3.1
scenario:Benchmarks.Trace.HttpClientBenchmark.SendAsync net472
scenario:Benchmarks.Trace.HttpClientBenchmark.SendAsync netcoreapp3.1
scenario:Benchmarks.Trace.RedisBenchmark.SendReceive net472
scenario:Benchmarks.Trace.RedisBenchmark.SendReceive net6.0
scenario:Benchmarks.Trace.RedisBenchmark.SendReceive netcoreapp3.1
scenario:Benchmarks.Trace.SingleSpanAspNetCoreBenchmark.SingleSpanAspNetCore net6.0
scenario:Benchmarks.Trace.SpanBenchmark.StartFinishScope net6.0
scenario:Benchmarks.Trace.SpanBenchmark.StartFinishSpan netcoreapp3.1
|
Execution-Time Benchmarks Report ⏱️Execution-time results for samples comparing This PR (8175) and master.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Metric | Master (Mean ± 95% CI) | Current (Mean ± 95% CI) | Change | Status |
|---|---|---|---|---|
| .NET Framework 4.8 - CallTarget+Inlining+NGEN | ||||
| duration | 1155.35 ± (1155.24 - 1161.34) ms | 1231.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 | ||||
| duration | 68.20 ± (68.19 - 68.40) ms | 68.19 ± (68.17 - 68.46) ms | -0.0% | ✅ |
| .NET Framework 4.8 - Bailout | ||||
| duration | 71.97 ± (71.88 - 72.15) ms | 71.83 ± (71.76 - 72.03) ms | -0.2% | ✅ |
| .NET Framework 4.8 - CallTarget+Inlining+NGEN | ||||
| duration | 1029.67 ± (1032.55 - 1041.04) ms | 1022.70 ± (1025.29 - 1033.24) ms | -0.7% | ✅ |
| .NET Core 3.1 - Baseline | ||||
| process.internal_duration_ms | 22.43 ± (22.38 - 22.48) ms | 22.52 ± (22.49 - 22.55) ms | +0.4% | ✅⬆️ |
| process.time_to_main_ms | 86.38 ± (86.19 - 86.56) ms | 86.26 ± (86.11 - 86.41) ms | -0.1% | ✅ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 15.50 ± (15.49 - 15.50) MB | 15.51 ± (15.51 - 15.52) MB | +0.1% | ✅⬆️ |
| runtime.dotnet.threads.count | 12 ± (12 - 12) | 12 ± (12 - 12) | +0.0% | ✅ |
| .NET Core 3.1 - Bailout | ||||
| process.internal_duration_ms | 22.24 ± (22.22 - 22.27) ms | 22.39 ± (22.37 - 22.42) ms | +0.7% | ✅⬆️ |
| process.time_to_main_ms | 87.39 ± (87.29 - 87.48) ms | 87.55 ± (87.43 - 87.68) ms | +0.2% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 15.53 ± (15.53 - 15.54) MB | 15.56 ± (15.55 - 15.56) MB | +0.2% | ✅⬆️ |
| runtime.dotnet.threads.count | 13 ± (13 - 13) | 13 ± (13 - 13) | +0.0% | ✅ |
| .NET Core 3.1 - CallTarget+Inlining+NGEN | ||||
| process.internal_duration_ms | 258.30 ± (255.23 - 261.37) ms | 245.67 ± (244.19 - 247.15) ms | -4.9% | ✅ |
| process.time_to_main_ms | 504.18 ± (503.58 - 504.79) ms | 524.97 ± (524.21 - 525.72) ms | +4.1% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 53.28 ± (53.26 - 53.31) MB | 53.65 ± (53.62 - 53.68) MB | +0.7% | ✅⬆️ |
| runtime.dotnet.threads.count | 28 ± (28 - 28) | 28 ± (28 - 28) | +1.8% | ✅⬆️ |
| .NET 6 - Baseline | ||||
| process.internal_duration_ms | 20.99 ± (20.97 - 21.02) ms | 20.94 ± (20.91 - 20.96) ms | -0.3% | ✅ |
| process.time_to_main_ms | 74.34 ± (74.24 - 74.45) ms | 74.42 ± (74.29 - 74.54) ms | +0.1% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 15.22 ± (15.22 - 15.22) MB | 15.23 ± (15.23 - 15.24) MB | +0.1% | ✅⬆️ |
| runtime.dotnet.threads.count | 10 ± (10 - 10) | 10 ± (10 - 10) | +0.0% | ✅ |
| .NET 6 - Bailout | ||||
| process.internal_duration_ms | 20.95 ± (20.93 - 20.97) ms | 20.89 ± (20.87 - 20.92) ms | -0.3% | ✅ |
| process.time_to_main_ms | 75.40 ± (75.33 - 75.47) ms | 75.68 ± (75.62 - 75.74) ms | +0.4% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 15.33 ± (15.33 - 15.33) MB | 15.33 ± (15.33 - 15.34) MB | +0.0% | ✅⬆️ |
| runtime.dotnet.threads.count | 11 ± (11 - 11) | 11 ± (11 - 11) | +0.0% | ✅ |
| .NET 6 - CallTarget+Inlining+NGEN | ||||
| process.internal_duration_ms | 253.58 ± (252.73 - 254.44) ms | 241.98 ± (240.84 - 243.11) ms | -4.6% | ✅ |
| process.time_to_main_ms | 477.69 ± (477.11 - 478.27) ms | 496.49 ± (496.00 - 496.98) ms | +3.9% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 53.97 ± (53.94 - 54.00) MB | 54.52 ± (54.49 - 54.55) MB | +1.0% | ✅⬆️ |
| runtime.dotnet.threads.count | 28 ± (28 - 28) | 28 ± (28 - 28) | +0.3% | ✅⬆️ |
| .NET 8 - Baseline | ||||
| process.internal_duration_ms | 19.23 ± (19.20 - 19.25) ms | 19.25 ± (19.22 - 19.28) ms | +0.1% | ✅⬆️ |
| process.time_to_main_ms | 73.68 ± (73.57 - 73.78) ms | 73.78 ± (73.68 - 73.89) ms | +0.1% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 12.27 ± (12.26 - 12.27) MB | 12.26 ± (12.25 - 12.26) MB | -0.1% | ✅ |
| runtime.dotnet.threads.count | 10 ± (10 - 10) | 10 ± (10 - 10) | +0.0% | ✅ |
| .NET 8 - Bailout | ||||
| process.internal_duration_ms | 19.20 ± (19.17 - 19.22) ms | 19.16 ± (19.14 - 19.18) ms | -0.2% | ✅ |
| process.time_to_main_ms | 75.02 ± (74.95 - 75.09) ms | 74.88 ± (74.81 - 74.94) ms | -0.2% | ✅ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 12.31 ± (12.30 - 12.32) MB | 12.30 ± (12.29 - 12.31) MB | -0.1% | ✅ |
| runtime.dotnet.threads.count | 11 ± (11 - 11) | 11 ± (11 - 11) | +0.0% | ✅ |
| .NET 8 - CallTarget+Inlining+NGEN | ||||
| process.internal_duration_ms | 183.35 ± (182.57 - 184.13) ms | 168.30 ± (167.42 - 169.19) ms | -8.2% | ✅ |
| process.time_to_main_ms | 462.07 ± (461.57 - 462.58) ms | 478.12 ± (477.54 - 478.71) ms | +3.5% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 0 ± (0 - 0) | 0 ± (0 - 0) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 41.23 ± (41.21 - 41.26) MB | 41.71 ± (41.67 - 41.74) MB | +1.2% | ✅⬆️ |
| runtime.dotnet.threads.count | 27 ± (26 - 27) | 27 ± (27 - 27) | +0.5% | ✅⬆️ |
HttpMessageHandler
| Metric | Master (Mean ± 95% CI) | Current (Mean ± 95% CI) | Change | Status |
|---|---|---|---|---|
| .NET Framework 4.8 - Baseline | ||||
| duration | 196.14 ± (195.88 - 196.85) ms | 197.00 ± (196.95 - 197.83) ms | +0.4% | ✅⬆️ |
| .NET Framework 4.8 - Bailout | ||||
| duration | 208.26 ± (211.40 - 215.30) ms | 199.96 ± (199.54 - 200.48) ms | -4.0% | ✅ |
| .NET Framework 4.8 - CallTarget+Inlining+NGEN | ||||
| duration | 1155.35 ± (1155.24 - 1161.34) ms | 1231.76 ± (1231.89 - 1238.73) ms | +6.6% | ❌⬆️ |
| .NET Core 3.1 - Baseline | ||||
| process.internal_duration_ms | 195.88 ± (195.49 - 196.27) ms | 195.31 ± (194.89 - 195.73) ms | -0.3% | ✅ |
| process.time_to_main_ms | 90.88 ± (90.59 - 91.18) ms | 90.59 ± (90.33 - 90.85) ms | -0.3% | ✅ |
| runtime.dotnet.exceptions.count | 3 ± (3 - 3) | 3 ± (3 - 3) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 20.80 ± (20.78 - 20.82) MB | 20.67 ± (20.65 - 20.69) MB | -0.6% | ✅ |
| runtime.dotnet.threads.count | 20 ± (20 - 20) | 20 ± (20 - 20) | +0.1% | ✅⬆️ |
| .NET Core 3.1 - Bailout | ||||
| process.internal_duration_ms | 195.65 ± (195.20 - 196.11) ms | 194.26 ± (193.80 - 194.71) ms | -0.7% | ✅ |
| process.time_to_main_ms | 92.04 ± (91.78 - 92.31) ms | 91.75 ± (91.55 - 91.95) ms | -0.3% | ✅ |
| runtime.dotnet.exceptions.count | 3 ± (3 - 3) | 3 ± (3 - 3) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 20.72 ± (20.69 - 20.74) MB | 20.77 ± (20.75 - 20.80) MB | +0.3% | ✅⬆️ |
| runtime.dotnet.threads.count | 21 ± (21 - 21) | 21 ± (21 - 21) | -1.0% | ✅ |
| .NET Core 3.1 - CallTarget+Inlining+NGEN | ||||
| process.internal_duration_ms | 446.29 ± (443.81 - 448.76) ms | 426.13 ± (424.24 - 428.02) ms | -4.5% | ✅ |
| process.time_to_main_ms | 516.56 ± (515.83 - 517.30) ms | 535.00 ± (534.13 - 535.86) ms | +3.6% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 3 ± (3 - 3) | 3 ± (3 - 3) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 63.45 ± (63.34 - 63.57) MB | 63.33 ± (63.23 - 63.43) MB | -0.2% | ✅ |
| runtime.dotnet.threads.count | 29 ± (29 - 29) | 29 ± (29 - 29) | +0.2% | ✅⬆️ |
| .NET 6 - Baseline | ||||
| process.internal_duration_ms | 199.60 ± (199.09 - 200.11) ms | 199.87 ± (199.39 - 200.36) ms | +0.1% | ✅⬆️ |
| process.time_to_main_ms | 78.99 ± (78.73 - 79.24) ms | 78.30 ± (78.14 - 78.45) ms | -0.9% | ✅ |
| runtime.dotnet.exceptions.count | 4 ± (4 - 4) | 4 ± (4 - 4) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 20.95 ± (20.93 - 20.97) MB | 20.87 ± (20.85 - 20.89) MB | -0.4% | ✅ |
| runtime.dotnet.threads.count | 19 ± (19 - 19) | 19 ± (19 - 19) | -0.1% | ✅ |
| .NET 6 - Bailout | ||||
| process.internal_duration_ms | 198.97 ± (198.53 - 199.40) ms | 198.91 ± (198.45 - 199.37) ms | -0.0% | ✅ |
| process.time_to_main_ms | 79.55 ± (79.35 - 79.76) ms | 79.73 ± (79.56 - 79.90) ms | +0.2% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 4 ± (4 - 4) | 4 ± (4 - 4) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 21.00 ± (20.98 - 21.03) MB | 21.02 ± (20.99 - 21.05) MB | +0.1% | ✅⬆️ |
| runtime.dotnet.threads.count | 20 ± (20 - 21) | 20 ± (20 - 20) | -0.1% | ✅ |
| .NET 6 - CallTarget+Inlining+NGEN | ||||
| process.internal_duration_ms | 466.46 ± (464.76 - 468.17) ms | 479.50 ± (472.41 - 486.59) ms | +2.8% | ✅⬆️ |
| process.time_to_main_ms | 491.33 ± (490.65 - 492.00) ms | 508.99 ± (508.24 - 509.74) ms | +3.6% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 4 ± (4 - 4) | 4 ± (4 - 4) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 63.14 ± (63.04 - 63.25) MB | 63.44 ± (63.34 - 63.54) MB | +0.5% | ✅⬆️ |
| runtime.dotnet.threads.count | 30 ± (30 - 30) | 30 ± (30 - 30) | +0.0% | ✅ |
| .NET 8 - Baseline | ||||
| process.internal_duration_ms | 197.37 ± (196.90 - 197.84) ms | 195.75 ± (195.31 - 196.19) ms | -0.8% | ✅ |
| process.time_to_main_ms | 77.92 ± (77.73 - 78.11) ms | 77.61 ± (77.40 - 77.81) ms | -0.4% | ✅ |
| runtime.dotnet.exceptions.count | 4 ± (4 - 4) | 4 ± (4 - 4) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 16.32 ± (16.30 - 16.34) MB | 16.34 ± (16.31 - 16.36) MB | +0.1% | ✅⬆️ |
| runtime.dotnet.threads.count | 19 ± (19 - 19) | 19 ± (19 - 19) | -0.7% | ✅ |
| .NET 8 - Bailout | ||||
| process.internal_duration_ms | 196.62 ± (196.14 - 197.10) ms | 196.73 ± (196.26 - 197.20) ms | +0.1% | ✅⬆️ |
| process.time_to_main_ms | 79.21 ± (79.01 - 79.41) ms | 78.70 ± (78.51 - 78.90) ms | -0.6% | ✅ |
| runtime.dotnet.exceptions.count | 4 ± (4 - 4) | 4 ± (4 - 4) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 16.40 ± (16.38 - 16.42) MB | 16.35 ± (16.33 - 16.38) MB | -0.3% | ✅ |
| runtime.dotnet.threads.count | 20 ± (20 - 20) | 20 ± (20 - 20) | -0.2% | ✅ |
| .NET 8 - CallTarget+Inlining+NGEN | ||||
| process.internal_duration_ms | 374.82 ± (373.35 - 376.28) ms | 459.07 ± (457.30 - 460.84) ms | +22.5% | ✅⬆️ |
| process.time_to_main_ms | 475.48 ± (474.87 - 476.09) ms | 489.04 ± (488.08 - 490.00) ms | +2.9% | ✅⬆️ |
| runtime.dotnet.exceptions.count | 4 ± (4 - 4) | 4 ± (4 - 4) | +0.0% | ✅ |
| runtime.dotnet.mem.committed | 52.99 ± (52.95 - 53.03) MB | 55.34 ± (55.31 - 55.38) MB | +4.4% | ✅⬆️ |
| runtime.dotnet.threads.count | 29 ± (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
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
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
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
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
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
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
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
098a5db to
769c3e9
Compare
andrewlock
left a comment
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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.
tracer/test/Datadog.Trace.Tests/Agent/MessagePack/SpanMessagePackFormatterTests.cs
Outdated
Show resolved
Hide resolved
| 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"); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Ok I'll simplify it a bit before merging then. Thanks.
There was a problem hiding this comment.
done in c91512b but I haven't simplified so much, I felt the rest was relevant.
|
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 🤷♂️ |
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 |
48dd387 to
5d9a366
Compare
- 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.
Summary of changes
In
SpanMessagePackFormattercache 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 fromMessagePackStringCacheas they never change.Reason for change
Mostly to make the code consistent with
MessagePackStringCachewhere we were using that method. I chose that solution instead of impactingMessagePackStringCacheas It's a micro optim.Also the cache should contain only values that can change.
Implementation details
MessagePackStringCachetoSpanMessagePackFormatter.MessagePackSerializer.SerializeandWriteRawinstead ofStringEncoding.UTF8.GetBytesandWriteStringBytesdotnetconstant 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:
After: