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 cce5f9b0d9 Avoid boxing in Framing (#1247)
cce5f9b0d9 is described below
commit cce5f9b0d9f691fe27996d58e7dd064d04c78f5b
Author: João Costa <[email protected]>
AuthorDate: Mon May 6 20:19:40 2024 +0100
Avoid boxing in Framing (#1247)
* Fix FramingBenchmark
* Add specialized indexOfByte
* Rename indexOfByte to indexOf
* Add missing @since(1.1.0) and reformat scaladoc
* Fix Scala 2.12 ambiguity problem
* Inline nextString
* Change default ByteString#indexOf to call indexWhere
This is always overriden, but I guess it won't hurt to have a default
* Remove default indexOf override
Calling indexWhere is already the default
* Fix MiMa issues
---
.../org/apache/pekko/util/ByteStringSpec.scala | 76 +++++++++++++++++++
.../org/apache/pekko/util/ByteString.scala | 86 +++++++++++++++++++++-
.../org/apache/pekko/util/ByteString.scala | 82 ++++++++++++++++++++-
.../scala-3/org/apache/pekko/util/ByteString.scala | 83 ++++++++++++++++++++-
.../org/apache/pekko/stream/FramingBenchmark.scala | 2 +-
5 files changed, 318 insertions(+), 11 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 aca2f242ed..d565174609 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
@@ -717,6 +717,82 @@ class ByteStringSpec extends AnyWordSpec with Matchers
with Checkers {
compact.indexOf('g', 5) should ===(5)
compact.indexOf('g', 6) should ===(-1)
}
+ "indexOf (specialized)" in {
+ ByteString.empty.indexOf(5.toByte) should ===(-1)
+ val byteString1 = ByteString1.fromString("abc")
+ byteString1.indexOf('a'.toByte) should ===(0)
+ byteString1.indexOf('b'.toByte) should ===(1)
+ byteString1.indexOf('c'.toByte) should ===(2)
+ byteString1.indexOf('d'.toByte) should ===(-1)
+
+ val byteStrings = ByteStrings(ByteString1.fromString("abc"),
ByteString1.fromString("efg"))
+ byteStrings.indexOf('a'.toByte) should ===(0)
+ byteStrings.indexOf('c'.toByte) should ===(2)
+ byteStrings.indexOf('d'.toByte) should ===(-1)
+ byteStrings.indexOf('e'.toByte) should ===(3)
+ byteStrings.indexOf('f'.toByte) should ===(4)
+ byteStrings.indexOf('g'.toByte) should ===(5)
+
+ val compact = byteStrings.compact
+ compact.indexOf('a'.toByte) should ===(0)
+ compact.indexOf('c'.toByte) should ===(2)
+ compact.indexOf('d'.toByte) should ===(-1)
+ compact.indexOf('e'.toByte) should ===(3)
+ compact.indexOf('f'.toByte) should ===(4)
+ compact.indexOf('g'.toByte) should ===(5)
+
+ }
+ "indexOf (specialized) from offset" in {
+ ByteString.empty.indexOf(5.toByte, -1) should ===(-1)
+ ByteString.empty.indexOf(5.toByte, 0) should ===(-1)
+ ByteString.empty.indexOf(5.toByte, 1) should ===(-1)
+ val byteString1 = ByteString1.fromString("abc")
+ byteString1.indexOf('d'.toByte, -1) should ===(-1)
+ byteString1.indexOf('d'.toByte, 0) should ===(-1)
+ byteString1.indexOf('d'.toByte, 1) should ===(-1)
+ byteString1.indexOf('d'.toByte, 4) should ===(-1)
+ byteString1.indexOf('a'.toByte, -1) should ===(0)
+ byteString1.indexOf('a'.toByte, 0) should ===(0)
+ byteString1.indexOf('a'.toByte, 1) should ===(-1)
+
+ val byteStrings = ByteStrings(ByteString1.fromString("abc"),
ByteString1.fromString("efg"))
+ byteStrings.indexOf('c'.toByte, -1) should ===(2)
+ byteStrings.indexOf('c'.toByte, 0) should ===(2)
+ byteStrings.indexOf('c'.toByte, 2) should ===(2)
+ byteStrings.indexOf('c'.toByte, 3) should ===(-1)
+
+ byteStrings.indexOf('e'.toByte, -1) should ===(3)
+ byteStrings.indexOf('e'.toByte, 0) should ===(3)
+ byteStrings.indexOf('e'.toByte, 1) should ===(3)
+ byteStrings.indexOf('e'.toByte, 4) should ===(-1)
+ byteStrings.indexOf('e'.toByte, 6) should ===(-1)
+
+ byteStrings.indexOf('g'.toByte, -1) should ===(5)
+ byteStrings.indexOf('g'.toByte, 0) should ===(5)
+ byteStrings.indexOf('g'.toByte, 1) should ===(5)
+ byteStrings.indexOf('g'.toByte, 4) should ===(5)
+ byteStrings.indexOf('g'.toByte, 5) should ===(5)
+ byteStrings.indexOf('g'.toByte, 6) should ===(-1)
+
+ val compact = byteStrings.compact
+ compact.indexOf('c'.toByte, -1) should ===(2)
+ compact.indexOf('c'.toByte, 0) should ===(2)
+ compact.indexOf('c'.toByte, 2) should ===(2)
+ compact.indexOf('c'.toByte, 3) should ===(-1)
+
+ compact.indexOf('e'.toByte, -1) should ===(3)
+ compact.indexOf('e'.toByte, 0) should ===(3)
+ compact.indexOf('e'.toByte, 1) should ===(3)
+ compact.indexOf('e'.toByte, 4) should ===(-1)
+ compact.indexOf('e'.toByte, 6) should ===(-1)
+
+ compact.indexOf('g'.toByte, -1) should ===(5)
+ compact.indexOf('g'.toByte, 0) should ===(5)
+ compact.indexOf('g'.toByte, 1) should ===(5)
+ compact.indexOf('g'.toByte, 4) should ===(5)
+ compact.indexOf('g'.toByte, 5) should ===(5)
+ compact.indexOf('g'.toByte, 6) should ===(-1)
+ }
"copyToArray" in {
val byteString = ByteString(1, 2) ++ ByteString(3) ++ ByteString(4)
diff --git a/actor/src/main/scala-2.12/org/apache/pekko/util/ByteString.scala
b/actor/src/main/scala-2.12/org/apache/pekko/util/ByteString.scala
index ac9d6ead76..4686525531 100644
--- a/actor/src/main/scala-2.12/org/apache/pekko/util/ByteString.scala
+++ b/actor/src/main/scala-2.12/org/apache/pekko/util/ByteString.scala
@@ -246,6 +246,20 @@ object ByteString {
}
}
+ override def indexOf(elem: Byte): Int = indexOf(elem, 0)
+ override def indexOf(elem: Byte, from: Int): Int = {
+ if (from >= length) -1
+ else {
+ var found = -1
+ var i = math.max(from, 0)
+ while (i < length && found == -1) {
+ if (bytes(i) == elem) found = i
+ i += 1
+ }
+ found
+ }
+ }
+
override def slice(from: Int, until: Int): ByteString =
if (from <= 0 && until >= length) this
else if (from >= length || until <= 0 || from >= until) ByteString.empty
@@ -433,6 +447,20 @@ object ByteString {
}
}
+ override def indexOf(elem: Byte): Int = indexOf(elem, 0)
+ override def indexOf(elem: Byte, from: Int): Int = {
+ if (from >= length) -1
+ else {
+ var found = -1
+ var i = math.max(from, 0)
+ while (i < length && found == -1) {
+ if (bytes(startIndex + i) == elem) found = i
+ i += 1
+ }
+ found
+ }
+ }
+
protected def writeReplace(): AnyRef = new SerializationProxy(this)
override def toArrayUnsafe(): Array[Byte] = {
@@ -682,8 +710,34 @@ object ByteString {
} else {
val subIndexOf = bs.indexOf(elem, relativeIndex)
if (subIndexOf < 0) {
- val nextString = bsIdx + 1
- find(nextString, relativeIndex - bs.length, bytesPassed +
bs.length)
+ find(bsIdx + 1, relativeIndex - bs.length, bytesPassed +
bs.length)
+ } else subIndexOf + bytesPassed
+ }
+ }
+ }
+
+ find(0, math.max(from, 0), 0)
+ }
+ }
+
+ override def indexOf(elem: Byte): Int = indexOf(elem, 0)
+ override def indexOf(elem: Byte, from: Int): Int = {
+ if (from >= length) -1
+ else {
+ val byteStringsSize = bytestrings.size
+
+ @tailrec
+ def find(bsIdx: Int, relativeIndex: Int, bytesPassed: Int): Int = {
+ if (bsIdx >= byteStringsSize) -1
+ else {
+ val bs = bytestrings(bsIdx)
+
+ if (bs.length <= relativeIndex) {
+ find(bsIdx + 1, relativeIndex - bs.length, bytesPassed +
bs.length)
+ } else {
+ val subIndexOf = bs.indexOf(elem, relativeIndex)
+ if (subIndexOf < 0) {
+ find(bsIdx + 1, relativeIndex - bs.length, bytesPassed +
bs.length)
} else subIndexOf + bytesPassed
}
}
@@ -788,7 +842,33 @@ sealed abstract class ByteString extends IndexedSeq[Byte]
with IndexedSeqOptimiz
override def indexWhere(p: Byte => Boolean): Int = iterator.indexWhere(p)
// optimized in subclasses
- override def indexOf[B >: Byte](elem: B): Int = indexOf(elem, 0)
+ override def indexOf[B >: Byte](elem: B): Int = indexOf[B](elem, 0)
+
+ // optimized version of indexOf for bytes, implemented in subclasses
+ /**
+ * 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.
+ *
+ * @param elem the element value to search for.
+ * @param from the start index
+ * @return the index `>= from` of the first element of this ByteString
that is equal (as determined by `==`)
+ * to `elem`, or `-1`, if none exists.
+ * @since 1.1.0
+ */
+ def indexOf(elem: Byte, from: Int): Int = indexOf[Byte](elem, from)
+
+ /**
+ * 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.
+ *
+ * @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 `==`)
+ * to `elem`, or `-1`, if none exists.
+ * @since 1.1.0
+ */
+ def indexOf(elem: Byte): Int = indexOf(elem, 0)
override def grouped(size: Int): Iterator[ByteString] = {
if (size <= 0) {
diff --git a/actor/src/main/scala-2.13/org/apache/pekko/util/ByteString.scala
b/actor/src/main/scala-2.13/org/apache/pekko/util/ByteString.scala
index ca3ee818d8..826d1b8120 100644
--- a/actor/src/main/scala-2.13/org/apache/pekko/util/ByteString.scala
+++ b/actor/src/main/scala-2.13/org/apache/pekko/util/ByteString.scala
@@ -243,6 +243,19 @@ object ByteString {
}
}
+ override def indexOf(elem: Byte, from: Int): Int = {
+ if (from >= length) -1
+ else {
+ var found = -1
+ var i = math.max(from, 0)
+ while (i < length && found == -1) {
+ if (bytes(i) == elem) found = i
+ i += 1
+ }
+ found
+ }
+ }
+
override def slice(from: Int, until: Int): ByteString =
if (from <= 0 && until >= length) this
else if (from >= length || until <= 0 || from >= until) ByteString.empty
@@ -434,6 +447,19 @@ object ByteString {
}
}
+ override def indexOf(elem: Byte, from: Int): Int = {
+ if (from >= length) -1
+ else {
+ var found = -1
+ var i = math.max(from, 0)
+ while (i < length && found == -1) {
+ if (bytes(startIndex + i) == elem) found = i
+ i += 1
+ }
+ found
+ }
+ }
+
override def copyToArray[B >: Byte](dest: Array[B], start: Int, len: Int):
Int = {
// min of the bytes available to copy, bytes there is room for in dest
and the requested number of bytes
val toCopy = math.min(math.min(len, length), dest.length - start)
@@ -691,8 +717,33 @@ object ByteString {
} else {
val subIndexOf = bs.indexOf(elem, relativeIndex)
if (subIndexOf < 0) {
- val nextString = bsIdx + 1
- find(nextString, relativeIndex - bs.length, bytesPassed +
bs.length)
+ find(bsIdx + 1, relativeIndex - bs.length, bytesPassed +
bs.length)
+ } else subIndexOf + bytesPassed
+ }
+ }
+ }
+
+ find(0, math.max(from, 0), 0)
+ }
+ }
+
+ override def indexOf(elem: Byte, from: Int): Int = {
+ if (from >= length) -1
+ else {
+ val byteStringsSize = bytestrings.size
+
+ @tailrec
+ def find(bsIdx: Int, relativeIndex: Int, bytesPassed: Int): Int = {
+ if (bsIdx >= byteStringsSize) -1
+ else {
+ val bs = bytestrings(bsIdx)
+
+ if (bs.length <= relativeIndex) {
+ find(bsIdx + 1, relativeIndex - bs.length, bytesPassed +
bs.length)
+ } else {
+ val subIndexOf = bs.indexOf(elem, relativeIndex)
+ if (subIndexOf < 0) {
+ find(bsIdx + 1, relativeIndex - bs.length, bytesPassed +
bs.length)
} else subIndexOf + bytesPassed
}
}
@@ -821,8 +872,31 @@ sealed abstract class ByteString
override def indexWhere(p: Byte => Boolean, from: Int): Int =
iterator.indexWhere(p, from)
- // optimized in subclasses
- override def indexOf[B >: Byte](elem: B, from: Int): Int = indexOf(elem,
from)
+ // optimized version of indexOf for bytes, optimized in subclasses
+ /**
+ * 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.
+ *
+ * @param elem the element value to search for.
+ * @param from the start index
+ * @return the index `>= from` of the first element of this ByteString
that is equal (as determined by `==`)
+ * to `elem`, or `-1`, if none exists.
+ * @since 1.1.0
+ */
+ def indexOf(elem: Byte, from: Int): Int = indexOf[Byte](elem, from)
+
+ /**
+ * 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.
+ *
+ * @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 `==`)
+ * to `elem`, or `-1`, if none exists.
+ * @since 1.1.0
+ */
+ def indexOf(elem: Byte): Int = indexOf(elem, 0)
override def grouped(size: Int): Iterator[ByteString] = {
if (size <= 0) {
diff --git a/actor/src/main/scala-3/org/apache/pekko/util/ByteString.scala
b/actor/src/main/scala-3/org/apache/pekko/util/ByteString.scala
index e8e64af848..7aba8dc515 100644
--- a/actor/src/main/scala-3/org/apache/pekko/util/ByteString.scala
+++ b/actor/src/main/scala-3/org/apache/pekko/util/ByteString.scala
@@ -244,6 +244,19 @@ object ByteString {
}
}
+ override def indexOf(elem: Byte, from: Int): Int = {
+ if (from >= length) -1
+ else {
+ var found = -1
+ var i = math.max(from, 0)
+ while (i < length && found == -1) {
+ if (bytes(i) == elem) found = i
+ i += 1
+ }
+ found
+ }
+ }
+
override def slice(from: Int, until: Int): ByteString =
if (from <= 0 && until >= length) this
else if (from >= length || until <= 0 || from >= until) ByteString.empty
@@ -435,6 +448,19 @@ object ByteString {
}
}
+ override def indexOf(elem: Byte, from: Int): Int = {
+ if (from >= length) -1
+ else {
+ var found = -1
+ var i = math.max(from, 0)
+ while (i < length && found == -1) {
+ if (bytes(startIndex + i) == elem) found = i
+ i += 1
+ }
+ found
+ }
+ }
+
override def copyToArray[B >: Byte](dest: Array[B], start: Int, len: Int):
Int = {
// min of the bytes available to copy, bytes there is room for in dest
and the requested number of bytes
val toCopy = math.min(math.min(len, length), dest.length - start)
@@ -692,8 +718,33 @@ object ByteString {
} else {
val subIndexOf = bs.indexOf(elem, relativeIndex)
if (subIndexOf < 0) {
- val nextString = bsIdx + 1
- find(nextString, relativeIndex - bs.length, bytesPassed +
bs.length)
+ find(bsIdx + 1, relativeIndex - bs.length, bytesPassed +
bs.length)
+ } else subIndexOf + bytesPassed
+ }
+ }
+ }
+
+ find(0, math.max(from, 0), 0)
+ }
+ }
+
+ override def indexOf(elem: Byte, from: Int): Int = {
+ if (from >= length) -1
+ else {
+ val byteStringsSize = bytestrings.size
+
+ @tailrec
+ def find(bsIdx: Int, relativeIndex: Int, bytesPassed: Int): Int = {
+ if (bsIdx >= byteStringsSize) -1
+ else {
+ val bs = bytestrings(bsIdx)
+
+ if (bs.length <= relativeIndex) {
+ find(bsIdx + 1, relativeIndex - bs.length, bytesPassed +
bs.length)
+ } else {
+ val subIndexOf = bs.indexOf(elem, relativeIndex)
+ if (subIndexOf < 0) {
+ find(bsIdx + 1, relativeIndex - bs.length, bytesPassed +
bs.length)
} else subIndexOf + bytesPassed
}
}
@@ -823,7 +874,33 @@ sealed abstract class ByteString
override def indexWhere(p: Byte => Boolean, from: Int): Int =
iterator.indexWhere(p, from)
// optimized in subclasses
- override def indexOf[B >: Byte](elem: B, from: Int): Int = indexOf(elem,
from)
+ override def indexOf[B >: Byte](elem: B, from: Int): Int =
super.indexOf(elem, from)
+
+ // optimized version of indexOf for bytes, optimized in subclasses
+ /**
+ * 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.
+ *
+ * @param elem the element value to search for.
+ * @param from the start index
+ * @return the index `>= from` of the first element of this ByteString
that is equal (as determined by `==`)
+ * to `elem`, or `-1`, if none exists.
+ * @since 1.1.0
+ */
+ def indexOf(elem: Byte, from: Int): Int = indexOf[Byte](elem, from)
+
+ /**
+ * 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.
+ *
+ * @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 `==`)
+ * to `elem`, or `-1`, if none exists.
+ * @since 1.1.0
+ */
+ def indexOf(elem: Byte): Int = indexOf(elem, 0)
override def grouped(size: Int): Iterator[ByteString] = {
if (size <= 0) {
diff --git
a/bench-jmh/src/main/scala/org/apache/pekko/stream/FramingBenchmark.scala
b/bench-jmh/src/main/scala/org/apache/pekko/stream/FramingBenchmark.scala
index e3a1f54521..269b120dbe 100644
--- a/bench-jmh/src/main/scala/org/apache/pekko/stream/FramingBenchmark.scala
+++ b/bench-jmh/src/main/scala/org/apache/pekko/stream/FramingBenchmark.scala
@@ -80,7 +80,7 @@ class FramingBenchmark {
val frame = List.range(0, messageSize, 1).map(_ =>
Random.nextPrintableChar()).mkString + "\n"
val messageChunk = ByteString(List.range(0, framePerSeq, 1).map(_ =>
frame).mkString)
- Source
+ flow = Source
.fromGraph(new BenchTestSourceSameElement(100000, messageChunk))
.via(Framing.delimiter(ByteString("\n"), Int.MaxValue))
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]