Improve peer-tag calculation fast-path performance#8445
Conversation
Execution-Time Benchmarks Report ⏱️Execution-time results for samples comparing This PR (8445) and master. ✅ No regressions detected - check the details below Full Metrics ComparisonFakeDbCommand
HttpMessageHandler
Comparison explanationExecution-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:
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 chartsFakeDbCommand (.NET Framework 4.8)gantt
title Execution time (ms) FakeDbCommand (.NET Framework 4.8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8445) - mean (73ms) : 70, 77
master - mean (73ms) : 70, 76
section Bailout
This PR (8445) - mean (77ms) : 75, 78
master - mean (79ms) : 75, 84
section CallTarget+Inlining+NGEN
This PR (8445) - mean (1,080ms) : 1018, 1142
master - mean (1,082ms) : 1029, 1134
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 (8445) - mean (114ms) : 107, 120
master - mean (115ms) : 108, 122
section Bailout
This PR (8445) - mean (114ms) : 111, 117
master - mean (115ms) : 109, 122
section CallTarget+Inlining+NGEN
This PR (8445) - mean (797ms) : 770, 823
master - mean (795ms) : 772, 818
FakeDbCommand (.NET 6)gantt
title Execution time (ms) FakeDbCommand (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8445) - mean (99ms) : 96, 103
master - mean (103ms) : 97, 110
section Bailout
This PR (8445) - mean (104ms) : 99, 108
master - mean (103ms) : 98, 107
section CallTarget+Inlining+NGEN
This PR (8445) - mean (940ms) : 905, 976
master - mean (940ms) : 906, 973
FakeDbCommand (.NET 8)gantt
title Execution time (ms) FakeDbCommand (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8445) - mean (100ms) : 97, 103
master - mean (101ms) : 98, 103
section Bailout
This PR (8445) - mean (100ms) : 98, 102
master - mean (104ms) : 100, 109
section CallTarget+Inlining+NGEN
This PR (8445) - mean (824ms) : 783, 866
master - mean (826ms) : 784, 869
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 (8445) - mean (192ms) : 187, 196
master - mean (191ms) : 187, 195
section Bailout
This PR (8445) - mean (195ms) : 192, 198
master - mean (195ms) : 192, 198
section CallTarget+Inlining+NGEN
This PR (8445) - mean (1,153ms) : 1106, 1201
master - mean (1,160ms) : 1100, 1219
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 (8445) - mean (275ms) : 271, 278
master - mean (274ms) : 269, 280
section Bailout
This PR (8445) - mean (275ms) : 271, 280
master - mean (274ms) : 271, 277
section CallTarget+Inlining+NGEN
This PR (8445) - mean (930ms) : 910, 951
master - mean (927ms) : 899, 956
HttpMessageHandler (.NET 6)gantt
title Execution time (ms) HttpMessageHandler (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8445) - mean (268ms) : 264, 272
master - mean (268ms) : 264, 272
section Bailout
This PR (8445) - mean (268ms) : 265, 270
master - mean (268ms) : 265, 271
section CallTarget+Inlining+NGEN
This PR (8445) - mean (1,137ms) : 1105, 1169
master - mean (1,132ms) : 1082, 1183
HttpMessageHandler (.NET 8)gantt
title Execution time (ms) HttpMessageHandler (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8445) - mean (266ms) : 262, 269
master - mean (266ms) : 262, 270
section Bailout
This PR (8445) - mean (266ms) : 262, 269
master - mean (266ms) : 263, 269
section CallTarget+Inlining+NGEN
This PR (8445) - mean (1,020ms) : 980, 1059
master - mean (1,016ms) : 981, 1051
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
BenchmarksBenchmark execution time: 2026-04-21 07:11:42 Comparing candidate commit 4ee6bae in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 27 metrics, 0 unstable metrics, 87 known flaky benchmarks.
|
| else | ||
| { | ||
| Interlocked.Exchange(ref _peerTagKeys, config.PeerTags); | ||
| Log.Warning("Stats computation disabled because the detected agent does not support this feature."); |
There was a problem hiding this comment.
We're almost certainly going to need an override for this when using the Rust agents, but that's a separate issue...
There was a problem hiding this comment.
Yeah, that whole question of how we identify the rust agent is a whole open question...
| var spanMetaStructs = jObject["span_meta_structs"]?.Value<bool>() ?? false; | ||
| var spanEvents = jObject["span_events"]?.Value<bool>() ?? false; | ||
| var peerTags = (jObject["peer_tags"] as JArray)?.Values<string>().Where(x => !string.IsNullOrEmpty(x)).Distinct().OrderBy(x => x).ToList(); | ||
| var peerTags = (jObject["peer_tags"] as JArray)?.Values<string>().ToList(); |
58d4473 to
9daade0
Compare
e94c3c9 to
9b41576
Compare
9daade0 to
7817c48
Compare
9b41576 to
25dabec
Compare
25dabec to
00ff8c1
Compare
7817c48 to
105b79a
Compare
105b79a to
4ee6bae
Compare
## Summary of changes Removes the minimum-agent version check from the client-side-stats check ## Reason for change We didn't have it before, which means people who previously had stats, suddenly won't have them. Also other agents (like bottlecap etc) don't have the version. And we haven't figured out how to handle that yet. While CSS is off by default, this seems like the best option to take. ## Implementation details Remove the code that is checking for CSS support. ## Test coverage Covered by existing (and removed a test for the behaviour) ## Other details Follows on from - #8417 - #8418 - #8420 - #8435 - #8436 - #8444 - #8445
Summary of changes
Improves the performance of peer-tag hash calculation for "fast path" cases (where there's an existing bucket)
Reason for change
We have to calculate the hash of all peer tags as part of client-side-stats bucketing calculations, which involves encoding them as utf-8. Additionally, when we send the buckets, we have to send the tags as utf-8.
In the initial CSS 1.2.0 implementation, we encoded the tags every-time we ran a calculation, which would allocate for every span that had peer tags, generally quite expensive. In this PR, we switch to doing the encoding twice: once with a zero-allocation implementation (amortized 0 on .NET Framework) to calculate the hash, and then, if we need the "real" encoded tags, then we do that encoding again.
Implementation details
key:valuestackallocfor .NET Core, and array pool implementation for .NET Framework etcBuildKeytoGetEncodedPeerTags(i.e. is this a "base service" only tag, if so, what's the tag value, otherwise how big does the peer tag list need to be)There are some possible future optimizations, not implemented in this PR:
tag:value, allow writing the pre-computedbyte[]toMessagePackBinaryand appending thevalue. This is doable, but requires updating theMessagePackBinaryimplementation to support it, so I considered it out of scope for nowTest coverage
Mostly covered by existing tests, but added some additional unit tests that directly compare the hashing to values used in Go agent tests.
Additionally did some benchmarking. The key thing is that the
ClientSpanWithPeerTagspath is zero-allocation (and it's nice that the slow-path is still lower allocation than before, even if it's slower over all)Details
Other details
Part of a stack:
charhelpers likeIsAsciiHexDigit#8417IpAddressObfuscationUtilfor use with client-side-stats #8418ContainsSpanIdtoSpanCollection#8435FnvHash64.GenerateHash()#8444