Add NumberOutput.outputDouble/outputFloat(char[]), use in WriterBasedJsonGenerator - #1704
Merged
Merged
Conversation
…sedJsonGenerator Char-buffer counterparts of the existing byte[] direct-write methods, so the Writer-based generator can also skip String allocation when USE_FAST_DOUBLE_WRITER is enabled (as UTF8JsonGenerator already does). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
WRITE_NUMBERS_AS_STRINGS / WRITE_NAN_AS_STRINGS paths went through writeString(NumberOutput.toString(...)) in both generators; with USE_FAST_DOUBLE_WRITER write quote + digits + quote straight into the output buffer instead. Number text is ASCII so no escaping is needed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Float-only boundary runs (double's larger reserve was masking the float flush branch), NaN with WRITE_NAN_AS_STRINGS disabled, custom quote char, root-value separator. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Member
Author
|
Rough benchmark numbers — JDK 17.0.19, hand-rolled loop (no JMH in this repo), best-of-3 runs, min ns per number, writing a 1000-element array of mixed-magnitude values to a discarding
Default behavior ( |
Member
Author
|
Follow-up: same harness on JDK 21, plus the XJB branch (#1656, head JDK 17.0.19 (control row agrees across jars, so this table is trustworthy)
JDK 21.0.11 (control row scattered 120–169 on identical code, so ±20% noise)
Takeaways:
|
Contributor
cowtowncoder
approved these changes
Sep 12, 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.
I'd still prefer to use XJB algorithm (#1656) but this change improves the Schubfach so that that algorithm can be used without allocating temp Strings which is the biggest performance win in the XJB PR anyway.
Adds
char[]counterparts to the existingNumberOutput.outputDouble(double, byte[], int)/outputFloat(float, byte[], int)(3.2.2), backed by newDoubleToDecimal.writeDouble(double, char[], int)/FloatToDecimal.writeFloat(float, char[], int).WriterBasedJsonGenerator.writeNumber(double|float)now uses them whenStreamWriteFeature.USE_FAST_DOUBLE_WRITERis enabled, writing straight into its char output buffer instead of going throughString+writeRaw()— the same shapeUTF8JsonGeneratoralready has for the byte path.The quoted paths (
WRITE_NUMBERS_AS_STRINGS, andWRITE_NAN_AS_STRINGSfor non-finite values) get the same treatment in both generators: quote + digits + quote written directly into the output buffer, instead ofwriteString(NumberOutput.toString(v, useFast)).What this does and does not avoid
To be precise about the allocation win, since that is the point of the change:
Stringper value (and thechar[]copy inside it), plus the subsequentwriteRaw()copy into the output buffer.DoubleToDecimal.writeDouble(...)/FloatToDecimal.writeFloat(...)still donew DoubleToDecimal()/new FloatToDecimal()per call, and each instance holds abyte[MAX_CHARS]scratch array. Digits are rendered into that array first and then widened byte-to-char in a loop (thebyte[]variant gets aSystem.arraycopyinstead).This matches the design of the existing 3.2.2
byte[]methods rather than diverging from it, and escape analysis typically eliminates the scratch instance. Making it genuinely allocation-free would mean char-emittingappend()/appendDigit()variants in the Schubfach classes — deliberately left out of scope here.Behavior
Rendered number text is unchanged: the fast path emits exactly what
NumberOutput.toString(v, true)emitted, includingNaN/Infinity/-Infinity/-0.0.One deliberate change beyond that: the non-fast quoted branch now goes through
_writeQuotedRaw()as well, matching whatwriteNumber(BigInteger)/writeNumber(BigDecimal)/writeNumber(String)have always done. Previously it went throughwriteString(), so a customCharacterEscapesescaping a character that appears in number text would escape quoted double/float output — but not quotedBigDecimaloutput, and not the new fast path. Quoted number output is now consistent across all of those, and independent ofUSE_FAST_DOUBLE_WRITER.Tests
SchubfachWriteCharBufferTest— char output matchestoString(v, true)and the byte variant, including 10k random bit patterns and tight-buffer max-length cases.FastDoubleCharBufferBoundaryTest— Writer-based generator flushing across output-buffer boundaries.FastDoubleQuotedWriteTest— quoted double/float for both generators:WRITE_NUMBERS_AS_STRINGS,WRITE_NAN_AS_STRINGS(on and off), custom quote char, root values, object values, buffer-boundary runs, and aCharacterEscapesregression test pinning the consistency described above.🤖 Generated with Claude Code