This is an automated email from the ASF dual-hosted git repository.
fanningpj 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 e62592c4b4 specialized version of indexOfSlice (#2306)
e62592c4b4 is described below
commit e62592c4b49dafdf1ab20f941be8a89b17bdb6c1
Author: PJ Fanning <[email protected]>
AuthorDate: Sat Oct 18 09:24:56 2025 +0100
specialized version of indexOfSlice (#2306)
* try specialized version of indexOf
* specialised ByteStrings indexOfSlice
* Update ByteString.scala
Update ByteString.scala
Update ByteString.scala
Update ByteString.scala
* remove ByteStrings indexOfSlice
* Update ByteString.scala
add test
add test
Update ByteString.scala
* Update actor/src/main/scala/org/apache/pekko/util/ByteString.scala
Co-authored-by: Copilot <[email protected]>
* Update actor/src/main/scala/org/apache/pekko/util/ByteString.scala
Co-authored-by: Copilot <[email protected]>
* Apply suggestion from @Copilot
Co-authored-by: Copilot <[email protected]>
* Update ByteStringSpec.scala
* when slice is len=1, use indexOf instead
---------
Co-authored-by: Copilot <[email protected]>
---
.../org/apache/pekko/util/ByteStringSpec.scala | 45 +++++++++
.../scala/org/apache/pekko/util/ByteString.scala | 109 ++++++++++++++++++++-
2 files changed, 152 insertions(+), 2 deletions(-)
diff --git
a/actor-tests/src/test/scala/org/apache/pekko/util/ByteStringSpec.scala
b/actor-tests/src/test/scala/org/apache/pekko/util/ByteStringSpec.scala
index 35d96f80be..81fe9cc93b 100644
--- a/actor-tests/src/test/scala/org/apache/pekko/util/ByteStringSpec.scala
+++ b/actor-tests/src/test/scala/org/apache/pekko/util/ByteStringSpec.scala
@@ -1041,16 +1041,61 @@ class ByteStringSpec extends AnyWordSpec with Matchers
with Checkers {
val slice0 = ByteString1.fromString("xyz")
val slice1 = ByteString1.fromString("xyzabc")
val notSlice = ByteString1.fromString("12345")
+ val pByte = ByteString1.fromString("p")
val byteStringLong = ByteString1.fromString("abcdefghijklmnopqrstuvwxyz")
val byteStrings = ByteStrings(byteStringLong, byteStringLong)
byteStringLong.indexOfSlice(slice0) should ===(23)
byteStringLong.indexOfSlice(slice1) should ===(-1)
byteStringLong.indexOfSlice(notSlice) should ===(-1)
+ byteStringLong.indexOfSlice(pByte) should ===(15)
byteStrings.indexOfSlice(slice0) should ===(23)
byteStrings.indexOfSlice(slice1) should ===(23)
byteStrings.indexOfSlice(notSlice) should ===(-1)
+ byteStrings.indexOfSlice(pByte) should ===(15)
+ byteStrings.indexOfSlice(pByte, 16) should ===(41)
+
+ val byteStringXxyz = ByteString1.fromString("xxyz")
+ byteStringXxyz.indexOfSlice(slice0) should ===(1)
+ byteStringXxyz.indexOfSlice(slice0, 2) should ===(-1)
}
+ "indexOfSlice (specialized)" in {
+ val slice0 = "xyz".getBytes(StandardCharsets.UTF_8)
+ val slice1 = "xyzabc".getBytes(StandardCharsets.UTF_8)
+ val notSlice = "12345".getBytes(StandardCharsets.UTF_8)
+ val pByte = Array('p'.toByte)
+ val byteStringLong = ByteString1.fromString("abcdefghijklmnopqrstuvwxyz")
+ val byteStrings = ByteStrings(byteStringLong, byteStringLong)
+ byteStringLong.indexOfSlice(slice0) should ===(23)
+ byteStringLong.indexOfSlice(slice1) should ===(-1)
+ byteStringLong.indexOfSlice(notSlice) should ===(-1)
+ byteStringLong.indexOfSlice(pByte) should ===(15)
+
+ byteStrings.indexOfSlice(slice0) should ===(23)
+ byteStrings.indexOfSlice(slice1) should ===(23)
+ byteStrings.indexOfSlice(notSlice) should ===(-1)
+ byteStrings.indexOfSlice(pByte) should ===(15)
+ byteStrings.indexOfSlice(pByte, 16) should ===(41)
+
+ val byteStringXxyz = ByteString1.fromString("xxyz")
+ byteStringXxyz.indexOfSlice(slice0) should ===(1)
+ byteStringXxyz.indexOfSlice(slice0, 2) should ===(-1)
+ }
+ "startsWith (specialized)" in {
+ val slice0 = "abc".getBytes(StandardCharsets.UTF_8)
+ val slice1 = "xyz".getBytes(StandardCharsets.UTF_8)
+ val notSlice = "12345".getBytes(StandardCharsets.UTF_8)
+ val byteStringLong = ByteString1.fromString("abcdefghijklmnopqrstuvwxyz")
+ val byteStrings = ByteStrings(byteStringLong, byteStringLong)
+ byteStringLong.startsWith(slice0) should ===(true)
+ byteStringLong.startsWith(slice1, 23) should ===(true)
+ byteStringLong.startsWith(notSlice) should ===(false)
+
+ byteStrings.startsWith(slice0) should ===(true)
+ byteStrings.startsWith(slice1, 23) should ===(true)
+ byteStrings.startsWith(notSlice) should ===(false)
+ }
+
}
"A ByteString" must {
diff --git a/actor/src/main/scala/org/apache/pekko/util/ByteString.scala
b/actor/src/main/scala/org/apache/pekko/util/ByteString.scala
index 849067dc2a..952e3fe060 100644
--- a/actor/src/main/scala/org/apache/pekko/util/ByteString.scala
+++ b/actor/src/main/scala/org/apache/pekko/util/ByteString.scala
@@ -1034,7 +1034,7 @@ sealed abstract class ByteString
/**
* Finds index of first occurrence of some byte in this ByteString after or
at some start index.
*
- * Similar to indexOf, but it avoids boxing if the value is already a byte.
+ * Similar to Seq's indexOf, but it avoids boxing if the value is already a
byte.
*
* @param elem the element value to search for.
* @param from the start index
@@ -1047,7 +1047,7 @@ sealed abstract class ByteString
/**
* Finds index of first occurrence of some byte in this ByteString.
*
- * Similar to indexOf, but it avoids boxing if the value is already a byte.
+ * Similar to Seq's indexOf, but it avoids boxing if the value is already a
byte.
*
* @param elem the element value to search for.
* @return the index `>= from` of the first element of this ByteString
that is equal (as determined by `==`)
@@ -1056,6 +1056,78 @@ sealed abstract class ByteString
*/
def indexOf(elem: Byte): Int = indexOf(elem, 0)
+ override def indexOfSlice[B >: Byte](slice: scala.collection.Seq[B], from:
Int): Int = {
+ // this is only called if the first byte matches, so we can skip that check
+ def check(startPos: Int): Boolean = {
+ var i = startPos + 1
+ var j = 1
+ // Bounds are guaranteed by the indexOf call limiting the search range,
so startPos + slice.length <= length.
+ while (j < slice.length) {
+ if (apply(i) != slice(j)) return false
+ i += 1
+ j += 1
+ }
+ true
+ }
+ val headByte = slice.head.asInstanceOf[Byte]
+ @tailrec def rec(from: Int): Int = {
+ val startPos = indexOf(headByte, from, length - slice.length + 1)
+ if (startPos == -1) -1
+ else if (check(startPos)) startPos
+ else rec(startPos + 1)
+ }
+ val sliceLength = slice.length
+ if (sliceLength == 0) 0
+ else if (sliceLength == 1) indexOf(headByte, from)
+ else rec(math.max(0, from))
+ }
+
+ /**
+ * Finds index of first occurrence of some slice in this ByteString.
+ *
+ * @param slice the slice to search for.
+ * @param from the start index
+ * @return the index greater than or equal to `from` of the first element
of this
+ * ByteString that starts a slice equal (as determined by `==`)
+ * to `slice`, or `-1`, if none exists.
+ * @since 2.0.0
+ */
+ def indexOfSlice(slice: Array[Byte], from: Int): Int = {
+ // this is only called if the first byte matches, so we can skip that check
+ def check(startPos: Int): Boolean = {
+ var i = startPos + 1
+ var j = 1
+ // let's trust the calling code has ensured that we have enough bytes in
this ByteString
+ while (j < slice.length) {
+ if (apply(i) != slice(j)) return false
+ i += 1
+ j += 1
+ }
+ true
+ }
+ @tailrec def rec(from: Int): Int = {
+ val startPos = indexOf(slice.head, from, length - slice.length + 1)
+ if (startPos == -1) -1
+ else if (check(startPos)) startPos
+ else rec(startPos + 1)
+ }
+ val sliceLength = slice.length
+ if (sliceLength == 0) 0
+ else if (sliceLength == 1) indexOf(slice.head, from)
+ else rec(math.max(0, from))
+ }
+
+ /**
+ * Finds index of first occurrence of some slice in this ByteString.
+ *
+ * @param slice the slice to search for.
+ * @return the index of the first element of this
+ * ByteString that starts a slice equal (as determined by `==`)
+ * to `slice`, or `-1`, if none exists.
+ * @since 2.0.0
+ */
+ def indexOfSlice(slice: Array[Byte]): Int = indexOfSlice(slice, 0)
+
override def contains[B >: Byte](elem: B): Boolean = indexOf(elem, 0) != -1
/**
@@ -1065,6 +1137,39 @@ sealed abstract class ByteString
*/
def contains(elem: Byte): Boolean = indexOf(elem, 0) != -1
+ /**
+ * Tests whether this ByteString starts with the given slice.
+ *
+ * @param bytes the slice to test
+ * @param offset the offset to start testing from
+ * @return true if this ByteString starts with the given slice
+ * @since 2.0.0
+ */
+ def startsWith(bytes: Array[Byte], offset: Int): Boolean = {
+ if (length - offset < bytes.length) false
+ else {
+ var i = offset
+ var j = 0
+ while (j < bytes.length) {
+ // we know that byteString is at least as long as bytes,
+ // so no need to check i < length
+ if (apply(i) != bytes(j)) return false
+ i += 1
+ j += 1
+ }
+ true
+ }
+ }
+
+ /**
+ * Tests whether this ByteString starts with the given slice.
+ *
+ * @param bytes the slice to test
+ * @return true if this ByteString starts with the given slice
+ * @since 2.0.0
+ */
+ def startsWith(bytes: Array[Byte]): Boolean = startsWith(bytes, 0)
+
override def grouped(size: Int): Iterator[ByteString] = {
if (size <= 0) {
throw new IllegalArgumentException(s"size=$size must be positive")
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]