Skip to content

Encode base64 into exactly-sized char[] instead of StringBuilder - #1674

Open
pjfanning wants to merge 1 commit into
FasterXML:3.xfrom
pjfanning:perf-base64-encode-3x
Open

Encode base64 into exactly-sized char[] instead of StringBuilder#1674
pjfanning wants to merge 1 commit into
FasterXML:3.xfrom
pjfanning:perf-base64-encode-3x

Conversation

@pjfanning

Copy link
Copy Markdown
Member

Base64Variant.encode(byte[], boolean) and encode(byte[], boolean, String) each built their result by appending char-at-a-time to a StringBuilder sized at an estimate of inputEnd + (inputEnd >> 2) + (inputEnd >> 3) (1.375x input).

Both overloads now delegate to a private _encodeToString(...) that computes the exact output length up front and encodes straight into a char[], using the already-public encodeBase64Chunk(int, char[], int) and encodeBase64Partial(int, int, char[], int) helpers.

Notes

  • No public API change. The StringBuilder-taking encodeBase64Chunk(StringBuilder, int) / encodeBase64Partial(StringBuilder, int, int) remain public and unchanged; they are simply no longer used internally.
  • No behaviour change, including the trailing linefeed emitted when the last chunk completes a line.
  • chunksPerLine is Math.max(1, getMaxLineLength() >> 2). The constructor does not validate maxLineLength, and values below 4 made the old counter emit a linefeed after every chunk; the guard reproduces that exactly and keeps the length calculation from dividing by zero.
  • Oversized input still fails the same way (NegativeArraySizeException from the wrapped-negative length), as it did from new StringBuilder(...).

Correctness

A throwaway differential test ran the verbatim old algorithm against the new one: 28,872 comparisons, all byte-identical — 9 variants (MIME, MIME_NO_LINEFEEDS, PEM, MODIFIED_FOR_URL, plus degenerate line lengths 0/1/4/7/8), input lengths 0-400, quoted and unquoted, and four linefeed strings including "". Re-run with a hard outPtr != buffer.length assertion in the encoder it was also clean, so the buffer is exactly sized in every case.

Three permanent tests added to Base64CodecTest (short inputs, line-boundary output, sub-chunk line lengths). Full verify green, 1839 tests.

Performance

These numbers are provisional. They come from a crude loop benchmark, not JMH, on a machine that was noisy enough that absolute timings drifted 2-4x between batches. @pjfanning will write a JMH benchmark to verify before this is relied on.

Min-of-5 separate JVMs, PEM + quotes, 200k iterations:

input current exactly-sized StringBuilder char[] (this PR)
48 B 153.0 ms 109.5 ms 74.9 ms (2.0x)
300 B 532.8 ms 450.9 ms 169.4 ms (3.1x)
3000 B 5351.2 ms 3742.6 ms 1428.4 ms (3.7x)

The middle column matters for interpreting this: merely fixing the capacity estimate recovers only ~1.2-1.4x, so the win is coming from the bulk array stores rather than from avoiding a resize. (The 1.375x estimate does under-shoot for shorter line lengths — PEM lands exactly on it and quotes push it over — but that turns out to be the minor effect.)

No release-notes entry, as there is no issue number for this yet.

🤖 Generated with Claude Code

https://claude.ai/code/session_0161vnbY6rGfPFAYBMnbv64c

Base64Variant.encode() appended char-at-a-time to a StringBuilder sized at
a 1.375x estimate. Both public overloads now share a private helper that
computes the exact output length and encodes straight into a char[] via the
existing public char[] chunk/partial helpers. Output is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0161vnbY6rGfPFAYBMnbv64c
@github-actions

Copy link
Copy Markdown
Contributor

📈 Overall Code Coverage

Metric Coverage Change
Instructions coverage 83.83% 📉 -0.070%
Branches branches 76.85% 📉 -0.030%

Overall project coverage from JaCoCo test results. Change values compare against the latest base branch build.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant