ByteString optimisations of methods in HTTP parsing hot-path (#20994)

* =act #20992 prepare benchmarks for ByteString optimisations

* =act #20992 optimise common ByteString operations: drop,take,slice...

* =act,htc #15965 add ByteString.decodeString(java.nio.charsets.Charset)
This commit is contained in:
Konrad Malawski 2016-07-20 14:01:51 +02:00 committed by GitHub
parent d3ea9e49db
commit fde9d86879
12 changed files with 552 additions and 60 deletions

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
*# *#
*.jfr
*.iml *.iml
*.ipr *.ipr
*.iws *.iws

View file

@ -10,6 +10,7 @@ import java.lang.Float.floatToRawIntBits
import java.nio.{ ByteBuffer, ByteOrder } import java.nio.{ ByteBuffer, ByteOrder }
import java.nio.ByteOrder.{ BIG_ENDIAN, LITTLE_ENDIAN } import java.nio.ByteOrder.{ BIG_ENDIAN, LITTLE_ENDIAN }
import akka.util.ByteString.{ ByteString1, ByteString1C, ByteStrings }
import org.apache.commons.codec.binary.Hex.encodeHex import org.apache.commons.codec.binary.Hex.encodeHex
import org.scalacheck.Arbitrary.arbitrary import org.scalacheck.Arbitrary.arbitrary
import org.scalacheck.{ Arbitrary, Gen } import org.scalacheck.{ Arbitrary, Gen }
@ -20,6 +21,12 @@ import scala.collection.mutable.Builder
class ByteStringSpec extends WordSpec with Matchers with Checkers { class ByteStringSpec extends WordSpec with Matchers with Checkers {
// // uncomment when developing locally to get better coverage
// implicit override val generatorDrivenConfig =
// PropertyCheckConfig(
// minSuccessful = 1000,
// minSize = 0, maxSize = 100)
def genSimpleByteString(min: Int, max: Int) = for { def genSimpleByteString(min: Int, max: Int) = for {
n Gen.choose(min, max) n Gen.choose(min, max)
b Gen.containerOfN[Array, Byte](n, arbitrary[Byte]) b Gen.containerOfN[Array, Byte](n, arbitrary[Byte])
@ -281,10 +288,113 @@ class ByteStringSpec extends WordSpec with Matchers with Checkers {
reference.toSeq == builder.result reference.toSeq == builder.result
} }
"ByteString1" must {
"drop(0)" in {
ByteString1.fromString("").drop(0) should ===(ByteString.empty)
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("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(""))
}
}
"ByteString1C" must {
"drop(0)" in {
ByteString1C.fromString("").drop(0) should ===(ByteString.empty)
ByteString1C.fromString("a").drop(0) should ===(ByteString("a"))
}
"drop(1)" in {
ByteString1C.fromString("").drop(1) should ===(ByteString(""))
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(""))
}
"take" in {
ByteString1.fromString("abcdefg").drop(1).take(0) should ===(ByteString(""))
ByteString1.fromString("abcdefg").drop(1).take(-1) should ===(ByteString(""))
ByteString1.fromString("abcdefg").drop(1).take(-2) should ===(ByteString(""))
ByteString1.fromString("abcdefg").drop(2) should ===(ByteString("cdefg"))
ByteString1.fromString("abcdefg").drop(2).take(1) should ===(ByteString("c"))
}
}
"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"))
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("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"))
}
"slice" in {
ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).slice(0, 1) should ===(ByteString("a"))
ByteStrings(ByteString1.fromString(""), ByteString1.fromString("a")).slice(1, 1) should ===(ByteString(""))
ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).slice(2, 2) should ===(ByteString(""))
ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).slice(2, 3) should ===(ByteString("c"))
ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).slice(2, 4) should ===(ByteString("cd"))
ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).slice(3, 4) should ===(ByteString("d"))
ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).slice(10, 100) 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 {
ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).drop(1).take(0) should ===(ByteString(""))
ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).drop(1).take(-1) should ===(ByteString(""))
ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).drop(1).take(-2) should ===(ByteString(""))
(ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")) ++ ByteString1.fromString("defg")).drop(2) should ===(ByteString("cdefg"))
ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).drop(2).take(1) should ===(ByteString("c"))
ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).take(100) should ===(ByteString("abc"))
ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).drop(1).take(100) should ===(ByteString("bc"))
}
}
"A ByteString" must { "A ByteString" must {
"have correct size" when { "have correct size" when {
"concatenating" in { check((a: ByteString, b: ByteString) (a ++ b).size == a.size + b.size) } "concatenating" in { check((a: ByteString, b: ByteString) (a ++ b).size == a.size + b.size) }
"dropping" in { check((a: ByteString, b: ByteString) (a ++ b).drop(b.size).size == a.size) } "dropping" in { check((a: ByteString, b: ByteString) (a ++ b).drop(b.size).size == a.size) }
"taking" in { check((a: ByteString, b: ByteString) (a ++ b).take(a.size) == a) }
"takingRight" in { check((a: ByteString, b: ByteString) (a ++ b).takeRight(b.size) == b) }
"droppnig then taking" in { check((a: ByteString, b: ByteString) (b ++ a ++ b).drop(b.size).take(a.size) == a) }
"droppingRight" in { check((a: ByteString, b: ByteString) (b ++ a ++ b).drop(b.size).dropRight(b.size) == a) }
} }
"be sequential" when { "be sequential" when {
@ -301,6 +411,21 @@ class ByteStringSpec extends WordSpec with Matchers with Checkers {
(a ++ b ++ c) == xs (a ++ b ++ c) == xs
} }
} }
def excerciseRecombining(xs: ByteString, from: Int, until: Int) = {
val (tmp, c) = xs.splitAt(until)
val (a, b) = tmp.splitAt(from)
(a ++ b ++ c) should ===(xs)
}
"recombining - edge cases" in {
excerciseRecombining(ByteStrings(Vector(ByteString1(Array[Byte](1)), ByteString1(Array[Byte](2)))), -2147483648, 112121212)
excerciseRecombining(ByteStrings(Vector(ByteString1(Array[Byte](100)))), 0, 2)
excerciseRecombining(ByteStrings(Vector(ByteString1(Array[Byte](100)))), -2147483648, 2)
excerciseRecombining(ByteStrings(Vector(ByteString1.fromString("ab"), ByteString1.fromString("cd"))), 0, 1)
excerciseRecombining(ByteString1.fromString("abc").drop(1).take(1), -324234, 234232)
excerciseRecombining(ByteString("a"), 0, 2147483647)
excerciseRecombining(ByteStrings(Vector(ByteString1.fromString("ab"), ByteString1.fromString("cd"))).drop(2), 2147483647, 1)
excerciseRecombining(ByteString1.fromString("ab").drop1(1), Int.MaxValue, Int.MaxValue)
}
} }
"behave as expected" when { "behave as expected" when {

View file

@ -234,6 +234,7 @@ object ByteIterator {
new MultiByteArrayIterator(clonedIterators) new MultiByteArrayIterator(clonedIterators)
} }
/** For performance sensitive code, call take() directly on ByteString (it's optimised there) */
final override def take(n: Int): this.type = { final override def take(n: Int): this.type = {
var rest = n var rest = n
val builder = new ListBuffer[ByteArrayIterator] val builder = new ListBuffer[ByteArrayIterator]
@ -249,7 +250,8 @@ object ByteIterator {
normalize() normalize()
} }
@tailrec final override def drop(n: Int): this.type = /** For performance sensitive code, call drop() directly on ByteString (it's optimised there) */
final override def drop(n: Int): this.type =
if ((n > 0) && !isEmpty) { if ((n > 0) && !isEmpty) {
val nCurrent = math.min(n, current.len) val nCurrent = math.min(n, current.len)
current.drop(n) current.drop(n)
@ -341,6 +343,7 @@ object ByteIterator {
def getDoubles(xs: Array[Double], offset: Int, n: Int)(implicit byteOrder: ByteOrder): this.type = def getDoubles(xs: Array[Double], offset: Int, n: Int)(implicit byteOrder: ByteOrder): this.type =
getToArray(xs, offset, n, 8) { getDouble(byteOrder) } { current.getDoubles(_, _, _)(byteOrder) } getToArray(xs, offset, n, 8) { getDouble(byteOrder) } { current.getDoubles(_, _, _)(byteOrder) }
/** For performance sensitive code, call copyToBuffer() directly on ByteString (it's optimised there) */
override def copyToBuffer(buffer: ByteBuffer): Int = { override def copyToBuffer(buffer: ByteBuffer): Int = {
// the fold here is better than indexing into the LinearSeq // the fold here is better than indexing into the LinearSeq
val n = iterators.foldLeft(0) { _ + _.copyToBuffer(buffer) } val n = iterators.foldLeft(0) { _ + _.copyToBuffer(buffer) }
@ -636,6 +639,7 @@ abstract class ByteIterator extends BufferedIterator[Byte] {
* @param buffer a ByteBuffer to copy bytes to * @param buffer a ByteBuffer to copy bytes to
* @return the number of bytes actually copied * @return the number of bytes actually copied
*/ */
/** For performance sensitive code, call take() directly on ByteString (it's optimised there) */
def copyToBuffer(buffer: ByteBuffer): Int def copyToBuffer(buffer: ByteBuffer): Int
/** /**

View file

@ -12,10 +12,10 @@ import scala.annotation.{ tailrec, varargs }
import scala.collection.IndexedSeqOptimized import scala.collection.IndexedSeqOptimized
import scala.collection.mutable.{ Builder, WrappedArray } import scala.collection.mutable.{ Builder, WrappedArray }
import scala.collection.immutable import scala.collection.immutable
import scala.collection.immutable.{ IndexedSeq, VectorBuilder } import scala.collection.immutable.{ IndexedSeq, VectorBuilder, VectorIterator }
import scala.collection.generic.CanBuildFrom import scala.collection.generic.CanBuildFrom
import scala.reflect.ClassTag import scala.reflect.ClassTag
import java.nio.charset.StandardCharsets import java.nio.charset.{ Charset, StandardCharsets }
object ByteString { object ByteString {
@ -104,6 +104,7 @@ object ByteString {
} }
private[akka] object ByteString1C extends Companion { private[akka] object ByteString1C extends Companion {
def fromString(s: String): ByteString1C = new ByteString1C(s.getBytes)
def apply(bytes: Array[Byte]): ByteString1C = new ByteString1C(bytes) def apply(bytes: Array[Byte]): ByteString1C = new ByteString1C(bytes)
val SerializationIdentity = 1.toByte val SerializationIdentity = 1.toByte
@ -124,29 +125,49 @@ object ByteString {
override def length: Int = bytes.length override def length: Int = bytes.length
// Avoid `iterator` in performance sensitive code, call ops directly on ByteString instead
override def iterator: ByteIterator.ByteArrayIterator = ByteIterator.ByteArrayIterator(bytes, 0, bytes.length) override def iterator: ByteIterator.ByteArrayIterator = ByteIterator.ByteArrayIterator(bytes, 0, bytes.length)
private[akka] def toByteString1: ByteString1 = ByteString1(bytes) /** INTERNAL API */
private[akka] def toByteString1: ByteString1 = ByteString1(bytes, 0, bytes.length)
/** INTERNAL API */
private[akka] def byteStringCompanion = ByteString1C private[akka] def byteStringCompanion = ByteString1C
def asByteBuffer: ByteBuffer = toByteString1.asByteBuffer override def asByteBuffer: ByteBuffer = toByteString1.asByteBuffer
def asByteBuffers: scala.collection.immutable.Iterable[ByteBuffer] = List(asByteBuffer) override def asByteBuffers: scala.collection.immutable.Iterable[ByteBuffer] = List(asByteBuffer)
def decodeString(charset: String): String = override def decodeString(charset: String): String =
if (isEmpty) "" else new String(bytes, charset) if (isEmpty) "" else new String(bytes, charset)
def ++(that: ByteString): ByteString = override def decodeString(charset: Charset): String =
if (isEmpty) "" else new String(bytes, charset)
override def ++(that: ByteString): ByteString = {
if (that.isEmpty) this if (that.isEmpty) this
else if (this.isEmpty) that else if (this.isEmpty) that
else toByteString1 ++ that else toByteString1 ++ that
}
override def take(n: Int): ByteString =
if (n <= 0) ByteString.empty
else toByteString1.take(n)
override def dropRight(n: Int): ByteString =
if (n <= 0) this
else toByteString1.dropRight(n)
override def drop(n: Int): ByteString =
if (n <= 0) this
else toByteString1.drop(n)
override def slice(from: Int, until: Int): ByteString = override def slice(from: Int, until: Int): ByteString =
if ((from != 0) || (until != length)) toByteString1.slice(from, until) if ((from == 0) && (until == length)) this
else this else if (from > length) ByteString.empty
else toByteString1.slice(from, until)
private[akka] def writeToOutputStream(os: ObjectOutputStream): Unit = private[akka] override def writeToOutputStream(os: ObjectOutputStream): Unit =
toByteString1.writeToOutputStream(os) toByteString1.writeToOutputStream(os)
override def copyToBuffer(buffer: ByteBuffer): Int = override def copyToBuffer(buffer: ByteBuffer): Int =
@ -154,7 +175,7 @@ object ByteString {
/** INTERNAL API: Specialized for internal use, writing multiple ByteString1C into the same ByteBuffer. */ /** INTERNAL API: Specialized for internal use, writing multiple ByteString1C into the same ByteBuffer. */
private[akka] def writeToBuffer(buffer: ByteBuffer, offset: Int): Int = { private[akka] def writeToBuffer(buffer: ByteBuffer, offset: Int): Int = {
val copyLength = math.min(buffer.remaining, offset + length) val copyLength = Math.min(buffer.remaining, offset + length)
if (copyLength > 0) { if (copyLength > 0) {
buffer.put(bytes, offset, copyLength) buffer.put(bytes, offset, copyLength)
drop(copyLength) drop(copyLength)
@ -164,11 +185,14 @@ object ByteString {
} }
/** INTERNAL API: ByteString backed by exactly one array, with start / end markers */
private[akka] object ByteString1 extends Companion { private[akka] object ByteString1 extends Companion {
val empty: ByteString1 = new ByteString1(Array.empty[Byte]) val empty: ByteString1 = new ByteString1(Array.empty[Byte])
def apply(bytes: Array[Byte]): ByteString1 = ByteString1(bytes, 0, bytes.length) def fromString(s: String): ByteString1 = apply(s.getBytes)
def apply(bytes: Array[Byte]): ByteString1 = apply(bytes, 0, bytes.length)
def apply(bytes: Array[Byte], startIndex: Int, length: Int): ByteString1 = def apply(bytes: Array[Byte], startIndex: Int, length: Int): ByteString1 =
if (length == 0) empty else new ByteString1(bytes, startIndex, length) if (length == 0) empty
else new ByteString1(bytes, Math.max(0, startIndex), Math.max(0, length))
val SerializationIdentity = 0.toByte val SerializationIdentity = 0.toByte
@ -185,6 +209,7 @@ object ByteString {
def apply(idx: Int): Byte = bytes(checkRangeConvert(idx)) def apply(idx: Int): Byte = bytes(checkRangeConvert(idx))
// Avoid `iterator` in performance sensitive code, call ops directly on ByteString instead
override def iterator: ByteIterator.ByteArrayIterator = override def iterator: ByteIterator.ByteArrayIterator =
ByteIterator.ByteArrayIterator(bytes, startIndex, startIndex + length) ByteIterator.ByteArrayIterator(bytes, startIndex, startIndex + length)
@ -204,12 +229,41 @@ object ByteString {
private[akka] def byteStringCompanion = ByteString1 private[akka] def byteStringCompanion = ByteString1
override def dropRight(n: Int): ByteString =
dropRight1(n)
/** INTERNAL API */
private[akka] def dropRight1(n: Int): ByteString1 =
if (n <= 0) this
else if (length - n <= 0) ByteString1.empty
else new ByteString1(bytes, startIndex, length - n)
override def drop(n: Int): ByteString =
if (n <= 0) this else drop1(n)
/** INTERNAL API */
private[akka] def drop1(n: Int): ByteString1 = {
val nextStartIndex = startIndex + n
if (nextStartIndex >= bytes.length) ByteString1.empty
else ByteString1(bytes, nextStartIndex, length - n)
}
override def take(n: Int): ByteString =
if (n <= 0) ByteString.empty
else ByteString1(bytes, startIndex, Math.min(n, length))
override def slice(from: Int, until: Int): ByteString = {
if (from <= 0 && until >= length) this // we can do < / > since we're Compact
else if (until <= from) ByteString1.empty
else ByteString1(bytes, startIndex + from, until - from)
}
override def copyToBuffer(buffer: ByteBuffer): Int = override def copyToBuffer(buffer: ByteBuffer): Int =
writeToBuffer(buffer) writeToBuffer(buffer)
/** INTERNAL API: Specialized for internal use, writing multiple ByteString1C into the same ByteBuffer. */ /** INTERNAL API: Specialized for internal use, writing multiple ByteString1C into the same ByteBuffer. */
private[akka] def writeToBuffer(buffer: ByteBuffer): Int = { private[akka] def writeToBuffer(buffer: ByteBuffer): Int = {
val copyLength = math.min(buffer.remaining, length) val copyLength = Math.min(buffer.remaining, length)
if (copyLength > 0) { if (copyLength > 0) {
buffer.put(bytes, startIndex, copyLength) buffer.put(bytes, startIndex, copyLength)
drop(copyLength) drop(copyLength)
@ -228,7 +282,10 @@ object ByteString {
def asByteBuffers: scala.collection.immutable.Iterable[ByteBuffer] = List(asByteBuffer) def asByteBuffers: scala.collection.immutable.Iterable[ByteBuffer] = List(asByteBuffer)
def decodeString(charset: String): String = override def decodeString(charset: String): String =
new String(if (length == bytes.length) bytes else toArray, charset)
override def decodeString(charset: Charset): String = // avoids Charset.forName lookup in String internals
new String(if (length == bytes.length) bytes else toArray, charset) new String(if (length == bytes.length) bytes else toArray, charset)
def ++(that: ByteString): ByteString = { def ++(that: ByteString): ByteString = {
@ -311,8 +368,9 @@ object ByteString {
*/ */
final class ByteStrings private (private[akka] val bytestrings: Vector[ByteString1], val length: Int) extends ByteString with Serializable { final class ByteStrings private (private[akka] val bytestrings: Vector[ByteString1], val length: Int) extends ByteString with Serializable {
if (bytestrings.isEmpty) throw new IllegalArgumentException("bytestrings must not be empty") if (bytestrings.isEmpty) throw new IllegalArgumentException("bytestrings must not be empty")
if (bytestrings.head.isEmpty) throw new IllegalArgumentException("bytestrings.head must not be empty")
def apply(idx: Int): Byte = def apply(idx: Int): Byte = {
if (0 <= idx && idx < length) { if (0 <= idx && idx < length) {
var pos = 0 var pos = 0
var seen = 0 var seen = 0
@ -322,7 +380,9 @@ object ByteString {
} }
bytestrings(pos)(idx - seen) bytestrings(pos)(idx - seen)
} else throw new IndexOutOfBoundsException(idx.toString) } else throw new IndexOutOfBoundsException(idx.toString)
}
// Avoid `iterator` in performance sensitive code, call ops directly on ByteString instead
override def iterator: ByteIterator.MultiByteArrayIterator = override def iterator: ByteIterator.MultiByteArrayIterator =
ByteIterator.MultiByteArrayIterator(bytestrings.toStream map { _.iterator }) ByteIterator.MultiByteArrayIterator(bytestrings.toStream map { _.iterator })
@ -367,11 +427,83 @@ object ByteString {
def decodeString(charset: String): String = compact.decodeString(charset) def decodeString(charset: String): String = compact.decodeString(charset)
def decodeString(charset: Charset): String =
compact.decodeString(charset)
private[akka] def writeToOutputStream(os: ObjectOutputStream): Unit = { private[akka] def writeToOutputStream(os: ObjectOutputStream): Unit = {
os.writeInt(bytestrings.length) os.writeInt(bytestrings.length)
bytestrings.foreach(_.writeToOutputStream(os)) bytestrings.foreach(_.writeToOutputStream(os))
} }
override def take(n: Int): ByteString = {
@tailrec def take0(n: Int, b: ByteStringBuilder, bs: Vector[ByteString1]): ByteString =
if (bs.isEmpty || n <= 0) b.result
else {
val head = bs.head
if (n <= head.length) b.append(head.take(n)).result
else take0(n - head.length, b.append(head), bs.tail)
}
if (n <= 0) ByteString.empty
else if (n >= length) this
else take0(n, ByteString.newBuilder, bytestrings)
}
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)
}
}
}
override def slice(from: Int, until: Int): ByteString =
if ((from == 0) && (until == length)) this
else if (from > length || until <= from) ByteString.empty
else drop(from).dropRight(length - until)
override def drop(n: Int): ByteString =
if (n <= 0) this
else if (n > length) ByteString.empty
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)
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
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)
}
protected def writeReplace(): AnyRef = new SerializationProxy(this) protected def writeReplace(): AnyRef = new SerializationProxy(this)
} }
@ -422,6 +554,8 @@ sealed abstract class ByteString extends IndexedSeq[Byte] with IndexedSeqOptimiz
// *must* be overridden by derived classes. This construction is necessary // *must* be overridden by derived classes. This construction is necessary
// to specialize the return type, as the method is already implemented in // to specialize the return type, as the method is already implemented in
// a parent trait. // a parent trait.
//
// Avoid `iterator` in performance sensitive code, call ops directly on ByteString instead
override def iterator: ByteIterator = throw new UnsupportedOperationException("Method iterator is not implemented in ByteString") override def iterator: ByteIterator = throw new UnsupportedOperationException("Method iterator is not implemented in ByteString")
override def head: Byte = apply(0) override def head: Byte = apply(0)
@ -429,14 +563,19 @@ sealed abstract class ByteString extends IndexedSeq[Byte] with IndexedSeqOptimiz
override def last: Byte = apply(length - 1) override def last: Byte = apply(length - 1)
override def init: ByteString = dropRight(1) override def init: ByteString = dropRight(1)
override def slice(from: Int, until: Int): ByteString = // *must* be overridden by derived classes.
if ((from == 0) && (until == length)) this override def take(n: Int): ByteString = throw new UnsupportedOperationException("Method slice is not implemented in ByteString")
else iterator.slice(from, until).toByteString
override def take(n: Int): ByteString = slice(0, n)
override def takeRight(n: Int): ByteString = slice(length - n, length) override def takeRight(n: Int): ByteString = slice(length - n, length)
override def drop(n: Int): ByteString = slice(n, length)
override def dropRight(n: Int): ByteString = slice(0, length - n) // these methods are optimized in derived classes utilising the maximum knowlage about data layout available to them:
// *must* be overridden by derived classes.
override def slice(from: Int, until: Int): ByteString = throw new UnsupportedOperationException("Method slice is not implemented in ByteString")
// *must* be overridden by derived classes.
override def drop(n: Int): ByteString = throw new UnsupportedOperationException("Method drop is not implemented in ByteString")
// *must* be overridden by derived classes.
override def dropRight(n: Int): ByteString = throw new UnsupportedOperationException("Method dropRight is not implemented in ByteString")
override def takeWhile(p: Byte Boolean): ByteString = iterator.takeWhile(p).toByteString override def takeWhile(p: Byte Boolean): ByteString = iterator.takeWhile(p).toByteString
override def dropWhile(p: Byte Boolean): ByteString = iterator.dropWhile(p).toByteString override def dropWhile(p: Byte Boolean): ByteString = iterator.dropWhile(p).toByteString
@ -461,7 +600,7 @@ sealed abstract class ByteString extends IndexedSeq[Byte] with IndexedSeqOptimiz
* *
* @return this ByteString copied into a byte array * @return this ByteString copied into a byte array
*/ */
protected[ByteString] def toArray: Array[Byte] = toArray[Byte] // protected[ByteString] == public to Java but hidden to Scala * fnizz * protected[ByteString] def toArray: Array[Byte] = toArray[Byte]
override def toArray[B >: Byte](implicit arg0: ClassTag[B]): Array[B] = iterator.toArray override def toArray[B >: Byte](implicit arg0: ClassTag[B]): Array[B] = iterator.toArray
override def copyToArray[B >: Byte](xs: Array[B], start: Int, len: Int): Unit = override def copyToArray[B >: Byte](xs: Array[B], start: Int, len: Int): Unit =
@ -488,11 +627,8 @@ sealed abstract class ByteString extends IndexedSeq[Byte] with IndexedSeqOptimiz
* @param buffer a ByteBuffer to copy bytes to * @param buffer a ByteBuffer to copy bytes to
* @return the number of bytes actually copied * @return the number of bytes actually copied
*/ */
def copyToBuffer(buffer: ByteBuffer): Int = { // *must* be overridden by derived classes.
// TODO: remove this impl, make it an abstract method when possible def copyToBuffer(buffer: ByteBuffer): Int = throw new UnsupportedOperationException("Method copyToBuffer is not implemented in ByteString")
// specialized versions of this method exist in sub-classes, we keep this impl for binary compatibility, it never is actually invoked
iterator.copyToBuffer(buffer)
}
/** /**
* Create a new ByteString with all contents compacted into a single, * Create a new ByteString with all contents compacted into a single,
@ -544,9 +680,16 @@ sealed abstract class ByteString extends IndexedSeq[Byte] with IndexedSeqOptimiz
/** /**
* Decodes this ByteString using a charset to produce a String. * Decodes this ByteString using a charset to produce a String.
* If you have a [[Charset]] instance available, use `decodeString(charset: java.nio.charset.Charset` instead.
*/ */
def decodeString(charset: String): String def decodeString(charset: String): String
/**
* Decodes this ByteString using a charset to produce a String.
* Avoids Charset.forName lookup in String internals, thus is preferable to `decodeString(charset: String)`.
*/
def decodeString(charset: Charset): String
/** /**
* map method that will automatically cast Int back into Byte. * map method that will automatically cast Int back into Byte.
*/ */
@ -608,8 +751,8 @@ object CompactByteString {
* an Array. * an Array.
*/ */
def fromArray(array: Array[Byte], offset: Int, length: Int): CompactByteString = { def fromArray(array: Array[Byte], offset: Int, length: Int): CompactByteString = {
val copyOffset = math.max(offset, 0) val copyOffset = Math.max(offset, 0)
val copyLength = math.max(math.min(array.length - copyOffset, length), 0) val copyLength = Math.max(Math.min(array.length - copyOffset, length), 0)
if (copyLength == 0) empty if (copyLength == 0) empty
else { else {
val copyArray = new Array[Byte](copyLength) val copyArray = new Array[Byte](copyLength)
@ -706,6 +849,8 @@ final class ByteStringBuilder extends Builder[Byte, ByteString] {
override def ++=(xs: TraversableOnce[Byte]): this.type = { override def ++=(xs: TraversableOnce[Byte]): this.type = {
xs match { xs match {
case b: ByteString if b.isEmpty
// do nothing
case b: ByteString1C case b: ByteString1C
clearTemp() clearTemp()
_builder += b.toByteString1 _builder += b.toByteString1
@ -748,7 +893,7 @@ final class ByteStringBuilder extends Builder[Byte, ByteString] {
/** /**
* Java API: append a ByteString to this builder. * Java API: append a ByteString to this builder.
*/ */
def append(bs: ByteString): this.type = this ++= bs def append(bs: ByteString): this.type = if (bs.isEmpty) this else this ++= bs
/** /**
* Add a single Byte to this builder. * Add a single Byte to this builder.
@ -915,7 +1060,7 @@ final class ByteStringBuilder extends Builder[Byte, ByteString] {
fillByteBuffer(len * 8, byteOrder) { _.asDoubleBuffer.put(array, start, len) } fillByteBuffer(len * 8, byteOrder) { _.asDoubleBuffer.put(array, start, len) }
def clear(): Unit = { def clear(): Unit = {
_builder.clear _builder.clear()
_length = 0 _length = 0
_tempLength = 0 _tempLength = 0
} }

View file

@ -12,7 +12,7 @@ import org.openjdk.jmh.infra.Blackhole
@State(Scope.Benchmark) @State(Scope.Benchmark)
@Measurement(timeUnit = TimeUnit.MILLISECONDS) @Measurement(timeUnit = TimeUnit.MILLISECONDS)
class ByteStringBenchmark { class ByteString_copyToBuffer_Benchmark {
val _bs_mini = ByteString(Array.ofDim[Byte](128 * 4)) val _bs_mini = ByteString(Array.ofDim[Byte](128 * 4))
val _bs_small = ByteString(Array.ofDim[Byte](1024 * 1)) val _bs_small = ByteString(Array.ofDim[Byte](1024 * 1))
@ -83,16 +83,10 @@ class ByteStringBenchmark {
bss_large.copyToBuffer(buf) bss_large.copyToBuffer(buf)
} }
// /** compact + copy */
// @Benchmark
// def bss_large_c_copyToBuffer: Int =
// bss_large.compact.copyToBuffer(buf)
/** Pre-compacted */ /** Pre-compacted */
@Benchmark @Benchmark
def bss_large_pc_copyToBuffer(): Int = { def bss_large_pc_copyToBuffer(): Int = {
buf.flip() buf.flip()
bss_pc_large.copyToBuffer(buf) bss_pc_large.copyToBuffer(buf)
} }
} }

View file

@ -0,0 +1,64 @@
/**
* Copyright (C) 2014-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.util
import java.nio.charset.Charset
import java.util.concurrent.TimeUnit
import akka.util.ByteString.{ ByteString1C, ByteStrings }
import org.openjdk.jmh.annotations._
@State(Scope.Benchmark)
@Measurement(timeUnit = TimeUnit.MILLISECONDS)
class ByteString_decode_Benchmark {
val _bs_large = ByteString(Array.ofDim[Byte](1024 * 4))
val bs_large = ByteString(Array.ofDim[Byte](1024 * 4 * 4))
val bss_large = ByteStrings(Vector.fill(4)(bs_large.asInstanceOf[ByteString1C].toByteString1), 4 * bs_large.length)
val bc_large = bss_large.compact // compacted
val utf8String = "utf-8"
val utf8 = Charset.forName(utf8String)
/*
Using Charset helps a bit, but nothing impressive:
[info] ByteString_decode_Benchmark.bc_large_decodeString_stringCharset_utf8 thrpt 20 21 612.293 ± 825.099 ops/s
=>
[info] ByteString_decode_Benchmark.bc_large_decodeString_charsetCharset_utf8 thrpt 20 22 473.372 ± 851.597 ops/s
[info] ByteString_decode_Benchmark.bs_large_decodeString_stringCharset_utf8 thrpt 20 84 443.674 ± 3723.987 ops/s
=>
[info] ByteString_decode_Benchmark.bs_large_decodeString_charsetCharset_utf8 thrpt 20 93 865.033 ± 2052.476 ops/s
[info] ByteString_decode_Benchmark.bss_large_decodeString_stringCharset_utf8 thrpt 20 14 886.553 ± 326.752 ops/s
=>
[info] ByteString_decode_Benchmark.bss_large_decodeString_charsetCharset_utf8 thrpt 20 16 031.670 ± 474.565 ops/s
*/
@Benchmark
def bc_large_decodeString_stringCharset_utf8: String =
bc_large.decodeString(utf8String)
@Benchmark
def bs_large_decodeString_stringCharset_utf8: String =
bs_large.decodeString(utf8String)
@Benchmark
def bss_large_decodeString_stringCharset_utf8: String =
bss_large.decodeString(utf8String)
@Benchmark
def bc_large_decodeString_charsetCharset_utf8: String =
bc_large.decodeString(utf8)
@Benchmark
def bs_large_decodeString_charsetCharset_utf8: String =
bs_large.decodeString(utf8)
@Benchmark
def bss_large_decodeString_charsetCharset_utf8: String =
bss_large.decodeString(utf8)
}

View file

@ -0,0 +1,156 @@
/**
* Copyright (C) 2014-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.util
import java.nio.ByteBuffer
import java.util.concurrent.TimeUnit
import akka.util.ByteString.{ ByteString1C, ByteStrings }
import org.openjdk.jmh.annotations._
@State(Scope.Benchmark)
@Measurement(timeUnit = TimeUnit.MILLISECONDS)
class ByteString_dropSliceTake_Benchmark {
val _bs_mini = ByteString(Array.ofDim[Byte](128 * 4))
val _bs_small = ByteString(Array.ofDim[Byte](1024 * 1))
val _bs_large = ByteString(Array.ofDim[Byte](1024 * 4))
val bs_mini = ByteString(Array.ofDim[Byte](128 * 4 * 4))
val bs_small = ByteString(Array.ofDim[Byte](1024 * 1 * 4))
val bs_large = ByteString(Array.ofDim[Byte](1024 * 4 * 4))
val bss_mini = ByteStrings(Vector.fill(4)(bs_mini.asInstanceOf[ByteString1C].toByteString1), 4 * bs_mini.length)
val bss_small = ByteStrings(Vector.fill(4)(bs_small.asInstanceOf[ByteString1C].toByteString1), 4 * bs_small.length)
val bss_large = ByteStrings(Vector.fill(4)(bs_large.asInstanceOf[ByteString1C].toByteString1), 4 * bs_large.length)
val bss_pc_large = bss_large.compact
/*
--------------------------------- BASELINE --------------------------------------------------------------------
[info] Benchmark Mode Cnt Score Error Units
[info] ByteString_dropSliceTake_Benchmark.bs_large_dropRight_100 thrpt 20 111 122 621.983 ± 6172679.160 ops/s
[info] ByteString_dropSliceTake_Benchmark.bs_large_dropRight_256 thrpt 20 110 238 003.870 ± 4042572.908 ops/s
[info] ByteString_dropSliceTake_Benchmark.bs_large_dropRight_2000 thrpt 20 106 435 449.123 ± 2972282.531 ops/s
[info] ByteString_dropSliceTake_Benchmark.bss_large_dropRight_100 thrpt 20 1 155 292.430 ± 23096.219 ops/s
[info] ByteString_dropSliceTake_Benchmark.bss_large_dropRight_256 thrpt 20 1 191 713.229 ± 15910.426 ops/s
[info] ByteString_dropSliceTake_Benchmark.bss_large_dropRight_2000 thrpt 20 1 201 342.579 ± 21119.392 ops/s
[info] ByteString_dropSliceTake_Benchmark.bs_large_drop_100 thrpt 20 108 252 561.824 ± 3841392.346 ops/s
[info] ByteString_dropSliceTake_Benchmark.bs_large_drop_256 thrpt 20 112 515 936.237 ± 5651549.124 ops/s
[info] ByteString_dropSliceTake_Benchmark.bs_large_drop_2000 thrpt 20 110 851 553.706 ± 3327510.108 ops/s
[info] ByteString_dropSliceTake_Benchmark.bss_large_drop_18 thrpt 20 983 544.541 ± 46299.808 ops/s
[info] ByteString_dropSliceTake_Benchmark.bss_large_drop_100 thrpt 20 875 345.433 ± 44760.533 ops/s
[info] ByteString_dropSliceTake_Benchmark.bss_large_drop_256 thrpt 20 864 182.258 ± 111172.303 ops/s
[info] ByteString_dropSliceTake_Benchmark.bss_large_drop_2000 thrpt 20 997 459.151 ± 33627.993 ops/s
[info] ByteString_dropSliceTake_Benchmark.bs_large_slice_80_80 thrpt 20 112 299 538.691 ± 7259114.294 ops/s
[info] ByteString_dropSliceTake_Benchmark.bs_large_slice_129_129 thrpt 20 105 640 836.625 ± 9112709.942 ops/s
[info] ByteString_dropSliceTake_Benchmark.bss_large_slice_80_80 thrpt 20 10 868 202.262 ± 526537.133 ops/s
[info] ByteString_dropSliceTake_Benchmark.bss_large_slice_129_129 thrpt 20 9 429 199.802 ± 1321542.453 ops/s
--------------------------------- AFTER -----------------------------------------------------------------------
------ TODAY
[info] Benchmark Mode Cnt Score Error Units
[info] ByteString_dropSliceTake_Benchmark.bs_large_dropRight_100 thrpt 20 126 091 961.654 ± 2813125.268 ops/s
[info] ByteString_dropSliceTake_Benchmark.bs_large_dropRight_256 thrpt 20 118 393 394.350 ± 2934782.759 ops/s
[info] ByteString_dropSliceTake_Benchmark.bs_large_dropRight_2000 thrpt 20 119 183 386.004 ± 4445324.298 ops/s
[info] ByteString_dropSliceTake_Benchmark.bss_large_dropRight_100 thrpt 20 8 813 065.392 ± 234570.880 ops/s
[info] ByteString_dropSliceTake_Benchmark.bss_large_dropRight_256 thrpt 20 9 039 585.934 ± 297168.301 ops/s
[info] ByteString_dropSliceTake_Benchmark.bss_large_dropRight_2000 thrpt 20 9 629 458.168 ± 124846.904 ops/s
[info] ByteString_dropSliceTake_Benchmark.bs_large_drop_100 thrpt 20 111 666 137.955 ± 4846727.674 ops/s
[info] ByteString_dropSliceTake_Benchmark.bs_large_drop_256 thrpt 20 114 405 514.622 ± 4985750.805 ops/s
[info] ByteString_dropSliceTake_Benchmark.bs_large_drop_2000 thrpt 20 114 364 716.297 ± 2512280.603 ops/s
[info] ByteString_dropSliceTake_Benchmark.bss_large_drop_18 thrpt 20 10 040 457.962 ± 527850.116 ops/s
[info] ByteString_dropSliceTake_Benchmark.bss_large_drop_100 thrpt 20 9 184 934.769 ± 549140.840 ops/s
[info] ByteString_dropSliceTake_Benchmark.bss_large_drop_256 thrpt 20 10 887 437.121 ± 195606.240 ops/s
[info] ByteString_dropSliceTake_Benchmark.bss_large_drop_2000 thrpt 20 10 725 300.292 ± 403470.413 ops/s
[info] ByteString_dropSliceTake_Benchmark.bs_large_slice_80_80 thrpt 20 233 017 314.148 ± 7070246.826 ops/s
[info] ByteString_dropSliceTake_Benchmark.bs_large_slice_129_129 thrpt 20 275 245 086.247 ± 4969752.048 ops/s
[info] ByteString_dropSliceTake_Benchmark.bss_large_slice_80_80 thrpt 20 264 963 420.976 ± 4259289.143 ops/s
[info] ByteString_dropSliceTake_Benchmark.bss_large_slice_129_129 thrpt 20 265 477 577.022 ± 4623974.283 ops/s
*/
// 18 == "http://example.com", a typical url length
@Benchmark
def bs_large_drop_0: ByteString =
bs_large.drop(0)
@Benchmark
def bss_large_drop_0: ByteString =
bss_large.drop(0)
@Benchmark
def bs_large_drop_18: ByteString =
bs_large.drop(18)
@Benchmark
def bss_large_drop_18: ByteString =
bss_large.drop(18)
@Benchmark
def bs_large_drop_100: ByteString =
bs_large.drop(100)
@Benchmark
def bss_large_drop_100: ByteString =
bss_large.drop(100)
@Benchmark
def bs_large_drop_256: ByteString =
bs_large.drop(256)
@Benchmark
def bss_large_drop_256: ByteString =
bss_large.drop(256)
@Benchmark
def bs_large_drop_2000: ByteString =
bs_large.drop(2000)
@Benchmark
def bss_large_drop_2000: ByteString =
bss_large.drop(2000)
/* these force 2 array drops, and 1 element drop inside the 2nd to first/last; can be considered as "bad case" */
@Benchmark
def bs_large_slice_129_129: ByteString =
bs_large.slice(129, 129)
@Benchmark
def bss_large_slice_129_129: ByteString =
bss_large.slice(129, 129)
/* these only move the indexes, don't drop any arrays "happy case" */
@Benchmark
def bs_large_slice_80_80: ByteString =
bs_large.slice(80, 80)
@Benchmark
def bss_large_slice_80_80: ByteString =
bss_large.slice(80, 80)
// drop right ---
@Benchmark
def bs_large_dropRight_100: ByteString =
bs_large.dropRight(100)
@Benchmark
def bss_large_dropRight_100: ByteString =
bss_large.dropRight(100)
@Benchmark
def bs_large_dropRight_256: ByteString =
bs_large.dropRight(256)
@Benchmark
def bss_large_dropRight_256: ByteString =
bss_large.dropRight(256)
@Benchmark
def bs_large_dropRight_2000: ByteString =
bs_large.dropRight(2000)
@Benchmark
def bss_large_dropRight_2000: ByteString =
bss_large.dropRight(2000)
}

View file

@ -473,7 +473,7 @@ private[http] object HttpHeaderParser {
private[parsing] class ModeledHeaderValueParser(headerName: String, maxHeaderValueLength: Int, maxValueCount: Int, settings: HeaderParser.Settings) private[parsing] class ModeledHeaderValueParser(headerName: String, maxHeaderValueLength: Int, maxValueCount: Int, settings: HeaderParser.Settings)
extends HeaderValueParser(headerName, maxValueCount) { extends HeaderValueParser(headerName, maxValueCount) {
def apply(hhp: HttpHeaderParser, input: ByteString, valueStart: Int, onIllegalHeader: ErrorInfo Unit): (HttpHeader, Int) = { def apply(hhp: HttpHeaderParser, input: ByteString, valueStart: Int, onIllegalHeader: ErrorInfo Unit): (HttpHeader, Int) = {
// TODO: optimize by running the header value parser directly on the input ByteString (rather than an extracted String) // TODO: optimize by running the header value parser directly on the input ByteString (rather than an extracted String); seems done?
val (headerValue, endIx) = scanHeaderValue(hhp, input, valueStart, valueStart + maxHeaderValueLength + 2)() val (headerValue, endIx) = scanHeaderValue(hhp, input, valueStart, valueStart + maxHeaderValueLength + 2)()
val trimmedHeaderValue = headerValue.trim val trimmedHeaderValue = headerValue.trim
val header = HeaderParser.parseFull(headerName, trimmedHeaderValue, settings) match { val header = HeaderParser.parseFull(headerName, trimmedHeaderValue, settings) match {
@ -569,4 +569,4 @@ private[http] object HttpHeaderParser {
def withValueCountIncreased = copy(valueCount = valueCount + 1) def withValueCountIncreased = copy(valueCount = valueCount + 1)
def spaceLeft = valueCount < parser.maxValueCount def spaceLeft = valueCount < parser.maxValueCount
} }
} }

View file

@ -101,8 +101,8 @@ private[http] class HttpRequestParser(
val uriEnd = findUriEnd() val uriEnd = findUriEnd()
try { try {
uriBytes = input.iterator.slice(uriStart, uriEnd).toArray[Byte] // TODO: can we reduce allocations here? uriBytes = input.slice(uriStart, uriEnd).toArray[Byte] // TODO: can we reduce allocations here?
uri = Uri.parseHttpRequestTarget(uriBytes, mode = uriParsingMode) uri = Uri.parseHttpRequestTarget(uriBytes, mode = uriParsingMode) // TODO ByteStringParserInput?
} catch { } catch {
case IllegalUriException(info) throw new ParsingException(BadRequest, info) case IllegalUriException(info) throw new ParsingException(BadRequest, info)
} }

View file

@ -52,29 +52,28 @@ object AkkaHttpServerLatencyMultiNodeSpec extends MultiNodeConfig {
private var _ifWrk2Available: Option[Boolean] = None private var _ifWrk2Available: Option[Boolean] = None
final def ifWrk2Available(test: Unit): Unit = final def ifWrk2Available(test: Unit): Unit =
if (isWrk2Available) test else throw new TestPendingException() if (isWrk2Available) test else throw new TestPendingException()
final def isWrk2Available: Boolean = final def isWrk2Available: Boolean =
_ifWrk2Available getOrElse { _ifWrk2Available getOrElse {
import scala.sys.process._ import scala.sys.process._
val wrkExitCode = Try("""wrk""".!).getOrElse(-1) val wrkExitCode = Try("""wrk""".!).getOrElse(-1)
_ifWrk2Available = Some(wrkExitCode == 1) // app found, help displayed _ifWrk2Available = Some(wrkExitCode == 1) // app found, help displayed
isWrk2Available isWrk2Available
} }
private var _abAvailable: Option[Boolean] = None private var _abAvailable: Option[Boolean] = None
final def ifAbAvailable(test: Unit): Unit = final def ifAbAvailable(test: Unit): Unit =
if (isAbAvailable) test else throw new TestPendingException() if (isAbAvailable) test else throw new TestPendingException()
final def isAbAvailable: Boolean = final def isAbAvailable: Boolean =
_abAvailable getOrElse { _abAvailable getOrElse {
import scala.sys.process._ import scala.sys.process._
val abExitCode = Try("""ab -h""".!).getOrElse(-1) val abExitCode = Try("""ab -h""".!).getOrElse(-1)
_abAvailable = Some(abExitCode == 22) // app found, help displayed (22 return code is when -h runs in ab, weird but true) _abAvailable = Some(abExitCode == 22) // app found, help displayed (22 return code is when -h runs in ab, weird but true)
isAbAvailable isAbAvailable
} }
final case class LoadGenCommand(cmd: String) final case class LoadGenCommand(cmd: String)
final case class LoadGenResults(results: String) { final case class LoadGenResults(results: String) {
def lines = results.split("\n") def lines = results.split("\n")
@ -92,13 +91,13 @@ object AkkaHttpServerLatencyMultiNodeSpec extends MultiNodeConfig {
import scala.sys.process._ import scala.sys.process._
def ready(port: Int): Receive = { def ready(port: Int): Receive = {
case LoadGenCommand(cmd) if cmd startsWith "wrk" case LoadGenCommand(cmd) if cmd startsWith "wrk"
val res = val res =
if (isWrk2Available) cmd.!! // blocking. DON'T DO THIS AT HOME, KIDS! if (isWrk2Available) cmd.!! // blocking. DON'T DO THIS AT HOME, KIDS!
else "=== WRK NOT AVAILABLE ===" else "=== WRK NOT AVAILABLE ==="
sender() ! LoadGenResults(res) sender() ! LoadGenResults(res)
case LoadGenCommand(cmd) if cmd startsWith "ab" case LoadGenCommand(cmd) if cmd startsWith "ab"
val res = val res =
if (isAbAvailable) cmd.!! // blocking. DON'T DO THIS AT HOME, KIDS! if (isAbAvailable) cmd.!! // blocking. DON'T DO THIS AT HOME, KIDS!
else "=== AB NOT AVAILABLE ===" else "=== AB NOT AVAILABLE ==="
sender() ! LoadGenResults(res) sender() ! LoadGenResults(res)

View file

@ -35,7 +35,7 @@ trait PredefinedFromEntityUnmarshallers extends MultipartUnmarshallers {
implicit def stringUnmarshaller: FromEntityUnmarshaller[String] = implicit def stringUnmarshaller: FromEntityUnmarshaller[String] =
byteStringUnmarshaller mapWithInput { (entity, bytes) byteStringUnmarshaller mapWithInput { (entity, bytes)
if (entity.isKnownEmpty) "" if (entity.isKnownEmpty) ""
else bytes.decodeString(Unmarshaller.bestUnmarshallingCharsetFor(entity).nioCharset.name) else bytes.decodeString(Unmarshaller.bestUnmarshallingCharsetFor(entity).nioCharset)
} }
implicit def defaultUrlEncodedFormDataUnmarshaller: FromEntityUnmarshaller[FormData] = implicit def defaultUrlEncodedFormDataUnmarshaller: FromEntityUnmarshaller[FormData] =
@ -53,4 +53,4 @@ trait PredefinedFromEntityUnmarshallers extends MultipartUnmarshallers {
} }
} }
object PredefinedFromEntityUnmarshallers extends PredefinedFromEntityUnmarshallers object PredefinedFromEntityUnmarshallers extends PredefinedFromEntityUnmarshallers

View file

@ -904,6 +904,10 @@ object MiMa extends AutoPlugin {
// #20543 GraphStage subtypes should not be private to akka // #20543 GraphStage subtypes should not be private to akka
ProblemFilters.exclude[DirectAbstractMethodProblem]("akka.stream.ActorMaterializer.actorOf") ProblemFilters.exclude[DirectAbstractMethodProblem]("akka.stream.ActorMaterializer.actorOf")
),
"2.4.9" -> Seq(
// #20994 adding new decode method, since we're on JDK7+ now
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.util.ByteString.decodeString")
) )
) )
} }