Cache span.kind as byte ordinal for fast isOutbound()#11116
Open
Cache span.kind as byte ordinal for fast isOutbound()#11116
Conversation
DDSpanContext.getTag("span.kind") was consuming ~14% of foreground CPU
in span creation stress tests. It was called from DDSpan.isOutbound()
on every root span start and finish, falling through the getTag() switch
to a full TagMap hash-table lookup with potential synchronization.
This change caches span.kind as a volatile byte ordinal on DDSpanContext
(same dual-store pattern as httpStatusCode). The TagInterceptor now
intercepts SPAN_KIND to set the ordinal, and isOutbound() does a simple
byte comparison instead of getTag() + String.equals(). Benchmark shows
isOutbound() at constant ~2.8ns regardless of span kind.
tag: no release note
tag: ai generated
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dougqh
commented
Apr 15, 2026
dougqh
commented
Apr 15, 2026
dougqh
commented
Apr 15, 2026
dougqh
commented
Apr 15, 2026
dougqh
commented
Apr 15, 2026
- Reduce visibility of SPAN_KIND_UNSET and SPAN_KIND_CUSTOM to package-private (they don't correspond to actual span.kind values) - Restructure setSpanKind: identity checks first as a fast path, then string switch fallback for non-interned strings. Extracted into static spanKindToOrdinal() helper. - Use identity-first comparison in removeTag() for SPAN_KIND check - Use ordinal > SPAN_KIND_UNSET && ordinal < SPAN_KIND_CUSTOM in getTag() to help JIT with bounds check elimination - Use Tags.SPAN_KIND constant instead of key in fallthrough unsafeGetTag Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BenchmarksStartupParameters
See matching parameters
SummaryFound 0 performance improvements and 0 performance regressions! Performance is the same for 59 metrics, 12 unstable metrics. Startup time reports for petclinicgantt
title petclinic - global startup overhead: candidate=1.62.0-SNAPSHOT~e497ad0c94, baseline=1.62.0-SNAPSHOT~ca32af75bc
dateFormat X
axisFormat %s
section tracing
Agent [baseline] (1.055 s) : 0, 1054549
Total [baseline] (11.113 s) : 0, 11113187
Agent [candidate] (1.059 s) : 0, 1059198
Total [candidate] (11.105 s) : 0, 11105092
section appsec
Agent [baseline] (1.252 s) : 0, 1252413
Total [baseline] (11.178 s) : 0, 11178430
Agent [candidate] (1.249 s) : 0, 1249415
Total [candidate] (11.15 s) : 0, 11150385
section iast
Agent [baseline] (1.236 s) : 0, 1236445
Total [baseline] (11.379 s) : 0, 11379014
Agent [candidate] (1.243 s) : 0, 1243249
Total [candidate] (11.272 s) : 0, 11272282
section profiling
Agent [baseline] (1.192 s) : 0, 1191826
Total [baseline] (11.094 s) : 0, 11094407
Agent [candidate] (1.184 s) : 0, 1183612
Total [candidate] (10.999 s) : 0, 10998798
gantt
title petclinic - break down per module: candidate=1.62.0-SNAPSHOT~e497ad0c94, baseline=1.62.0-SNAPSHOT~ca32af75bc
dateFormat X
axisFormat %s
section tracing
crashtracking [baseline] (1.247 ms) : 0, 1247
crashtracking [candidate] (1.262 ms) : 0, 1262
BytebuddyAgent [baseline] (632.175 ms) : 0, 632175
BytebuddyAgent [candidate] (633.999 ms) : 0, 633999
AgentMeter [baseline] (29.364 ms) : 0, 29364
AgentMeter [candidate] (29.611 ms) : 0, 29611
GlobalTracer [baseline] (248.666 ms) : 0, 248666
GlobalTracer [candidate] (249.311 ms) : 0, 249311
AppSec [baseline] (32.272 ms) : 0, 32272
AppSec [candidate] (32.429 ms) : 0, 32429
Debugger [baseline] (60.011 ms) : 0, 60011
Debugger [candidate] (60.123 ms) : 0, 60123
Remote Config [baseline] (595.119 µs) : 0, 595
Remote Config [candidate] (593.832 µs) : 0, 594
Telemetry [baseline] (8.024 ms) : 0, 8024
Telemetry [candidate] (8.113 ms) : 0, 8113
Flare Poller [baseline] (5.968 ms) : 0, 5968
Flare Poller [candidate] (7.484 ms) : 0, 7484
section appsec
crashtracking [baseline] (1.232 ms) : 0, 1232
crashtracking [candidate] (1.228 ms) : 0, 1228
BytebuddyAgent [baseline] (662.394 ms) : 0, 662394
BytebuddyAgent [candidate] (662.259 ms) : 0, 662259
AgentMeter [baseline] (12.137 ms) : 0, 12137
AgentMeter [candidate] (12.146 ms) : 0, 12146
GlobalTracer [baseline] (250.1 ms) : 0, 250100
GlobalTracer [candidate] (249.195 ms) : 0, 249195
AppSec [baseline] (185.987 ms) : 0, 185987
AppSec [candidate] (185.055 ms) : 0, 185055
Debugger [baseline] (66.512 ms) : 0, 66512
Debugger [candidate] (65.921 ms) : 0, 65921
Remote Config [baseline] (635.784 µs) : 0, 636
Remote Config [candidate] (596.647 µs) : 0, 597
Telemetry [baseline] (8.535 ms) : 0, 8535
Telemetry [candidate] (8.389 ms) : 0, 8389
Flare Poller [baseline] (3.602 ms) : 0, 3602
Flare Poller [candidate] (3.508 ms) : 0, 3508
IAST [baseline] (24.702 ms) : 0, 24702
IAST [candidate] (24.541 ms) : 0, 24541
section iast
crashtracking [baseline] (1.245 ms) : 0, 1245
crashtracking [candidate] (1.25 ms) : 0, 1250
BytebuddyAgent [baseline] (811.385 ms) : 0, 811385
BytebuddyAgent [candidate] (813.4 ms) : 0, 813400
AgentMeter [baseline] (11.539 ms) : 0, 11539
AgentMeter [candidate] (11.674 ms) : 0, 11674
GlobalTracer [baseline] (241.052 ms) : 0, 241052
GlobalTracer [candidate] (243.636 ms) : 0, 243636
AppSec [baseline] (31.077 ms) : 0, 31077
AppSec [candidate] (32.091 ms) : 0, 32091
Debugger [baseline] (61.522 ms) : 0, 61522
Debugger [candidate] (63.089 ms) : 0, 63089
Remote Config [baseline] (537.073 µs) : 0, 537
Remote Config [candidate] (538.166 µs) : 0, 538
Telemetry [baseline] (12.267 ms) : 0, 12267
Telemetry [candidate] (11.175 ms) : 0, 11175
Flare Poller [baseline] (3.427 ms) : 0, 3427
Flare Poller [candidate] (3.516 ms) : 0, 3516
IAST [baseline] (25.959 ms) : 0, 25959
IAST [candidate] (26.338 ms) : 0, 26338
section profiling
crashtracking [baseline] (1.193 ms) : 0, 1193
crashtracking [candidate] (1.169 ms) : 0, 1169
BytebuddyAgent [baseline] (694.589 ms) : 0, 694589
BytebuddyAgent [candidate] (690.456 ms) : 0, 690456
AgentMeter [baseline] (9.156 ms) : 0, 9156
AgentMeter [candidate] (9.042 ms) : 0, 9042
GlobalTracer [baseline] (208.739 ms) : 0, 208739
GlobalTracer [candidate] (207.35 ms) : 0, 207350
AppSec [baseline] (33.113 ms) : 0, 33113
AppSec [candidate] (32.856 ms) : 0, 32856
Debugger [baseline] (66.133 ms) : 0, 66133
Debugger [candidate] (65.559 ms) : 0, 65559
Remote Config [baseline] (594.593 µs) : 0, 595
Remote Config [candidate] (582.905 µs) : 0, 583
Telemetry [baseline] (7.836 ms) : 0, 7836
Telemetry [candidate] (7.792 ms) : 0, 7792
Flare Poller [baseline] (3.599 ms) : 0, 3599
Flare Poller [candidate] (3.536 ms) : 0, 3536
ProfilingAgent [baseline] (95.041 ms) : 0, 95041
ProfilingAgent [candidate] (94.002 ms) : 0, 94002
Profiling [baseline] (95.612 ms) : 0, 95612
Profiling [candidate] (94.579 ms) : 0, 94579
Startup time reports for insecure-bankgantt
title insecure-bank - global startup overhead: candidate=1.62.0-SNAPSHOT~e497ad0c94, baseline=1.62.0-SNAPSHOT~ca32af75bc
dateFormat X
axisFormat %s
section tracing
Agent [baseline] (1.061 s) : 0, 1060563
Total [baseline] (8.83 s) : 0, 8829871
Agent [candidate] (1.063 s) : 0, 1063462
Total [candidate] (8.893 s) : 0, 8892550
section iast
Agent [baseline] (1.24 s) : 0, 1239871
Total [baseline] (9.578 s) : 0, 9577815
Agent [candidate] (1.22 s) : 0, 1220172
Total [candidate] (9.524 s) : 0, 9523603
gantt
title insecure-bank - break down per module: candidate=1.62.0-SNAPSHOT~e497ad0c94, baseline=1.62.0-SNAPSHOT~ca32af75bc
dateFormat X
axisFormat %s
section tracing
crashtracking [baseline] (1.254 ms) : 0, 1254
crashtracking [candidate] (1.232 ms) : 0, 1232
BytebuddyAgent [baseline] (635.544 ms) : 0, 635544
BytebuddyAgent [candidate] (637.555 ms) : 0, 637555
AgentMeter [baseline] (29.449 ms) : 0, 29449
AgentMeter [candidate] (29.809 ms) : 0, 29809
GlobalTracer [baseline] (248.944 ms) : 0, 248944
GlobalTracer [candidate] (250.698 ms) : 0, 250698
AppSec [baseline] (32.327 ms) : 0, 32327
AppSec [candidate] (32.419 ms) : 0, 32419
Debugger [baseline] (59.021 ms) : 0, 59021
Debugger [candidate] (59.383 ms) : 0, 59383
Remote Config [baseline] (588.978 µs) : 0, 589
Remote Config [candidate] (585.591 µs) : 0, 586
Telemetry [baseline] (8.016 ms) : 0, 8016
Telemetry [candidate] (8.057 ms) : 0, 8057
Flare Poller [baseline] (9.075 ms) : 0, 9075
Flare Poller [candidate] (7.305 ms) : 0, 7305
section iast
crashtracking [baseline] (1.254 ms) : 0, 1254
crashtracking [candidate] (1.234 ms) : 0, 1234
BytebuddyAgent [baseline] (813.022 ms) : 0, 813022
BytebuddyAgent [candidate] (797.544 ms) : 0, 797544
AgentMeter [baseline] (11.75 ms) : 0, 11750
AgentMeter [candidate] (11.405 ms) : 0, 11405
GlobalTracer [baseline] (240.66 ms) : 0, 240660
GlobalTracer [candidate] (238.757 ms) : 0, 238757
AppSec [baseline] (30.094 ms) : 0, 30094
AppSec [candidate] (30.267 ms) : 0, 30267
Debugger [baseline] (63.099 ms) : 0, 63099
Debugger [candidate] (62.248 ms) : 0, 62248
Remote Config [baseline] (1.165 ms) : 0, 1165
Remote Config [candidate] (535.624 µs) : 0, 536
Telemetry [baseline] (12.008 ms) : 0, 12008
Telemetry [candidate] (11.998 ms) : 0, 11998
Flare Poller [baseline] (3.48 ms) : 0, 3480
Flare Poller [candidate] (3.454 ms) : 0, 3454
IAST [baseline] (26.802 ms) : 0, 26802
IAST [candidate] (26.581 ms) : 0, 26581
LoadParameters
See matching parameters
SummaryFound 2 performance improvements and 1 performance regressions! Performance is the same for 17 metrics, 16 unstable metrics.
Request duration reports for petclinicgantt
title petclinic - request duration [CI 0.99] : candidate=1.62.0-SNAPSHOT~e497ad0c94, baseline=1.62.0-SNAPSHOT~ca32af75bc
dateFormat X
axisFormat %s
section baseline
no_agent (17.414 ms) : 17240, 17587
. : milestone, 17414,
appsec (18.692 ms) : 18502, 18883
. : milestone, 18692,
code_origins (17.881 ms) : 17702, 18060
. : milestone, 17881,
iast (19.388 ms) : 19194, 19582
. : milestone, 19388,
profiling (18.858 ms) : 18670, 19046
. : milestone, 18858,
tracing (17.7 ms) : 17525, 17875
. : milestone, 17700,
section candidate
no_agent (18.501 ms) : 18310, 18691
. : milestone, 18501,
appsec (18.938 ms) : 18746, 19130
. : milestone, 18938,
code_origins (17.756 ms) : 17582, 17929
. : milestone, 17756,
iast (18.001 ms) : 17825, 18178
. : milestone, 18001,
profiling (18.199 ms) : 18020, 18378
. : milestone, 18199,
tracing (17.674 ms) : 17503, 17845
. : milestone, 17674,
Request duration reports for insecure-bankgantt
title insecure-bank - request duration [CI 0.99] : candidate=1.62.0-SNAPSHOT~e497ad0c94, baseline=1.62.0-SNAPSHOT~ca32af75bc
dateFormat X
axisFormat %s
section baseline
no_agent (1.279 ms) : 1266, 1292
. : milestone, 1279,
iast (3.302 ms) : 3253, 3350
. : milestone, 3302,
iast_FULL (6.179 ms) : 6115, 6243
. : milestone, 6179,
iast_GLOBAL (3.552 ms) : 3500, 3604
. : milestone, 3552,
profiling (2.441 ms) : 2417, 2465
. : milestone, 2441,
tracing (1.893 ms) : 1877, 1909
. : milestone, 1893,
section candidate
no_agent (1.235 ms) : 1223, 1247
. : milestone, 1235,
iast (3.419 ms) : 3372, 3466
. : milestone, 3419,
iast_FULL (6.037 ms) : 5975, 6099
. : milestone, 6037,
iast_GLOBAL (3.547 ms) : 3495, 3600
. : milestone, 3547,
profiling (2.439 ms) : 2414, 2464
. : milestone, 2439,
tracing (1.918 ms) : 1901, 1935
. : milestone, 1918,
DacapoParameters
See matching parameters
SummaryFound 1 performance improvements and 0 performance regressions! Performance is the same for 11 metrics, 0 unstable metrics.
Execution time for biojavagantt
title biojava - execution time [CI 0.99] : candidate=1.62.0-SNAPSHOT~e497ad0c94, baseline=1.62.0-SNAPSHOT~ca32af75bc
dateFormat X
axisFormat %s
section baseline
no_agent (15.637 s) : 15637000, 15637000
. : milestone, 15637000,
appsec (14.82 s) : 14820000, 14820000
. : milestone, 14820000,
iast (18.308 s) : 18308000, 18308000
. : milestone, 18308000,
iast_GLOBAL (18.156 s) : 18156000, 18156000
. : milestone, 18156000,
profiling (14.939 s) : 14939000, 14939000
. : milestone, 14939000,
tracing (14.827 s) : 14827000, 14827000
. : milestone, 14827000,
section candidate
no_agent (15.056 s) : 15056000, 15056000
. : milestone, 15056000,
appsec (14.276 s) : 14276000, 14276000
. : milestone, 14276000,
iast (18.252 s) : 18252000, 18252000
. : milestone, 18252000,
iast_GLOBAL (18.106 s) : 18106000, 18106000
. : milestone, 18106000,
profiling (14.992 s) : 14992000, 14992000
. : milestone, 14992000,
tracing (15.207 s) : 15207000, 15207000
. : milestone, 15207000,
Execution time for tomcatgantt
title tomcat - execution time [CI 0.99] : candidate=1.62.0-SNAPSHOT~e497ad0c94, baseline=1.62.0-SNAPSHOT~ca32af75bc
dateFormat X
axisFormat %s
section baseline
no_agent (1.485 ms) : 1473, 1496
. : milestone, 1485,
appsec (3.828 ms) : 3607, 4049
. : milestone, 3828,
iast (2.265 ms) : 2196, 2334
. : milestone, 2265,
iast_GLOBAL (2.308 ms) : 2239, 2377
. : milestone, 2308,
profiling (2.093 ms) : 2039, 2148
. : milestone, 2093,
tracing (2.075 ms) : 2021, 2128
. : milestone, 2075,
section candidate
no_agent (1.487 ms) : 1475, 1498
. : milestone, 1487,
appsec (2.531 ms) : 2476, 2586
. : milestone, 2531,
iast (2.274 ms) : 2204, 2343
. : milestone, 2274,
iast_GLOBAL (2.31 ms) : 2241, 2379
. : milestone, 2310,
profiling (2.106 ms) : 2050, 2162
. : milestone, 2106,
tracing (2.079 ms) : 2026, 2132
. : milestone, 2079,
|
dougqh
commented
Apr 15, 2026
dougqh
commented
Apr 15, 2026
| } | ||
|
|
||
| public void removeTag(String tag) { | ||
| if (tag == Tags.SPAN_KIND || Tags.SPAN_KIND.equals(tag)) { |
Contributor
Author
There was a problem hiding this comment.
Use equal helper function proposed in comment above
dougqh
commented
Apr 15, 2026
dougqh
commented
Apr 15, 2026
dougqh
commented
Apr 15, 2026
- Replace identity-first + string-switch with simpler if/else chain using a static tagEquals(tagValue, tagLiteral) helper that does identity check then falls back to equals - Make all ordinal constants package-private - Add isClientSpanKind() convenience method so TagInterceptor doesn't need to reference ordinal constants from a sub-package - Use tagEquals helper in removeTag for consistency Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dougqh
commented
Apr 15, 2026
- Extract SPAN_KIND getTag fallthrough into private getSpanKindTag() helper method for readability - Simplify benchmark setup: cast to DDSpan immediately instead of using intermediate AgentSpan variables Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dougqh
commented
Apr 15, 2026
dougqh
commented
Apr 15, 2026
dougqh
commented
Apr 15, 2026
…tring - Remove superfluous isClientSpanKind() (only one call site, inline the check in TagInterceptor using string comparison instead) - Reduce getSpanKindOrdinal() visibility to package-private - Rename getSpanKindTag -> getSpanKindString, return String, use unsafeTags.getString for the fallthrough path Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dougqh
commented
Apr 15, 2026
| } | ||
| if (path != null) { | ||
| final boolean isClient = Tags.SPAN_KIND_CLIENT.equals(span.unsafeGetTag(Tags.SPAN_KIND)); | ||
| final boolean isClient = Tags.SPAN_KIND_CLIENT.equals(span.getSpanKindString()); |
Contributor
Author
There was a problem hiding this comment.
Because I decided to keep the "enum" package visible for now, we cannot do a faster ordinal comparison here. I think that's okay.
dougqh
commented
Apr 15, 2026
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dougqh
commented
Apr 15, 2026
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What Does This Do
span.kindas avolatile byteordinal onDDSpanContext, following the existinghttpStatusCodedual-store patternDDSpan.isOutbound()fromgetTag() + String.equals()to a constant-time byte comparison (~2.8ns vs ~10ns)SPAN_KINDinterception inTagInterceptorto populate the cache onsetTag()IsOutboundBenchmarkJMH benchmarkMotivation
DDSpanContext.getTag("span.kind")consumed ~14% of foreground CPU in a 16-thread span creation stress test. It was called fromCoreTracer.onRootSpanStartedandonRootSpanFinishedon every root span, falling through thegetTag()switch to a fullTagMaphash-table lookup with potential synchronization.In spring petclinic throughput test, this result in 10-20% reduction in our throughput overhead with ample heap. Reducing throughput impact from adding tracer from -10% to -8%.
Additional Notes
Contributor Checklist
type:and (comp:orinst:) labels in addition to any other useful labelsclose,fix, or any linking keywords when referencing an issueUse
solvesinstead, and assign the PR milestone to the issue🤖 Generated with Claude Code