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 c8df14eeb7..276b27b1c0 100644 --- a/akka-actor-tests/src/test/scala/akka/util/ByteStringSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/util/ByteStringSpec.scala @@ -309,6 +309,23 @@ class ByteStringSpec extends WordSpec with Matchers with Checkers { ByteString1.fromString("0123456789").drop(1).take(2) should ===(ByteString("12")) ByteString1.fromString("0123456789").drop(5).take(4).drop(1).take(2) should ===(ByteString("67")) } + "dropRight" in { + ByteString1.empty.dropRight(-1) should ===(ByteString("")) + ByteString1.empty.dropRight(0) should ===(ByteString("")) + ByteString1.empty.dropRight(1) should ===(ByteString("")) + ByteString1.fromString("a").dropRight(-1) should ===(ByteString("a")) + ByteString1.fromString("a").dropRight(0) should ===(ByteString("a")) + ByteString1.fromString("a").dropRight(1) should ===(ByteString("")) + ByteString1.fromString("a").dropRight(2) should ===(ByteString("")) + ByteString1.fromString("abc").dropRight(-1) should ===(ByteString("abc")) + ByteString1.fromString("abc").dropRight(0) should ===(ByteString("abc")) + ByteString1.fromString("abc").dropRight(1) should ===(ByteString("ab")) + ByteString1.fromString("abc").dropRight(2) should ===(ByteString("a")) + ByteString1.fromString("abc").dropRight(3) should ===(ByteString("")) + ByteString1.fromString("abc").dropRight(4) should ===(ByteString("")) + ByteString1.fromString("0123456789").dropRight(1).take(2) should ===(ByteString("01")) + ByteString1.fromString("0123456789").dropRight(5).take(4).drop(1).take(2) should ===(ByteString("12")) + } "take" in { ByteString1.empty.take(-1) should ===(ByteString("")) ByteString1.empty.take(0) should ===(ByteString("")) @@ -341,6 +358,23 @@ class ByteStringSpec extends WordSpec with Matchers with Checkers { ByteString1C.fromString("0123456789").drop(1).take(2) should ===(ByteString("12")) ByteString1C.fromString("0123456789").drop(5).take(4).drop(1).take(2) should ===(ByteString("67")) } + "dropRight" in { + ByteString1C.fromString("").dropRight(-1) should ===(ByteString("")) + ByteString1C.fromString("").dropRight(0) should ===(ByteString("")) + ByteString1C.fromString("").dropRight(1) should ===(ByteString("")) + ByteString1C.fromString("a").dropRight(-1) should ===(ByteString("a")) + ByteString1C.fromString("a").dropRight(0) should ===(ByteString("a")) + ByteString1C.fromString("a").dropRight(1) should ===(ByteString("")) + ByteString1C.fromString("a").dropRight(2) should ===(ByteString("")) + ByteString1C.fromString("abc").dropRight(-1) should ===(ByteString("abc")) + ByteString1C.fromString("abc").dropRight(0) should ===(ByteString("abc")) + ByteString1C.fromString("abc").dropRight(1) should ===(ByteString("ab")) + ByteString1C.fromString("abc").dropRight(2) should ===(ByteString("a")) + ByteString1C.fromString("abc").dropRight(3) should ===(ByteString("")) + ByteString1C.fromString("abc").dropRight(4) should ===(ByteString("")) + ByteString1C.fromString("0123456789").dropRight(1).take(2) should ===(ByteString("01")) + ByteString1C.fromString("0123456789").dropRight(5).take(4).drop(1).take(2) should ===(ByteString("12")) + } "take" in { ByteString1.fromString("abcdefg").drop(1).take(0) should ===(ByteString("")) ByteString1.fromString("abcdefg").drop(1).take(-1) should ===(ByteString("")) @@ -392,6 +426,48 @@ class ByteStringSpec extends WordSpec with Matchers with Checkers { ByteString("0123456789").drop(5).drop(3).take(1) should ===(ByteString("8")) (ByteString1C.fromString("a") ++ ByteString1.fromString("bc")).drop(2) should ===(ByteString("c")) } + "dropRight" in { + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("")).dropRight(Int.MinValue) should ===(ByteString("")) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("")).dropRight(-1) should ===(ByteString("")) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("")).dropRight(0) should ===(ByteString("")) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("")).dropRight(1) should ===(ByteString("")) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("")).dropRight(Int.MaxValue) should ===(ByteString("")) + + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).dropRight(Int.MinValue) should ===(ByteString("a")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).dropRight(-1) should ===(ByteString("a")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).dropRight(0) should ===(ByteString("a")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).dropRight(1) should ===(ByteString("")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).dropRight(2) should ===(ByteString("")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).dropRight(Int.MaxValue) should ===(ByteString("")) + + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("a")).dropRight(Int.MinValue) should ===(ByteString("a")) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("a")).dropRight(-1) should ===(ByteString("a")) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("a")).dropRight(0) should ===(ByteString("a")) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("a")).dropRight(1) should ===(ByteString("")) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("a")).dropRight(2) should ===(ByteString("")) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("a")).dropRight(Int.MaxValue) should ===(ByteString("")) + + val bss = ByteStrings(Vector( + ByteString1.fromString("a"), + ByteString1.fromString("bc"), + ByteString1.fromString("def"))) + + bss.dropRight(Int.MinValue) should ===(ByteString("abcdef")) + bss.dropRight(-1) should ===(ByteString("abcdef")) + bss.dropRight(0) should ===(ByteString("abcdef")) + bss.dropRight(1) should ===(ByteString("abcde")) + bss.dropRight(2) should ===(ByteString("abcd")) + bss.dropRight(3) should ===(ByteString("abc")) + bss.dropRight(4) should ===(ByteString("ab")) + bss.dropRight(5) should ===(ByteString("a")) + bss.dropRight(6) should ===(ByteString("")) + bss.dropRight(7) should ===(ByteString("")) + bss.dropRight(Int.MaxValue) should ===(ByteString("")) + + ByteString("0123456789").dropRight(5).take(2) should ===(ByteString("01")) + ByteString("0123456789").dropRight(5).drop(3).take(1) should ===(ByteString("3")) + (ByteString1C.fromString("a") ++ ByteString1.fromString("bc")).dropRight(2) should ===(ByteString("a")) + } "slice" in { ByteStrings(ByteString1.fromString(""), ByteString1.fromString("a")).slice(1, 1) should ===(ByteString("")) // We explicitly test all edge cases to always test them, refs bug #21237 @@ -436,16 +512,6 @@ class ByteStringSpec extends WordSpec with Matchers with Checkers { // Get an empty if `from` is greater than `until` ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).slice(4, 0) should ===(ByteString("")) } - "dropRight" in { - ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).dropRight(0) should ===(ByteString("a")) - ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).dropRight(-1) should ===(ByteString("a")) - ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).dropRight(Int.MinValue) should ===(ByteString("a")) - ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).dropRight(1) should ===(ByteString("")) - ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).dropRight(Int.MaxValue) should ===(ByteString("")) - ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).dropRight(1) should ===(ByteString("ab")) - ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).dropRight(2) should ===(ByteString("a")) - ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).dropRight(3) should ===(ByteString("")) - } "take" in { ByteString.empty.take(-1) should ===(ByteString("")) ByteString.empty.take(0) 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 508466214e..f9ee14dd80 100644 --- a/akka-actor/src/main/scala/akka/util/ByteString.scala +++ b/akka-actor/src/main/scala/akka/util/ByteString.scala @@ -487,21 +487,29 @@ object ByteString { } override def dropRight(n: Int): ByteString = - if (n <= 0) this - else { - val last = bytestrings.last - if (n < last.length) new ByteStrings(bytestrings.init :+ last.dropRight1(n), length - n) - else { - val remaining = bytestrings.init - if (remaining.isEmpty) ByteString.empty - else { - val s = new ByteStrings(remaining, length - last.length) - val remainingToBeDropped = n - last.length - s.dropRight(remainingToBeDropped) - } + if (0 < n && n < length) dropRight0(n) + else if (n >= length) ByteString.empty + else this + + private def dropRight0(n: Int): ByteString = { + val byteStringsSize = bytestrings.length + @tailrec def dropRightWithFullDropsAndRemainig(fullDrops: Int, remainingToDrop: Int): ByteString = { + val bs = bytestrings(byteStringsSize - fullDrops - 1) + if (bs.length > remainingToDrop) { + if (fullDrops == byteStringsSize - 1) + bytestrings(0).dropRight(remainingToDrop) + else if (remainingToDrop == 0) + new ByteStrings(bytestrings.dropRight(fullDrops), length - n) + else + new ByteStrings(bytestrings.dropRight(fullDrops + 1) :+ bytestrings(byteStringsSize - fullDrops - 1).dropRight1(remainingToDrop), length - n) + } else { + dropRightWithFullDropsAndRemainig(fullDrops + 1, remainingToDrop - bs.length) } } + dropRightWithFullDropsAndRemainig(0, n) + } + override def slice(from: Int, until: Int): ByteString = if (from <= 0 && until >= length) this else if (from > length || until <= from) ByteString.empty diff --git a/akka-bench-jmh/src/main/scala/akka/util/ByteString_dropRight_Benchmark.scala b/akka-bench-jmh/src/main/scala/akka/util/ByteString_dropRight_Benchmark.scala new file mode 100644 index 0000000000..352f81bdac --- /dev/null +++ b/akka-bench-jmh/src/main/scala/akka/util/ByteString_dropRight_Benchmark.scala @@ -0,0 +1,70 @@ +/** + * 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_dropRight_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 ----------------------------------------------------------------------- + commit 0f2da7b26b5c4af35be87d2bd4a1a2392365df15 + [info] Benchmark Mode Cnt Score Error Units + [info] ByteString_dropRight_Benchmark.bss_avg thrpt 40 25626.311 ± 1395.662 ops/s + [info] ByteString_dropRight_Benchmark.bss_best thrpt 40 8667558.031 ± 200233.008 ops/s + [info] ByteString_dropRight_Benchmark.bss_greater_or_eq_to_len thrpt 40 12658.684 ± 376.730 ops/s + [info] ByteString_dropRight_Benchmark.bss_negative thrpt 40 1214680926.895 ± 10661843.507 ops/s + [info] ByteString_dropRight_Benchmark.bss_worst thrpt 40 13087.245 ± 246.911 ops/s + + --------------------------------- AFTER -------------------------------------------------------------------------- + + ------ TODAY ––––––– + [info] Benchmark Mode Cnt Score Error Units + [info] ByteString_dropRight_Benchmark.bss_avg thrpt 40 528969.025 ± 6039.001 ops/s + [info] ByteString_dropRight_Benchmark.bss_best thrpt 40 7925951.396 ± 249279.950 ops/s + [info] ByteString_dropRight_Benchmark.bss_greater_or_eq_to_len thrpt 40 893475724.604 ± 9836471.105 ops/s + [info] ByteString_dropRight_Benchmark.bss_negative thrpt 40 1182275022.613 ± 9710755.955 ops/s + [info] ByteString_dropRight_Benchmark.bss_worst thrpt 40 244599.957 ± 3276.140 ops/s + + */ + + @Benchmark + def bss_negative(): ByteString = + bss.dropRight(n_neg) + + @Benchmark + def bss_greater_or_eq_to_len(): ByteString = + bss.dropRight(n_greater_or_eq_to_len) + + @Benchmark + def bss_avg(): ByteString = + bss.dropRight(n_avg) + + @Benchmark + def bss_best(): ByteString = + bss.dropRight(n_best) + + @Benchmark + def bss_worst(): ByteString = + bss.dropRight(n_worst) +}