perf(core): Drop per-instance lock from SentryId and SpanId#5645
Open
runningcode wants to merge 2 commits into
Open
perf(core): Drop per-instance lock from SentryId and SpanId#5645runningcode wants to merge 2 commits into
runningcode wants to merge 2 commits into
Conversation
📲 Install BuildsAndroid
|
Contributor
Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 1edbdfa | 364.77 ms | 450.29 ms | 85.52 ms |
| a416a65 | 333.78 ms | 410.37 ms | 76.59 ms |
| d15471f | 302.62 ms | 353.84 ms | 51.22 ms |
| 22f4345 | 314.79 ms | 375.02 ms | 60.23 ms |
| 6b019b7 | 343.31 ms | 417.23 ms | 73.91 ms |
| 22f4345 | 312.78 ms | 347.40 ms | 34.62 ms |
| d217708 | 411.22 ms | 430.86 ms | 19.63 ms |
| 319f256 | 315.96 ms | 372.96 ms | 57.00 ms |
| e2dce0b | 308.96 ms | 360.10 ms | 51.14 ms |
| 8558cac | 306.16 ms | 355.24 ms | 49.09 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 1edbdfa | 1.58 MiB | 2.20 MiB | 635.34 KiB |
| a416a65 | 1.58 MiB | 2.12 MiB | 555.26 KiB |
| d15471f | 1.58 MiB | 2.13 MiB | 559.54 KiB |
| 22f4345 | 1.58 MiB | 2.29 MiB | 719.83 KiB |
| 6b019b7 | 0 B | 0 B | 0 B |
| 22f4345 | 1.58 MiB | 2.29 MiB | 719.83 KiB |
| d217708 | 1.58 MiB | 2.10 MiB | 532.97 KiB |
| 319f256 | 1.58 MiB | 2.19 MiB | 619.79 KiB |
| e2dce0b | 0 B | 0 B | 0 B |
| 8558cac | 0 B | 0 B | 0 B |
runningcode
commented
Jun 30, 2026
| new SentryId(StringUtils.PROPER_NIL_UUID.replace("-", "")); | ||
|
|
||
| private final @NotNull LazyEvaluator<String> lazyStringValue; | ||
| private volatile @Nullable String value; |
Contributor
Author
There was a problem hiding this comment.
i don't like the name of this field value along with the getValue() method but to change the getValue() would break the API so probably better to keep it like this.
Member
There was a problem hiding this comment.
l: Still true, or was this comment from a previous version?
runningcode
commented
Jun 30, 2026
| private @NotNull String getValue() { | ||
| String result = value; | ||
| if (result == null) { | ||
| synchronized (this) { |
Contributor
Author
There was a problem hiding this comment.
using synchronized here still provides thread safety without allocating any extra objects.
8d0622c to
288b97d
Compare
SentryId and SpanId stored their string value behind a LazyEvaluator<String>, which allocates an AutoClosableReentrantLock (a ReentrantLock with its internal Sync) plus a capturing lambda on every instance. Since one SentryId is created per event/transaction and one SpanId per span, this per-instance lock machinery is far heavier than the single String it guards, and the eager string-arg constructors gained no laziness at all. Replace the LazyEvaluator with a plain volatile String guarded by a double-checked synchronized(this) block. Eager constructors now assign the value directly; the no-arg and UUID constructors still defer UUID-string generation. Synchronization is retained because UUID generation is non-idempotent and two racing threads must not produce different ids. Follow-up to the SDK Overhead Reduction work (#5499). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
288b97d to
d3f0fe2
Compare
0xadam-brown
approved these changes
Jun 30, 2026
| new SentryId(StringUtils.PROPER_NIL_UUID.replace("-", "")); | ||
|
|
||
| private final @NotNull LazyEvaluator<String> lazyStringValue; | ||
| private volatile @Nullable String value; |
Member
There was a problem hiding this comment.
l: Still true, or was this comment from a previous version?
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.
📜 Description
SentryIdandSpanIdstored their string value behind aLazyEvaluator<String>. EachLazyEvaluatorallocates anAutoClosableReentrantLock(aReentrantLocksubclass, including its internalSyncobject) plus a capturing lambda — on every instance.This change replaces the
LazyEvaluator<String>in both classes with a plainvolatile Stringguarded by a double-checkedsynchronized (this)block:UUIDconstructors still defer UUID-string generation;SentryIdkeeps theUUIDreference sotoSentryIdStringstays lazy.No public API change (
apiDumpproduces no diff).💡 Motivation and Context
One
SentryIdis created per event/transaction and oneSpanIdper span, so these are among the most frequently allocated objects in the SDK. Paying for a per-instanceReentrantLock+ lambda to guard a singleStringis pure overhead, and the eager string-arg constructors got no laziness benefit at all. Follow-up to the SDK Overhead Reduction work (#5499).Resolves JAVA-589.
💚 How did you test it?
Existing
SpanIdTestandSentryIdTestcover the lazy-generation and normalization behavior (UUID generated/normalized only once, not on init, etc.). The full:sentryunit test suite passes (3372/3373; the one failingSentryIdTestcase is a pre-existing test-isolation artifact that fails identically onmainand is unrelated to this change).Benchmark
Measured against
mainusing the real compiled classes on the same JVM (Temurin 17.0.16). Allocation is measured viaThreadMXBean.getThreadAllocatedBytes(exact TLAB accounting), with objects stored into a live array so the JIT can't scalar-replace them. Throughput is single-threaded, best-of-10 measured rounds after 5 warmup rounds of 1M ops each.SpanId(String)eagerSpanId()lazy +toStringSentryId(String)eagerSentryId()lazy +toStringSentryId(UUID)lazy +toStringThe ~88-byte reduction on eager
SpanIdaccounts exactly for the removed object graph:LazyEvaluator(24B) +AutoClosableReentrantLock(16B) + its internalNonfairSync/AQS (32B) + the capturing lambda (16B). Eager constructors (hot deserialization/propagation paths) win biggest. The lazy paths still shed the lock machinery;SentryId(UUID)throughput barely moves because that path is dominated byUUID.randomUUID()(aSecureRandomdraw), not the lock.📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps
Other
LazyEvaluator/AutoClosableReentrantLockusages that guard genuinely light objects can be reviewed similarly as part of the SDK overhead reduction effort.