This is an automated email from the ASF dual-hosted git repository.
pjfanning pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko.git
The following commit(s) were added to refs/heads/main by this push:
new 819ec921bd perf: avoid redundant bounds checks on ByteString indexed
reads (#3533)
819ec921bd is described below
commit 819ec921bdda9c106982bb86a5b796880637d3aa
Author: PJ Fanning <[email protected]>
AuthorDate: Tue Sep 8 08:21:44 2026 +0100
perf: avoid redundant bounds checks on ByteString indexed reads (#3533)
Motivation:
ByteString.apply(idx) bounds-checks before delegating to byteAtUnchecked,
and for the ByteStrings composite it also resolves the absolute offset to a
fragment. Two call sites under src/main pay that check when the caller has
already established the bound:
- ByteStringParser.ByteReader.readByte guards with `off < input.length` and
then calls input(off), so the bound is tested twice. Its siblings
readShortLE/readIntLE/readLongLE already use the *Unchecked variants after
their own guard, so readByte was the odd one out. ByteReader backs
gzip/deflate header parsing, TLS and framing.
- TcpDnsClient.decodeLength read the two length-prefix bytes with two apply
calls: two bounds checks, and for a composite ByteString two fragment
lookups.
These are the only two places under src/main that index a ByteString outside
ByteString.scala itself and bench-jmh.
Modification:
readByte now calls input.byteAtUnchecked(off). `off` starts at 0 and only
ever
moves forward, so the existing guard is sufficient.
decodeLength now uses data.readShortBE(0) & 0xFFFF. readShortBE keeps the
bounds check, so short input still throws IndexOutOfBoundsException as
before,
and it is SWAR-optimised for the single-array ByteString1C/ByteString1 that
the
common single-chunk read path produces.
Result:
One bounds check per byte instead of two in the stream parser, and one
checked
16-bit read instead of two indexed reads for the DNS length prefix.
Behaviour
is unchanged.
Tests:
- sbt "stream-tests/testOnly
org.apache.pekko.stream.io.ByteStringParserSpec
org.apache.pekko.stream.scaladsl.CompressionSpec
org.apache.pekko.stream.io.compression.*" - 106 succeeded, 0 failed
- sbt "actor-tests/testOnly
org.apache.pekko.io.dns.internal.TcpDnsClientSpec" - 8 succeeded, 0 failed
- sbt "actor/mimaReportBinaryIssues" "stream/mimaReportBinaryIssues" -
success
- Native scalafmt run on the two changed files.
- No new tests: both changes are behaviour-preserving refactors of internal
code, covered by the existing specs above.
- Not benchmarked. The change is a strict reduction in work per read, not
an algorithmic change.
References:
None - found by reviewing ByteString indexed access under src/main
---
.../src/main/scala/org/apache/pekko/io/dns/internal/TcpDnsClient.scala | 2 +-
.../main/scala/org/apache/pekko/stream/impl/io/ByteStringParser.scala | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git
a/actor/src/main/scala/org/apache/pekko/io/dns/internal/TcpDnsClient.scala
b/actor/src/main/scala/org/apache/pekko/io/dns/internal/TcpDnsClient.scala
index 4db1d1503d..cb8db381f4 100644
--- a/actor/src/main/scala/org/apache/pekko/io/dns/internal/TcpDnsClient.scala
+++ b/actor/src/main/scala/org/apache/pekko/io/dns/internal/TcpDnsClient.scala
@@ -107,7 +107,7 @@ private[internal] object TcpDnsClient {
def encodeLength(length: Int): ByteString =
ByteString(((length >> 8) & 0xFF).toByte, (length & 0xFF).toByte)
- def decodeLength(data: ByteString): Int = ((data(0) & 0xFF) << 8) | (data(1)
& 0xFF)
+ def decodeLength(data: ByteString): Int = data.readShortBE(0) & 0xFFFF
def throwFailure(message: String, cause: Option[Throwable]): Unit =
cause match {
diff --git
a/stream/src/main/scala/org/apache/pekko/stream/impl/io/ByteStringParser.scala
b/stream/src/main/scala/org/apache/pekko/stream/impl/io/ByteStringParser.scala
index bd81867026..e36f364e3b 100644
---
a/stream/src/main/scala/org/apache/pekko/stream/impl/io/ByteStringParser.scala
+++
b/stream/src/main/scala/org/apache/pekko/stream/impl/io/ByteStringParser.scala
@@ -215,7 +215,8 @@ import pekko.util.ByteString
def readByte(): Int =
if (off < input.length) {
- val x = input(off)
+ // bounds already checked above, and off never goes backwards, so skip
the check in apply
+ val x = input.byteAtUnchecked(off)
off += 1
x & 0xFF
} else throw NeedMoreData
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]