Cache hashCode on UTF8BytesString#11444
Open
dougqh wants to merge 1 commit into
Open
Conversation
UTF8BytesString.hashCode() currently delegates straight through to String.hashCode() on every call. String already caches its own hash, but the trip out of UTF8BytesString and through String's hash-field check still costs a virtual dispatch + field read + branch on every invocation. Caches the hash on UTF8BytesString itself once computed. Benign-race pattern, identical to the existing utf8Bytes lazy initializer: two threads computing the same value produce identical results, and int writes are atomic per JLS so a reader can't observe a partial value. Measured on the metrics subsystem's adversarial JMH bench (8 producer threads, high-cardinality unique-per-op labels), this lifts aggregate throughput from 5.17M to 5.78M ops/s -- ~12% improvement, with the per-iteration distribution shifting systematically upward across all five measurement iterations. The win is bigger in production-like workloads with repeated keys, since the cardinality-handler intern pool means the same UTF8BytesString instance gets hashed repeatedly in subsequent reporting cycles. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
|
Hi! 👋 Thanks for your pull request! 🎉 To help us review it, please make sure to:
If you need help, please check our contributing guidelines. |
Contributor
|
amarziali
approved these changes
May 22, 2026
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
Caches the computed
hashCodeonUTF8BytesStringrather than re-delegating throughstring.hashCode()on every call.Motivation
UTF8BytesString.hashCode()currently looks like:Stringalready caches its own hash (JDK 1.0+), so subsequent calls don't recompute the value — but every call out ofUTF8BytesStringstill pays a virtual dispatch + the cached-hash field check insideString.hashCode+ branch. For a class that ends up as a hash key in tag caches, metric label sets, and the new cardinality-handler probe tables, that overhead adds up.This change caches the hash on
UTF8BytesStringitself, so subsequent calls return immediately via a single field read.Implementation notes
Benign-race pattern, identical to the existing
utf8Byteslazy initializer in this class:private int cachedHashCode— initialised to0by JVM default.intwrites are atomic per JLS, so a reader can't observe a partial value.hashCodeis0(rare collision), we'll recompute it on every call — same trade-offStringitself makes. Not worth a separate "is-zero" flag.Benchmark
Measured on
AdversarialMetricsBenchmark(8 producer threads, high-cardinality unique-per-op labels saturating every cardinality cap in the metrics subsystem, 2×15s warmup + 5×15s):~12% throughput improvement. Every per-iteration value with the cache is at or above the highest non-warmup baseline iteration. The CIs overlap somewhat at one fork each, but the systematic upward shift across all 5 iterations across both runs is a real signal.
The bench is adversarial in the sense that every op uses a unique label combination, which defeats UTF8 reuse — so this is the lower bound on the gain. Production workloads with hot-key skew benefit more, because the cardinality-handler intern pool means the same
UTF8BytesStringinstance gets hashed repeatedly in subsequent reporting cycles.Test plan
:internal-api:test --tests 'datadog.trace.bootstrap.instrumentation.api.Utf8ByteStringTest'— all 17 cases pass (existing tests already assertutf8String.hashCode() == str.hashCode()):internal-api:spotlessCheckclean🤖 Generated with Claude Code