Skip to content

Commit c62ddcb

Browse files
committed
copy over some code from Netty that uses SWAR techniques for checking array segments match
1 parent 0e2eeb5 commit c62ddcb

File tree

4 files changed

+91
-25
lines changed

4 files changed

+91
-25
lines changed

LICENSE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ Copyright EPFL and Lightbend, Inc.
222222
pekko-actor contains code from Netty which was released under an Apache 2.0 license.
223223
Copyright 2014 The Netty Project
224224
- actor/src/main/scala/org/apache/pekko/io/dns/DnsSettings.scala
225+
- actor/src/main/scala/org/apache/pekko/util/ByteString.scala
225226
- actor/src/main/scala/org/apache/pekko/util/SWARUtil.scala
226227

227228
---------------

actor/src/main/scala/org/apache/pekko/util/ByteString.scala

Lines changed: 88 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,21 @@
1111
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
1212
*/
1313

14+
/*
15+
* Copyright 2012 The Netty Project
16+
*
17+
* The Netty Project licenses this file to you under the Apache License, version 2.0 (the
18+
* "License"); you may not use this file except in compliance with the License. You may obtain a
19+
* copy of the License at:
20+
*
21+
* https://www.apache.org/licenses/LICENSE-2.0
22+
*
23+
* Unless required by applicable law or agreed to in writing, software distributed under the License
24+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
25+
* or implied. See the License for the specific language governing permissions and limitations under
26+
* the License.
27+
*/
28+
1429
package org.apache.pekko.util
1530

1631
import java.io.{ InputStream, ObjectInputStream, ObjectOutputStream, SequenceInputStream }
@@ -314,6 +329,31 @@ object ByteString {
314329
else -1
315330
}
316331

332+
// Derived from code in Netty
333+
// https://github.com/netty/netty/blob/d28a0fc6598b50fbe8f296831777cf4b653a475f/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java#L366-L408
334+
override private[util] def bytesMatch(fromIndex: Int, checkBytes: Array[Byte], bytesFromIndex: Int,
335+
checkLength: Int): Boolean = {
336+
var aIndex = fromIndex
337+
var bIndex = bytesFromIndex
338+
val longCount = checkLength >>> 3
339+
val byteCount = checkLength & 7
340+
var i = 0
341+
while (i < longCount) {
342+
if (SWARUtil.getLong(bytes, aIndex) != SWARUtil.getLong(checkBytes, bIndex)) return false
343+
aIndex += 8
344+
bIndex += 8
345+
i += 1
346+
}
347+
i = 0
348+
while (i < byteCount) {
349+
if (apply(aIndex) != checkBytes(bIndex)) return false
350+
aIndex += 1
351+
bIndex += 1
352+
i += 1
353+
}
354+
true
355+
}
356+
317357
override def slice(from: Int, until: Int): ByteString =
318358
if (from <= 0 && until >= length) this
319359
else if (from >= length || until <= 0 || from >= until) ByteString.empty
@@ -575,6 +615,31 @@ object ByteString {
575615
else -1
576616
}
577617

618+
// Derived from code in Netty
619+
// https://github.com/netty/netty/blob/d28a0fc6598b50fbe8f296831777cf4b653a475f/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java#L366-L408
620+
override private[util] def bytesMatch(fromIndex: Int, checkBytes: Array[Byte], bytesFromIndex: Int,
621+
checkLength: Int): Boolean = {
622+
var aIndex = fromIndex + startIndex
623+
var bIndex = bytesFromIndex
624+
val longCount = checkLength >>> 3
625+
val byteCount = checkLength & 7
626+
var i = 0
627+
while (i < longCount) {
628+
if (SWARUtil.getLong(bytes, aIndex) != SWARUtil.getLong(checkBytes, bIndex)) return false
629+
aIndex += 8
630+
bIndex += 8
631+
i += 1
632+
}
633+
i = 0
634+
while (i < byteCount) {
635+
if (apply(aIndex) != checkBytes(bIndex)) return false
636+
aIndex += 1
637+
bIndex += 1
638+
i += 1
639+
}
640+
true
641+
}
642+
578643
override def copyToArray[B >: Byte](dest: Array[B], start: Int, len: Int): Int = {
579644
// min of the bytes available to copy, bytes there is room for in dest and the requested number of bytes
580645
val toCopy = math.min(math.min(len, length), dest.length - start)
@@ -912,6 +977,20 @@ object ByteString {
912977
}
913978
}
914979

980+
private[util] def bytesMatch(fromIndex: Int, checkBytes: Array[Byte], checkBytesFromIndex: Int, checkLength: Int)
981+
: Boolean = {
982+
if (checkLength > 1 && bytestrings.nonEmpty && bytestrings.head.length >= fromIndex + checkLength - 1) {
983+
bytestrings.head.bytesMatch(fromIndex, checkBytes, checkBytesFromIndex, checkLength)
984+
} else {
985+
var i = 0
986+
while (i < checkLength) {
987+
if (apply(fromIndex + i) != checkBytes(checkBytesFromIndex + i)) return false
988+
i += 1
989+
}
990+
true
991+
}
992+
}
993+
915994
protected def writeReplace(): AnyRef = new SerializationProxy(this)
916995
}
917996

@@ -1093,22 +1172,10 @@ sealed abstract class ByteString
10931172
* @since 2.0.0
10941173
*/
10951174
def indexOfSlice(slice: Array[Byte], from: Int): Int = {
1096-
// this is only called if the first byte matches, so we can skip that check
1097-
def check(startPos: Int): Boolean = {
1098-
var i = startPos + 1
1099-
var j = 1
1100-
// let's trust the calling code has ensured that we have enough bytes in this ByteString
1101-
while (j < slice.length) {
1102-
if (apply(i) != slice(j)) return false
1103-
i += 1
1104-
j += 1
1105-
}
1106-
true
1107-
}
11081175
@tailrec def rec(from: Int): Int = {
11091176
val startPos = indexOf(slice.head, from, length - slice.length + 1)
11101177
if (startPos == -1) -1
1111-
else if (check(startPos)) startPos
1178+
else if (bytesMatch(startPos, slice, 0, slice.length)) startPos
11121179
else rec(startPos + 1)
11131180
}
11141181
val sliceLength = slice.length
@@ -1147,18 +1214,7 @@ sealed abstract class ByteString
11471214
*/
11481215
def startsWith(bytes: Array[Byte], offset: Int): Boolean = {
11491216
if (length - offset < bytes.length) false
1150-
else {
1151-
var i = offset
1152-
var j = 0
1153-
while (j < bytes.length) {
1154-
// we know that byteString is at least as long as bytes,
1155-
// so no need to check i < length
1156-
if (apply(i) != bytes(j)) return false
1157-
i += 1
1158-
j += 1
1159-
}
1160-
true
1161-
}
1217+
else bytesMatch(offset, bytes, 0, bytes.length)
11621218
}
11631219

11641220
/**
@@ -1170,6 +1226,13 @@ sealed abstract class ByteString
11701226
*/
11711227
def startsWith(bytes: Array[Byte]): Boolean = startsWith(bytes, 0)
11721228

1229+
/**
1230+
* Tests whether the bytes in a segment of this ByteString match the provided bytes.
1231+
* Internal use only. ByteString1 and ByteString1C have optimized versions.
1232+
*/
1233+
private[util] def bytesMatch(fromIndex: Int, checkBytes: Array[Byte], checkBytesFromIndex: Int, checkLength: Int)
1234+
: Boolean
1235+
11731236
override def grouped(size: Int): Iterator[ByteString] = {
11741237
if (size <= 0) {
11751238
throw new IllegalArgumentException(s"size=$size must be positive")

actor/src/main/scala/org/apache/pekko/util/SWARUtil.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ private[util] object SWARUtil {
7777
* @param array the byte array to read from
7878
* @param index the index to read from
7979
* @return the long value at the specified index
80+
* @throws IndexOutOfBoundsException if index is out of bounds
8081
*/
8182
def getLong(array: Array[Byte], index: Int): Long = {
8283
if (longBeArrayViewSupported) {

legal/pekko-actor-jar-license.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ Copyright EPFL and Lightbend, Inc.
222222
pekko-actor contains code from Netty which was released under an Apache 2.0 license.
223223
Copyright 2014 The Netty Project
224224
- actor/src/main/scala/org/apache/pekko/io/dns/DnsSettings.scala
225+
- actor/src/main/scala/org/apache/pekko/util/ByteString.scala
225226
- actor/src/main/scala/org/apache/pekko/util/SWARUtil.scala
226227

227228
---------------

0 commit comments

Comments
 (0)