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]

Reply via email to