Skip to content

HDDS-16277. Reuse CharsetEncoder/Decoder in StringCodec - #11112

Open
rich7420 wants to merge 1 commit into
apache:masterfrom
rich7420:HDDS-16277
Open

HDDS-16277. Reuse CharsetEncoder/Decoder in StringCodec#11112
rich7420 wants to merge 1 commit into
apache:masterfrom
rich7420:HDDS-16277

Conversation

@rich7420

@rich7420 rich7420 commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

StringCodecBase (the base of StringCodec and FixedLengthStringCodec) allocates a fresh CharsetEncoder on every encode and a fresh CharsetDecoder on every decode:

  • encode(...) -> newEncoder().encode(...)
  • decodeNoFallback(...) / decodeWithFallback(...) -> newDecoder().decode(...)

These codecs are singletons on a hot path: every String RocksDB key/value (OM/SCM tables, iterators, compaction) is serialized/deserialized through them, so a short-lived coder is created on each call.

CharsetEncoder/CharsetDecoder are stateful and not thread-safe, so this caches one per thread (ThreadLocal) and reuses it:

  • the encoder is reset() before each use, because the 3-arg encode(in, out, endOfInput) does not reset on its own;
  • the decoder uses the single-arg CharsetDecoder.decode(ByteBuffer), which resets internally (per its javadoc), so no explicit reset is added there.

The cache is per codec instance rather than static, because subclasses use different charsets. reset() only clears coding state and keeps onMalformedInput/onUnmappableCharacter(REPORT), so behavior is unchanged.

What is the link to the Apache JIRA

https://issues.apache.org/jira/browse/HDDS-16277

How was this patch tested?

fork CI run:
https://github.com/rich7420/ozone/actions/runs/32851264063

Microbenchmark

JMH (out of tree), JDK 21, single thread, fork=2. Both variants run the identical encode/decode and differ only in coder acquisition (fresh vs. reused per thread), so the delta is attributable to this change. gc.alloc.rate.norm error is +/- 0.001 B/op, i.e. the per-call allocation is exact. that's on my macbook.

encode:

input ns/op (old -> new) B/op (old -> new)
/vol1/bucket1/dir1/dir2/object-file-000000123.dat 81.7 -> 66.3 (-19%) 208 -> 112 (-96 B, -46%)
8f14e45f-ceea-467a-9d1b-2f3c4d5e6f70 58.7 -> 53.6 (-9%) 208 -> 112 (-96 B, -46%)

decode:

input ns/op (old -> new) B/op (old -> new)
/vol1/bucket1/dir1/dir2/object-file-000000123.dat 29.5 -> 25.5 (-14%) 368 -> 328 (-40 B, -11%)
8f14e45f-ceea-467a-9d1b-2f3c4d5e6f70 28.7 -> 26.5 (-8%) 320 -> 280 (-40 B, -13%)

StringCodecBase allocated a fresh CharsetEncoder/CharsetDecoder on every
encode/decode call. Cache them per thread (ThreadLocal) since the codec
instances are singletons and the coders are stateful and not thread-safe.
The encoder is reset() before each reuse; the single-arg
CharsetDecoder.decode resets internally, so behavior is unchanged.
Copilot AI lite review requested due to automatic review settings August 25, 2026 13:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@chihsuan chihsuan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the patch! @rich7420 +1 LGTM

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.

3 participants