[Code Origin] Reduce endpoint detector allocations#8588
Conversation
There was a problem hiding this comment.
Pull request overview
This PR optimizes span code-origin endpoint detection and PDB sequence-point caching to reduce allocations and CPU overhead during per-assembly cache population (improving cold-path latency and GC pressure).
Changes:
- Reworked
EndpointDetectorto avoid metadata string materialization/collections and to classify endpoint types via a single base-type walk with direct handle comparisons. - Updated
SpanCodeOriginto populate the per-assembly endpoint sequence-point cache via a struct consumer and store only usable entries in a mutable dictionary. - Expanded
EndpointDetectorTestswith additional coverage for inherited controller attributes, generic hub bases, and a “no endpoints” assembly scenario.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| tracer/test/Datadog.Trace.Tests/Debugger/EndpointDetectorTests.cs | Updates tests for the new consumer-based endpoint enumeration and adds new scenarios (inherited attributes, generic hub base, no-endpoints assembly). |
| tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOrigin.cs | Switches endpoint token enumeration to a consumer model and replaces the immutable cache with a dictionary containing only usable sequence points. |
| tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/EndpointDetector.cs | Refactors endpoint detection to reduce allocations by using handle-based comparisons and a consumer API for results. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Execution-Time Benchmarks Report ⏱️Execution-time results for samples comparing This PR (8588) 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 (8588) - mean (75ms) : 70, 79
master - mean (73ms) : 71, 76
section Bailout
This PR (8588) - mean (78ms) : 75, 80
master - mean (77ms) : 75, 79
section CallTarget+Inlining+NGEN
This PR (8588) - mean (1,130ms) : 1080, 1180
master - mean (1,128ms) : 1076, 1179
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 (8588) - mean (114ms) : 108, 121
master - mean (114ms) : 111, 118
section Bailout
This PR (8588) - mean (115ms) : 112, 118
master - mean (119ms) : 113, 124
section CallTarget+Inlining+NGEN
This PR (8588) - mean (807ms) : 776, 837
master - mean (810ms) : 785, 835
FakeDbCommand (.NET 6)gantt
title Execution time (ms) FakeDbCommand (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8588) - mean (103ms) : 96, 110
master - mean (103ms) : 97, 109
section Bailout
This PR (8588) - mean (102ms) : 98, 105
master - mean (101ms) : 99, 104
section CallTarget+Inlining+NGEN
This PR (8588) - mean (946ms) : 906, 986
master - mean (946ms) : 898, 994
FakeDbCommand (.NET 8)gantt
title Execution time (ms) FakeDbCommand (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8588) - mean (103ms) : 98, 109
master - mean (103ms) : 96, 109
section Bailout
This PR (8588) - mean (104ms) : 98, 110
master - mean (105ms) : 99, 111
section CallTarget+Inlining+NGEN
This PR (8588) - mean (835ms) : 779, 892
master - mean (831ms) : 791, 871
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 (8588) - mean (208ms) : 198, 217
master - mean (207ms) : 194, 220
section Bailout
This PR (8588) - mean (210ms) : 202, 219
master - mean (210ms) : 201, 219
section CallTarget+Inlining+NGEN
This PR (8588) - mean (1,270ms) : 1229, 1312
master - mean (1,269ms) : 1220, 1319
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 (8588) - mean (297ms) : 283, 310
master - mean (298ms) : 280, 315
section Bailout
This PR (8588) - mean (295ms) : 282, 307
master - mean (300ms) : 285, 316
section CallTarget+Inlining+NGEN
This PR (8588) - mean (995ms) : 976, 1014
master - mean (1,005ms) : 970, 1041
HttpMessageHandler (.NET 6)gantt
title Execution time (ms) HttpMessageHandler (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8588) - mean (285ms) : 272, 297
master - mean (287ms) : 270, 303
section Bailout
This PR (8588) - mean (286ms) : 273, 298
master - mean (286ms) : 272, 301
section CallTarget+Inlining+NGEN
This PR (8588) - mean (1,167ms) : 1127, 1207
master - mean (1,167ms) : 1120, 1215
HttpMessageHandler (.NET 8)gantt
title Execution time (ms) HttpMessageHandler (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8588) - mean (284ms) : 272, 296
master - mean (285ms) : 270, 300
section Bailout
This PR (8588) - mean (286ms) : 274, 298
master - mean (285ms) : 271, 299
section CallTarget+Inlining+NGEN
This PR (8588) - mean (1,065ms) : 990, 1140
master - mean (1,064ms) : 984, 1145
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
BenchmarksBenchmark execution time: 2026-05-08 19:13:31 Comparing candidate commit 8e4c1e6 in PR branch Some scenarios are present only in baseline or only in candidate runs. If you didn't create or remove some scenarios in your branch, this maybe a sign of crashed benchmarks 💥💥💥 Scenarios present only in baseline:
Found 4 performance improvements and 2 performance regressions! Performance is the same for 47 metrics, 19 unstable metrics, 89 known flaky benchmarks, 37 flaky benchmarks without significant changes.
|
2470486 to
62657a0
Compare
Avoid string materialization and redundant metadata walks when detecting endpoint methods for span code origin. Also reduce sequence-point cache churn by storing only useful entries in a mutable dictionary.
fce44a3 to
8e4c1e6
Compare
Summary of changes
EndpointDetectorby avoiding metadata string materialization, removing matcher objects/arrays, and using direct metadata handle comparisons.Reason for change
Implementation details
MetadataName[]matching with specialized namespace/name predicates usingMetadataReader.StringComparer.SpanCodeOriginendpoint cache storage fromImmutableDictionary<int, CachedSequencePoint?>with null entries toDictionary<int, CachedSequencePoint>containing only usable sequence points.net8.0, Release, short job, 3 iterations):DetectEndpointMethodTokensDetectEndpointMethodTokensDeepInheritancefirst-call mean allocation dropped from ~18.82 MB to ~1 KB.Benchmark scenarios:
MixedSmall: realistic small mix of controllers, Razor Pages, SignalR, and generic hubs.NoEndpointsLarge/ManyPlainTypes: large non-endpoint assemblies to measure fast rejection and attribute scan cost.DeepInheritance: long base-class chains to stress base-type walking.RazorPagesManyHelpers: PageModels with many helper methods to stress handler filtering.SignalRWithAccessors: hubs with methods plus property/event accessors to validate special-name filtering.TypeSpecGenericBases: generic base types likeHub<T>and nested wrappers to stress TypeSpecification parsing.Test coverage
EndpointDetectorTests