=act clean up ByteString#drop(...) (#21440)

* =act clean up ByteString#drop(...)

Current implementation has a good algorithm but seems a little bit complicated.

Clening-up does not suffer the performance (actually seems to have the better
performance when dropping(N-1)) where N is the length, and is easy to understand
almost the same algorithm now.

* Change private[akka] to priavte

* Rename go(...) and some variables

They should be easy for us to understand what they are.

* Add benchmark of ByteString#drop(...)
This commit is contained in:
monkey-mas 2016-10-05 23:25:34 +09:00 committed by Konrad Malawski
parent cc845c84b0
commit 19dbe9a487
3 changed files with 150 additions and 70 deletions

View file

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

View file

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

View file

@ -0,0 +1,69 @@
/**
* Copyright (C) 2014-2016 Lightbend Inc. <http://www.lightbend.com>
*/
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)
}
}