Assign IonSystemBuilder sub-builder defaults in the constructor - #1162
Merged
NovemberZulu merged 1 commit intoSep 2, 2026
Merged
Conversation
Java compiles instance field initializers into every constructor, so the three sub-builder initializers on IonSystemBuilder also ran inside the copy constructor, whose body then immediately overwrote all three fields. Every copy() -- and therefore mutable(), immutable(), and each withXxx() -- allocated a full set of default text writer, binary writer, and reader builders only to discard them. Moving the defaults into the no-argument constructor leaves standard() byte-for-byte unchanged while making the copy path allocate nothing beyond the builder itself: copy() drops from 288 to 32 bytes per call and from ~78 to ~5 ns on the benchmark added here. No behavior change: standard() still starts with the same three default sub-builders, and copies still share them by reference exactly as before. Tests added to lock that down.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1162 +/- ##
============================================
+ Coverage 67.23% 68.00% +0.77%
- Complexity 5484 5676 +192
============================================
Files 159 160 +1
Lines 23025 23378 +353
Branches 4126 4204 +78
============================================
+ Hits 15481 15899 +418
+ Misses 6262 6173 -89
- Partials 1282 1306 +24 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
jobarr-amzn
approved these changes
Sep 2, 2026
jobarr-amzn
left a comment
Contributor
There was a problem hiding this comment.
Thanks! This is a solid improvement. Did you happen to notice the same class of error elsewhere?
NovemberZulu
approved these changes
Sep 2, 2026
Contributor
Author
A few more applications with related workload see this hotspot. |
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.
Issue #, if available: #1161
Description of changes:
IonSystemBuilderassigns its three sub-builder defaults in field initializers. Javacompiles instance field initializers into every constructor (JLS 12.5), so those three
initializers also ran inside the copy constructor — whose body then immediately overwrote
all three fields with the source builder's values. The three default builders were
constructed and discarded on every copy.
The copy constructor is the only path behind
copy(), and therefore also behindmutable(),immutable(), and everywithXxx()method.This change declares the three fields without initializers and assigns the defaults in the
no-argument constructor instead, so the copy constructor does no redundant work. It is a
four-line change plus comments;
_Private_IonReaderBuilderalready uses this shape for itslstFactoryfield.Measured effect —
IonSystemBuilderCopyBenchmark, added here (Corretto 17.0.20,x86-64,
-prof gc, 3×1 s warmup / 5×1 s measurement, 1 fork):standard().copy()standard().withCatalog(c)standard().withCatalog(c).build()Allocation is exact (
gc.alloc.rate.normreports ± 0.001 B/op on every row): 256 B/opeliminated on every copy, and the same 256 B on the build path. That is the discarded text
writer builder (72 B), binary writer builder and its catalog (152 B), and reader builder
(32 B); the surviving 32 B is the
IonSystemBuilder.Mutableitself. The wall-clock win oncopy()is larger than the byte count suggests because_Private_IonManagedBinaryWriterBuilderdeclares tenvolatilefields, so constructing thediscarded binary writer builder emits a series of memory fences. (The
build()timing isnoisy; the allocation delta is the reliable signal there.)
A production CPU profile of a large internal service attributes 0.66% of total service CPU
to
IonSystemBuilder.copy.Why the constructor and not
static finalconstants. Hoisting the three defaults intoprivate static finalfields looks cleaner but is a silent trap:STANDARDis initializedat line 75, textually before where the constants would be declared, so under JLS 12.4.2
it would capture
nullfor all three andbuild()would NPE. JLS 8.3.3'sillegal-forward-reference rule only covers simple-name uses, so javac accepts it without a
warning.
Behavior. Unchanged.
javap -p -c), sostandard()still starts with the same three default sub-builders:ASCII text writer, standard binary writer, standard reader. The copy constructor drops
from 74 to 50 bytes of bytecode and from three
invokestaticcalls to none.pre-change copies also shared them, because the initializers' results were overwritten.
emitted binary and text bytes, DOM round-trips, reference sharing across
copy()/mutable()/immutable()chains, andUnsupportedOperationExceptionon mutatingan immutable builder.
Tests. The two new tests, plus three new assertions in
testCopy, are regression guardsrather than reproductions — the
defect was wasted work, not wrong results, so they pass before this change as well. They
pin the behavior the fix must not break:
standard()exposes non-null defaults with theASCII charset,
copy()propagates all three sub-builders by reference, and acopy().mutable().copy().immutable().mutable()chain still ends up sharingstandard()'sinstances.
Follow-up, not addressed here.
_Private_IonTextWriterBuilder.build(...)constructs acomplete throwaway
IonSystemper text writer viafillDefaults(), and_Private_IonBinaryWriterBuildercallsIonSystemBuilder.standard().build()per binarywriter. That is a larger source of the same profile frame and worth a separate look; happy
to open an issue for it.
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.