Conversation
Motivation: UnsynchronizedByteArrayInputStream only overrode the basic read/skip methods, so readAllBytes, readNBytes, skipNBytes and transferTo fell through to the generic InputStream defaults. Those allocate 16 KiB scratch buffers in a loop and then concatenate, which is wasteful when the whole payload already sits in a byte[]. skip also advanced the offset unconditionally with Math.addExact/toIntExact, so it could throw ArithmeticException for large values and leave offset past eod. Modification: - Override readAllBytes, readNBytes(int), readNBytes(byte[],int,int), skipNBytes and transferTo to operate directly on the backing array (one Arrays.copyOfRange / OutputStream.write each). - Clamp skip to the remaining bytes so offset never exceeds eod and large skips cannot overflow; simplify available() and readLocal() accordingly. - Simplify the (data, offset, length) constructor so offset/eod are clamped to the array with long arithmetic (no int overflow), and use Objects.checkFromIndexSize for the read(byte[],int,int) bounds check. - Expand UnsynchronizedByteArrayInputStreamSpec to cover every method and edge case (EOF, zero-length reads, clamping, overflow). - Add readAllBytes and transferTo cases to ByteString_asInputStream_Benchmark. Result: readAllBytes/readNBytes/transferTo on ByteString.asInputStream copy the data once instead of chunking through temporary buffers. Local JMH (-f 1 -wi 2 -i 3) for single_bs_as_input_stream_read_all_bytes: 10 KB 201k -> 503k ops/s, 1000 KB 3.3k -> 5.5k ops/s. Tests: - sbt "actor-tests/testOnly org.apache.pekko.util.UnsynchronizedByteArrayInputStreamSpec org.apache.pekko.util.ByteStringSpec" (226 passed) - sbt "actor/mimaReportBinaryIssues" (no issues) - sbt "bench-jmh/Jmh/compile" and Jmh/run of the new benchmarks - scalafmt on changed Scala files, sbt actor/javafmtAll (JDK 17) - git diff --check References: None - follow-up to apache#2300 which introduced this class
pjfanning
force-pushed
the
optimise-unsync-bais
branch
from
September 13, 2026 17:02
4d7beb0 to
fee78f9
Compare
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.
Motivation
UnsynchronizedByteArrayInputStream(used byByteString.asInputStreamand the Java/Jackson serializers) only overrode the basicread/skipmethods.readAllBytes,readNBytes,skipNBytesandtransferTofell through to the genericInputStreamdefaults, which allocate 16 KiB scratch buffers in a loop and then concatenate — wasteful when the whole payload already sits in abyte[].skipalso advancedoffsetunconditionally viaMath.addExact(offset, Math.toIntExact(n)), so a largencould throwArithmeticExceptionandoffsetcould end up pasteod.Modification
readAllBytes,readNBytes(int),readNBytes(byte[],int,int),skipNBytesandtransferToto operate directly on the backing array (oneArrays.copyOfRange/OutputStream.writeeach) — the same set of overridesjava.io.ByteArrayInputStreamhas.skipto the remaining bytes sooffset <= eodalways holds and large skips cannot overflow; simplifyavailable()andreadLocal()accordingly.(data, offset, length)constructor sooffset/eodare clamped to the array using long arithmetic (no int overflow), and useObjects.checkFromIndexSizefor theread(byte[],int,int)bounds check.UnsynchronizedByteArrayInputStreamSpecto cover every method and edge case (EOF, zero-length reads, clamping, overflow, negative args).readAllBytesandtransferTocases toByteString_asInputStream_Benchmark.Result
readAllBytes/readNBytes/transferToonByteString.asInputStreamcopy the data once instead of chunking through temporary buffers. Observable behaviour is otherwise unchanged (negativeskipstill throws, as before).Local JMH (
-f 1 -wi 2 -i 3, short run so error bars are wide):single_bs_as_input_stream_read_all_bytessingle_bs_as_input_stream_read_all_bytessingle_bs_as_input_stream_transfer_tosingle_bs_as_input_stream_transfer_totransferTois dominated by theByteArrayOutputStreamcopy on the receiving side, so the difference there is within noise.Tests
sbt "actor-tests/testOnly org.apache.pekko.util.UnsynchronizedByteArrayInputStreamSpec org.apache.pekko.util.ByteStringSpec"— 226 passedsbt "actor/mimaReportBinaryIssues"— no issues (class is@InternalApi; only methods added)sbt "bench-jmh/Jmh/compile"andJmh/runof the new benchmarksscalafmton changed Scala files,sbt actor/javafmtAllon JDK 17git diff --checkReferences
None - follow-up to #2300 which introduced this class