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
This commit is contained in:
João Costa 2024-05-06 20:19:40 +01:00 committed by GitHub
parent b6f2c5774e
commit cce5f9b0d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 318 additions and 11 deletions

View file

@ -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)

View file

@ -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) {

View file

@ -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) {

View file

@ -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) {

View file

@ -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))
}