Skip to content

Decode and determine equality for strings directly from immutable byte segments - #856

Merged
DanielThomas merged 9 commits into
masterfrom
dannyt/immutable-segmented-string-decoding
Oct 7, 2026
Merged

DanielThomas merged 9 commits into
masterfrom
dannyt/immutable-segmented-string-decoding

Conversation

@DanielThomas

@DanielThomas DanielThomas commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

This is a String decoding change I've wanted to make for years, and a string equality change that also shows up on read paths.

When decoding String from SegmentedByteArray, every call resolves segments[index] again, and with ZGC, that also means another reference load barrier. In an Images profile, those byte lookups while decoding strings accounted for about 1.6% of total process CPU.

The immutability established by #853 lets this path load the segment table once and then process each contiguous byte[] range directly. That same segmented traversal is also now used by isStringFieldEqual(...), which is primarily exercised by primary-key matching.

The JDK has since seen a lot of investment in String to support compact strings, so a contiguous ASCII range now goes directly to the specialized String(byte[], ..., ISO_8859_1) path. On JDK 19 and later, contiguous ranges also use the JDK ASCII decoder to identify and inflate the leading ASCII run through the countPositives intrinsic. Earlier releases retain the scalar path; JDK 17 exposed the decoder fast path but did not yet have the prefix-returning intrinsic and regressed on mixed strings.

Benchmark

HollowObjectTypeReadStateShardBenchmark.testReadString, Azul JDK 25.0.4.1 on x86-64 with ZGC, 500 strings per operation, and a 10% probability of U+123E characters. Both sides use non-recycling storage and the same seeded input:

Maximum string length Parent (#853) This PR Improvement
25 0.0293 ms/op 0.0237 ms/op 19.1%
150 0.1103 ms/op 0.0516 ms/op 53.2%

The additional JDK 19+ ASCII-prefix path, measured against the scalar path on Azul JDK 21.0.12.1 with ZGC on the same host and 500 strings per operation:

Maximum length Unicode probability Scalar prefix JDK intrinsic Improvement
150 1% 0.0492 ms/op 0.0366 ms/op 25.6%
150 10% 0.0611 ms/op 0.0501 ms/op 18.0%
1000 1% 0.3481 ms/op 0.3055 ms/op 12.2%
1000 10% 0.5236 ms/op 0.5083 ms/op 2.9%

HollowObjectTypeReadStateShardBenchmark.testStringEquality, Azul JDK 25.0.4.1 with ZGC on the same host, 500 matching comparisons per operation, non-recycling storage, and the same seeded Unicode distribution:

Maximum string length Per-byte equality Segmented equality Improvement
25 0.0381 ms/op 0.0290 ms/op 23.9%
150 0.1589 ms/op 0.0874 ms/op 45.0%

The equality method was added to the parent benchmark harness for this comparison; the parent implementation was unchanged.

@DanielThomas
DanielThomas added this pull request to stack #857 September 22, 2026 07:27
@DanielThomas
DanielThomas marked this pull request as ready for review September 22, 2026 07:27
@DanielThomas
DanielThomas force-pushed the dannyt/immutable-segmented-string-decoding branch from 213cfe9 to 9ab6b04 Compare September 23, 2026 03:04
@DanielThomas DanielThomas changed the title Decode strings directly from immutable byte segments Decode and compare strings directly from immutable byte segments Sep 23, 2026
@DanielThomas DanielThomas changed the title Decode and compare strings directly from immutable byte segments Decode and determine equality for strings directly from immutable byte segments Sep 23, 2026
@Sunjeet

Sunjeet commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator

Redundant Arrays.fill on the default (non-immutable) path. (minor regression)

The diff removed the else:

if (length > chararr.length)
chararr = new char[length];

if(immutable)
return ((SegmentedByteArray)data).readVIntString(position, length, chararr);

Arrays.fill(chararr, 0, length, '\0');

When length > 100, chararr was just allocated and is already zeroed, so the fill is a pure O(length) waste — on the path every default-configured consumer still uses. Restoring the else for the
non-immutable branch keeps it free.

@DanielThomas
DanielThomas force-pushed the dannyt/immutable-segmented-string-decoding branch from ef4dd55 to a1b61dd Compare September 26, 2026 02:20
@DanielThomas

Copy link
Copy Markdown
Member Author

Restored conditional clearing so the legacy decoder clears reused scratch but leaves newly allocated arrays alone.

Fixed empty-string reads and comparisons when variable-length storage is absent. Added regression tests for empty and null values with both recycler modes.

@DanielThomas
DanielThomas force-pushed the dannyt/immutable-segmented-string-decoding branch from 64116bf to 41cac4b Compare October 5, 2026 04:49
@joegoogle123
joegoogle123 force-pushed the dannyt/immutable-segmented-string-decoding branch from 41cac4b to e2718b4 Compare October 5, 2026 14:02

@jasonk000 jasonk000 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice work!

@DanielThomas
DanielThomas force-pushed the dannyt/immutable-segmented-string-decoding branch from e2718b4 to 4c6b5e6 Compare October 7, 2026 02:48
@DanielThomas

Copy link
Copy Markdown
Member Author

Gated direct segment string decoding and equality checks behind DIRECT_SEGMENT_STRING_READS, independently of the shard-read experiment. Default off.

Base automatically changed from dannyt/non-recycling-read-fast-path to master October 7, 2026 02:54
Extend the immutable segmented string fast path to equality checks used by primary-key matching. Decode VInts directly from captured byte-array segments instead of resolving each encoded byte through ByteData.get().

Keep the existing equality implementation for recycling and shared-memory data, and add segment-boundary, Unicode, surrogate-pair, read-state, and benchmark coverage.
@DanielThomas
DanielThomas force-pushed the dannyt/immutable-segmented-string-decoding branch from 4c6b5e6 to ffa7775 Compare October 7, 2026 02:54
@DanielThomas
DanielThomas merged commit fea8fcc into master Oct 7, 2026
3 checks passed
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