diff --git a/akka-actor-tests/src/test/scala/akka/util/ByteStringSpec.scala b/akka-actor-tests/src/test/scala/akka/util/ByteStringSpec.scala index a6a614475c..98a76a8128 100644 --- a/akka-actor-tests/src/test/scala/akka/util/ByteStringSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/util/ByteStringSpec.scala @@ -292,22 +292,23 @@ class ByteStringSpec extends WordSpec with Matchers with Checkers { } "ByteString1" must { - "drop(0)" in { - ByteString1.fromString("").drop(0) should ===(ByteString.empty) + "drop" in { + ByteString1.empty.drop(-1) should ===(ByteString("")) + ByteString1.empty.drop(0) should ===(ByteString("")) + ByteString1.empty.drop(1) should ===(ByteString("")) + ByteString1.fromString("a").drop(-1) should ===(ByteString("a")) ByteString1.fromString("a").drop(0) should ===(ByteString("a")) - } - "drop(1)" in { - ByteString1.fromString("").drop(1) should ===(ByteString("")) ByteString1.fromString("a").drop(1) should ===(ByteString("")) - ByteString1.fromString("ab").drop(1) should ===(ByteString("b")) - ByteString1.fromString("xaaa").drop(1) should ===(ByteString("aaa")) - ByteString1.fromString("xaab").drop(1).take(2) should ===(ByteString("aa")) + ByteString1.fromString("a").drop(2) should ===(ByteString("")) + ByteString1.fromString("abc").drop(-1) should ===(ByteString("abc")) + ByteString1.fromString("abc").drop(0) should ===(ByteString("abc")) + ByteString1.fromString("abc").drop(1) should ===(ByteString("bc")) + ByteString1.fromString("abc").drop(2) should ===(ByteString("c")) + ByteString1.fromString("abc").drop(3) should ===(ByteString("")) + ByteString1.fromString("abc").drop(4) should ===(ByteString("")) + ByteString1.fromString("0123456789").drop(1).take(2) should ===(ByteString("12")) ByteString1.fromString("0123456789").drop(5).take(4).drop(1).take(2) should ===(ByteString("67")) } - "drop(n)" in { - ByteString1.fromString("ab").drop(2) should ===(ByteString("")) - ByteString1.fromString("ab").drop(3) should ===(ByteString("")) - } "take" in { ByteString1.empty.take(-1) should ===(ByteString("")) ByteString1.empty.take(0) should ===(ByteString("")) @@ -323,18 +324,22 @@ class ByteStringSpec extends WordSpec with Matchers with Checkers { } } "ByteString1C" must { - "drop(0)" in { - ByteString1C.fromString("").drop(0) should ===(ByteString.empty) - ByteString1C.fromString("a").drop(0) should ===(ByteString("a")) - } - "drop(1)" in { + "drop" in { + ByteString1C.fromString("").drop(-1) should ===(ByteString("")) + ByteString1C.fromString("").drop(0) should ===(ByteString("")) ByteString1C.fromString("").drop(1) should ===(ByteString("")) + ByteString1C.fromString("a").drop(-1) should ===(ByteString("a")) + ByteString1C.fromString("a").drop(0) should ===(ByteString("a")) ByteString1C.fromString("a").drop(1) should ===(ByteString("")) - ByteString1C.fromString("ab").drop(1) should ===(ByteString("b")) - } - "drop(n)" in { - ByteString1C.fromString("ab").drop(2) should ===(ByteString("")) - ByteString1C.fromString("ab").drop(3) should ===(ByteString("")) + ByteString1C.fromString("a").drop(2) should ===(ByteString("")) + ByteString1C.fromString("abc").drop(-1) should ===(ByteString("abc")) + ByteString1C.fromString("abc").drop(0) should ===(ByteString("abc")) + ByteString1C.fromString("abc").drop(1) should ===(ByteString("bc")) + ByteString1C.fromString("abc").drop(2) should ===(ByteString("c")) + ByteString1C.fromString("abc").drop(3) should ===(ByteString("")) + ByteString1C.fromString("abc").drop(4) should ===(ByteString("")) + ByteString1C.fromString("0123456789").drop(1).take(2) should ===(ByteString("12")) + ByteString1C.fromString("0123456789").drop(5).take(4).drop(1).take(2) should ===(ByteString("67")) } "take" in { ByteString1.fromString("abcdefg").drop(1).take(0) should ===(ByteString("")) @@ -345,33 +350,47 @@ class ByteStringSpec extends WordSpec with Matchers with Checkers { } } "ByteStrings" must { - "drop(0)" in { - ByteStrings(ByteString1.fromString(""), ByteString1.fromString("")).drop(0) should ===(ByteString.empty) - ByteStrings(ByteString1.fromString(""), ByteString1.fromString("a")).drop(0) should ===(ByteString("a")) - (ByteString1C.fromString("") ++ ByteString1.fromString("a")).drop(0) should ===(ByteString("a")) - ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).drop(0) should ===(ByteString("a")) - ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("a")).drop(0) should ===(ByteString("aa")) + "drop" in { + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("")).drop(Int.MinValue) should ===(ByteString("")) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("")).drop(-1) should ===(ByteString("")) ByteStrings(ByteString1.fromString(""), ByteString1.fromString("")).drop(0) should ===(ByteString("")) - } - "drop(1)" in { ByteStrings(ByteString1.fromString(""), ByteString1.fromString("")).drop(1) should ===(ByteString("")) - ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).drop(1) should ===(ByteString("")) - ByteStrings(ByteString1.fromString(""), ByteString1.fromString("a")).drop(1) should ===(ByteString("")) - ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).drop(1) should ===(ByteString("bcd")) - ByteStrings(Vector(ByteString1.fromString("xaaa"))).drop(1) should ===(ByteString("aaa")) - } - "drop(n)" in { - ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).drop(1) should ===(ByteString("")) - ByteStrings(ByteString1.fromString(""), ByteString1.fromString("a")).drop(1) should ===(ByteString("")) - ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).drop(3) should ===(ByteString("d")) - ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).drop(4) should ===(ByteString("")) - ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).drop(5) should ===(ByteString("")) - ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).drop(10) should ===(ByteString("")) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("")).drop(Int.MaxValue) should ===(ByteString("")) - ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).drop(-2) should ===(ByteString("abcd")) - ByteStrings(ByteString1.fromString(""), ByteString1.fromString("")).drop(-2) should ===(ByteString("")) - ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("")).drop(Int.MinValue) should ===(ByteString("ab")) - ByteStrings(ByteString1.fromString(""), ByteString1.fromString("ab")).dropRight(Int.MinValue) should ===(ByteString("ab")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).drop(Int.MinValue) should ===(ByteString("a")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).drop(-1) should ===(ByteString("a")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).drop(0) should ===(ByteString("a")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).drop(1) should ===(ByteString("")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).drop(2) should ===(ByteString("")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).drop(Int.MaxValue) should ===(ByteString("")) + + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("a")).drop(Int.MinValue) should ===(ByteString("a")) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("a")).drop(-1) should ===(ByteString("a")) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("a")).drop(0) should ===(ByteString("a")) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("a")).drop(1) should ===(ByteString("")) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("a")).drop(2) should ===(ByteString("")) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("a")).drop(Int.MaxValue) should ===(ByteString("")) + + val bss = ByteStrings(Vector( + ByteString1.fromString("a"), + ByteString1.fromString("bc"), + ByteString1.fromString("def"))) + + bss.drop(Int.MinValue) should ===(ByteString("abcdef")) + bss.drop(-1) should ===(ByteString("abcdef")) + bss.drop(0) should ===(ByteString("abcdef")) + bss.drop(1) should ===(ByteString("bcdef")) + bss.drop(2) should ===(ByteString("cdef")) + bss.drop(3) should ===(ByteString("def")) + bss.drop(4) should ===(ByteString("ef")) + bss.drop(5) should ===(ByteString("f")) + bss.drop(6) should ===(ByteString("")) + bss.drop(7) should ===(ByteString("")) + bss.drop(Int.MaxValue) should ===(ByteString("")) + + ByteString("0123456789").drop(5).take(2) should ===(ByteString("56")) + ByteString("0123456789").drop(5).drop(3).take(1) should ===(ByteString("8")) + (ByteString1C.fromString("a") ++ ByteString1.fromString("bc")).drop(2) should ===(ByteString("c")) } "slice" in { ByteStrings(ByteString1.fromString(""), ByteString1.fromString("a")).slice(1, 1) should ===(ByteString("")) diff --git a/akka-actor/src/main/scala/akka/util/ByteString.scala b/akka-actor/src/main/scala/akka/util/ByteString.scala index 6588a9917c..aceb6f298e 100644 --- a/akka-actor/src/main/scala/akka/util/ByteString.scala +++ b/akka-actor/src/main/scala/akka/util/ByteString.scala @@ -485,32 +485,24 @@ object ByteString { else drop0(n) private def drop0(n: Int): ByteString = { - var continue = true - var fullDrops = 0 - var remainingToDrop = n - do { - // impl note: could be optimised a bit by using VectorIterator instead, - // however then we're forced to call .toVector which halfs performance - // We can work around that, as there's a Scala private method "remainingVector" which is fast, - // but let's not go into calling private APIs here just yet. - val currentLength = bytestrings(fullDrops).length - if (remainingToDrop >= currentLength) { - fullDrops += 1 - remainingToDrop -= currentLength - } else continue = false - } while (remainingToDrop > 0 && continue) + // impl note: could be optimised a bit by using VectorIterator instead, + // however then we're forced to call .toVector which halfs performance + // We can work around that, as there's a Scala private method "remainingVector" which is fast, + // but let's not go into calling private APIs here just yet. + @tailrec def findSplit(fullDrops: Int, remainingToDrop: Int): (Int, Int) = { + val bs = bytestrings(fullDrops) + if (bs.length > remainingToDrop) (fullDrops, remainingToDrop) + else findSplit(fullDrops + 1, remainingToDrop - bs.length) + } - val remainingByteStrings = bytestrings.drop(fullDrops) - if (remainingByteStrings.isEmpty) ByteString.empty - else if (remainingToDrop > 0) { - val h: ByteString1 = remainingByteStrings.head.drop1(remainingToDrop) - val bs = remainingByteStrings.tail + val (fullDrops, remainingToDrop) = findSplit(0, n) - if (h.isEmpty) - if (bs.isEmpty) ByteString.empty - else new ByteStrings(bs, length - n) - else new ByteStrings(h +: bs, length - n) - } else ByteStrings(remainingByteStrings, length - n) + if (remainingToDrop == 0) + new ByteStrings(bytestrings.drop(fullDrops), length - n) + else if (fullDrops == bytestrings.length - 1) + bytestrings(fullDrops).drop(remainingToDrop) + else + new ByteStrings(bytestrings(fullDrops).drop1(remainingToDrop) +: bytestrings.drop(fullDrops + 1), length - n) } protected def writeReplace(): AnyRef = new SerializationProxy(this) diff --git a/akka-bench-jmh/src/main/scala/akka/util/ByteString_drop_Benchmark.scala b/akka-bench-jmh/src/main/scala/akka/util/ByteString_drop_Benchmark.scala new file mode 100644 index 0000000000..3c4b0563f5 --- /dev/null +++ b/akka-bench-jmh/src/main/scala/akka/util/ByteString_drop_Benchmark.scala @@ -0,0 +1,69 @@ +/** + * Copyright (C) 2014-2016 Lightbend Inc. + */ +package akka.util + +import java.util.concurrent.TimeUnit + +import akka.util.ByteString.{ ByteString1, ByteStrings } +import org.openjdk.jmh.annotations._ + +import scala.util.Random + +@State(Scope.Benchmark) +@Measurement(timeUnit = TimeUnit.MILLISECONDS) +class ByteString_drop_Benchmark { + + val str = List.fill[Byte](4)(0).mkString + val numVec = 1024 + val bss = ByteStrings(Vector.fill(numVec)(ByteString1.fromString(str))) + + val rand = new Random() + val len = str.size * numVec + val n_greater_or_eq_to_len = len + rand.nextInt(Int.MaxValue - len) + val n_neg = rand.nextInt(Int.MaxValue) * -1 + val n_avg = len / 2 + val n_best = 1 + val n_worst = len - 1 + + /* + --------------------------------- BASELINE ------------------------------------------------------------------ + [info] Benchmark Mode Cnt Score Error Units + [info] ByteString_drop_Benchmark.bss_avg thrpt 40 544841.222 ± 12917.565 ops/s + [info] ByteString_drop_Benchmark.bss_best thrpt 40 10141204.609 ± 415441.925 ops/s + [info] ByteString_drop_Benchmark.bss_greater_or_eq_to_len thrpt 40 902173327.723 ± 9921650.983 ops/s + [info] ByteString_drop_Benchmark.bss_negative thrpt 40 1179430602.793 ± 12193702.247 ops/s + [info] ByteString_drop_Benchmark.bss_worst thrpt 40 297489.038 ± 5534.801 ops/s + + */ + + @Benchmark + def bss_negative(): Unit = { + @volatile var m: ByteString = null + m = bss.drop(n_neg) + } + + @Benchmark + def bss_greater_or_eq_to_len(): Unit = { + @volatile var m: ByteString = null + m = bss.drop(n_greater_or_eq_to_len) + } + + @Benchmark + def bss_avg(): Unit = { + @volatile var m: ByteString = null + m = bss.drop(n_avg) + } + + @Benchmark + def bss_best(): Unit = { + @volatile var m: ByteString = null + m = bss.drop(n_best) + } + + @Benchmark + def bss_worst(): Unit = { + @volatile var m: ByteString = null + m = bss.drop(n_worst) + } +}