-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
1,026 additions
and
3 deletions.
There are no files selected for viewing
47 changes: 47 additions & 0 deletions
47
bench/src/main/scala/rallyhealth/weepickle/v1/CardinalityVisitor.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package rallyhealth.weepickle.v1 | ||
|
||
import com.rallyhealth.weepickle.v1.core._ | ||
|
||
/** | ||
* Returns the number of elements in an array or object. | ||
* | ||
* Useful for benchmarks to prevent the compiler from cheating. | ||
*/ | ||
class CardinalityVisitor extends SimpleVisitor[Any, Int] { | ||
|
||
override def expectedMsg: String = "expected obj/arr" | ||
|
||
override def visitObject( | ||
length: Int | ||
): ObjVisitor[Any, Int] = new ObjVisitor[Any, Int] { | ||
private var i = 0 | ||
|
||
override def visitKey(): Visitor[_, _] = NoOpVisitor | ||
|
||
override def visitKeyValue( | ||
v: Any | ||
): Unit = () | ||
|
||
override def subVisitor: Visitor[_, _] = NoOpVisitor | ||
|
||
override def visitValue( | ||
v: Any | ||
): Unit = i += 1 | ||
|
||
override def visitEnd(): Int = i | ||
} | ||
|
||
override def visitArray( | ||
length: Int | ||
): ArrVisitor[Any, Int] = new ArrVisitor[Any, Int] { | ||
private var i = 0 | ||
|
||
override def subVisitor: Visitor[_, _] = NoOpVisitor | ||
|
||
override def visitValue( | ||
v: Any | ||
): Unit = i += 1 | ||
|
||
override def visitEnd(): Int = i | ||
} | ||
} |
74 changes: 74 additions & 0 deletions
74
bench/src/main/scala/rallyhealth/weepickle/v1/core/ops/ConcatFromInputsBench.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package rallyhealth.weepickle.v1.core.ops | ||
|
||
import com.rallyhealth.weepickle.v1.core.ops.ConcatFromInputs | ||
import com.rallyhealth.weepickle.v1.core.{FromInput, Visitor} | ||
import org.openjdk.jmh.annotations._ | ||
import rallyhealth.weepickle.v1.CardinalityVisitor | ||
|
||
import java.util.concurrent.TimeUnit | ||
|
||
/** | ||
* ==Quick Run== | ||
* bench / Jmh / run -f1 -wi 2 -i 3 .*ConcatFromInputsBench | ||
* | ||
* ==Profile with Flight Recorder== | ||
* bench / Jmh / run -prof jfr -f1 .*ConcatFromInputsBench | ||
* | ||
* ==Jmh Visualizer Report== | ||
* bench / Jmh / run -prof gc -rf json -rff ConcatFromInputsBench-results.json .*ConcatFromInputsBench | ||
* | ||
* ==Sample Results== | ||
* bench / Jmh / run -f3 -wi 3 -i 3 .*ConcatFromInputsBench | ||
* {{{ | ||
* Benchmark Mode Cnt Score Error Units | ||
* ConcatFromInputsBench.arrs thrpt 9 96.627 ± 6.636 ops/s | ||
* ConcatFromInputsBench.objs thrpt 9 72.950 ± 13.792 ops/s | ||
* }}} | ||
* | ||
* @see https://github.com/ktoso/sbt-jmh | ||
*/ | ||
@Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS) | ||
@Measurement(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS) | ||
@State(Scope.Benchmark) | ||
@BenchmarkMode(Array(Mode.Throughput)) | ||
@OutputTimeUnit(TimeUnit.SECONDS) | ||
@Fork(jvmArgsAppend = Array("-Xmx350m", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:-BackgroundCompilation", "-XX:-TieredCompilation"), value = 1) | ||
class ConcatFromInputsBench { | ||
|
||
@Benchmark | ||
def objs: Int = ConcatFromInputs.from(manyObjs).get.transform(sink) | ||
|
||
@Benchmark | ||
def arrs: Int = ConcatFromInputs.from(manyArrs).get.transform(sink) | ||
|
||
private val manyObjs = { | ||
// not using FromScala(Obj(...)) to avoid profiler noise from singleObj.size(), etc. | ||
val singleObj = new FromInput { | ||
override def transform[T]( | ||
to: Visitor[_, T] | ||
): T = { | ||
val obj = to.visitObject(1).narrow | ||
obj.visitKeyValue(obj.visitKey().visitString("key")) | ||
obj.visitValue(obj.subVisitor.visitTrue()) | ||
obj.visitEnd() | ||
} | ||
} | ||
Seq.fill(1000 * 1000)(singleObj) | ||
} | ||
|
||
private val manyArrs = { | ||
// not using FromScala(Arr(...)) to avoid profiler noise from singleArr.size(), etc. | ||
val singleArr = new FromInput { | ||
override def transform[T]( | ||
to: Visitor[_, T] | ||
): T = { | ||
val arr = to.visitArray(1).narrow | ||
arr.visitValue(arr.subVisitor.visitTrue()) | ||
arr.visitEnd() | ||
} | ||
} | ||
Seq.fill(1000 * 1000)(singleArr) | ||
} | ||
|
||
private def sink = new CardinalityVisitor | ||
} |
12 changes: 12 additions & 0 deletions
12
bench/src/test/scala/com/rallyhealth/weepickle/v1/core/ops/ConcatFromInputsBenchTests.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.rallyhealth.weepickle.v1.core.ops | ||
|
||
import rallyhealth.weepickle.v1.core.ops.ConcatFromInputsBench | ||
import utest._ | ||
|
||
object ConcatFromInputsBenchTests extends TestSuite { | ||
|
||
override val tests = Tests { | ||
test("objs")(new ConcatFromInputsBench().objs ==> 1000000) | ||
test("arrs")(new ConcatFromInputsBench().arrs ==> 1000000) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.9.2") | ||
addSbtPlugin("com.dwijnand" % "sbt-dynver" % "4.1.1") | ||
addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.3.7") | ||
addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.2") | ||
addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.7") | ||
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
143 changes: 143 additions & 0 deletions
143
weepickle-core/src/main/scala/com/rallyhealth/weepickle/v1/core/ops/ConcatFromInputs.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
package com.rallyhealth.weepickle.v1.core.ops | ||
|
||
import com.rallyhealth.weepickle.v1.core.Visitor.{ArrDelegate, ObjDelegate} | ||
import com.rallyhealth.weepickle.v1.core._ | ||
import scala.collection.compat._ | ||
|
||
import scala.annotation.tailrec | ||
|
||
object ConcatFromInputs { | ||
|
||
/** | ||
* Concatenates elements of one or more objects or arrays. | ||
* | ||
* ==Examples== | ||
* - {{{ [1] ++ [2] ==> [1, 2] }}} | ||
* - {{{ {"a": 0} ++ {"b": 1} ==> {"a": 0, "b": 1} }}} | ||
* - {{{ {"a": 0} ++ {"a": 1} ==> {"a": 0, "a": 1} }}} | ||
* - {{{ {"a": 0} ++ [false] ==> }}} [[com.rallyhealth.weepickle.v1.core.Abort]] | ||
* | ||
* ==Caveats== | ||
* - Does not report object or array length. Will not work for msgpack. | ||
*/ | ||
def apply( | ||
head: FromInput, | ||
tail: FromInput* | ||
): FromInput = from(head +: tail).get | ||
|
||
/** | ||
* Concatenates elements of one or more objects or arrays. | ||
* | ||
* ==Examples== | ||
* - {{{ [1] ++ [2] ==> [1, 2] }}} | ||
* - {{{ {"a": 0} ++ {"b": 1} ==> {"a": 0, "b": 1} }}} | ||
* - {{{ {"a": 0} ++ {"a": 1} ==> {"a": 0, "a": 1} }}} | ||
* - {{{ {"a": 0} ++ [false] ==> }}} [[com.rallyhealth.weepickle.v1.core.Abort]] | ||
* | ||
* ==Caveats== | ||
* - Does not report object or array length. Will not work for msgpack. | ||
*/ | ||
def from( | ||
inputs: Iterable[FromInput] | ||
): Option[FromInput] = { | ||
if (inputs.isEmpty) None | ||
else if (inputs.sizeCompare(1) == 0) Some(inputs.head) | ||
else { | ||
// flatten for stack safety | ||
val flattened = | ||
if (inputs.exists(_.isInstanceOf[ConcatFromInputs])) { | ||
inputs.flatMap { | ||
case c: ConcatFromInputs => c.inputs | ||
case other => other :: Nil | ||
} | ||
} else inputs | ||
Some(new ConcatFromInputs(flattened)) | ||
} | ||
} | ||
|
||
private[this] class ConcatFromInputs( | ||
val inputs: Iterable[FromInput] | ||
) extends FromInput { | ||
|
||
assert(inputs.sizeCompare(1) > 0) | ||
|
||
override def transform[T]( | ||
to: Visitor[_, T] | ||
): T = { | ||
val it = inputs.iterator | ||
it.next().transform { | ||
new SimpleVisitor[Any, T] { | ||
override def expectedMsg: String = "expected arr or obj" | ||
|
||
override def visitObject( | ||
length: Int | ||
): ObjVisitor[Any, T] = { | ||
val obj = to.visitObject(-1).narrow // final length unknown | ||
val nonLastObj = new ObjDelegate[Any, T](obj) { | ||
override def visitEnd() = null.asInstanceOf[T] | ||
} | ||
|
||
new ObjDelegate[Any, T](obj) { | ||
override def visitEnd(): T = { | ||
@tailrec def transformNext(): T = { | ||
val result = it.next().transform { | ||
new SimpleVisitor[Any, T] { | ||
override def expectedMsg: String = "expected another obj" | ||
|
||
override def visitObject( | ||
length: Int | ||
): ObjVisitor[Any, T] = { | ||
// suppress visitEnd if more input is coming | ||
if (it.hasNext) nonLastObj | ||
else obj | ||
} | ||
} | ||
} | ||
|
||
if (it.hasNext) transformNext() | ||
else result | ||
} | ||
|
||
transformNext() | ||
} | ||
} | ||
} | ||
|
||
override def visitArray( | ||
length: Int | ||
): ArrVisitor[Any, T] = { | ||
val arr = to.visitArray(-1).narrow // final length unknown | ||
val nonLastArr = new ArrDelegate[Any, T](arr) { | ||
override def visitEnd() = null.asInstanceOf[T] | ||
} | ||
|
||
new ArrDelegate[Any, T](arr) { | ||
override def visitEnd(): T = { | ||
@tailrec def transformNext(): T = { | ||
val result = it.next().transform { | ||
new SimpleVisitor[Any, T] { | ||
override def expectedMsg: String = "expected another arr" | ||
|
||
override def visitArray( | ||
length: Int | ||
): ArrVisitor[Any, T] = { | ||
// suppress visitEnd if more input is coming | ||
if (it.hasNext) nonLastArr | ||
else arr | ||
} | ||
} | ||
} | ||
|
||
if (it.hasNext) transformNext() | ||
else result | ||
} | ||
|
||
transformNext() | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
weepickle-core/src/main/scala/com/rallyhealth/weepickle/v1/core/ops/FromInputOps.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package com.rallyhealth.weepickle.v1.core.ops | ||
|
||
import com.rallyhealth.weepickle.v1.core.FromInput | ||
|
||
/** | ||
* Mixes extensions into bundles, e.g. WeePickle. | ||
*/ | ||
trait FromInputOpsImplicits { | ||
|
||
implicit def asFromInputOps(a: FromInput): FromInputOps = new FromInputOps(a) | ||
} | ||
|
||
/** | ||
* Extension operations on [[FromInput]] that are part of the core API. | ||
* | ||
* More methods may be added here without breaking binary compatibility (which | ||
* is not possible with traits). | ||
*/ | ||
class FromInputOps( | ||
val a: FromInput | ||
) extends AnyVal { | ||
|
||
/** | ||
* Concatenates elements of one or more objects or arrays. | ||
* | ||
* ==Examples== | ||
* - {{{ [1] ++ [2] ==> [1, 2] }}} | ||
* - {{{ {"a": 0} ++ {"b": 1} ==> {"a": 0, "b": 1} }}} | ||
* - {{{ {"a": 0} ++ {"a": 1} ==> {"a": 0, "a": 1} }}} | ||
* - {{{ {"a": 0} ++ [false] ==> }}} [[com.rallyhealth.weepickle.v1.core.Abort]] | ||
* | ||
* ==Caveats== | ||
* - Does not report object or array length. Will not work for msgpack. | ||
*/ | ||
def ++( | ||
b: FromInput | ||
): FromInput = ConcatFromInputs(a, b) | ||
} |
Oops, something went wrong.