Skip to content
41 changes: 41 additions & 0 deletions bench/src/main/scala/bench/JsoniterScalaBench.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package bench

import com.rallyhealth.weejson.v1.wee_jsoniter_scala.FromJsoniterScala
import com.rallyhealth.weepickle.v1.WeePickle.ToScala
import org.openjdk.jmh.annotations._

import java.util.concurrent.TimeUnit

/**
* Quick and dirty test to see how badly we're butchering performance of floats.
*
* ==Quick Run==
* bench / Jmh / run .*JsoniterScalabench
*
* ==Profile with Flight Recorder==
* bench / Jmh / run -prof jfr .*JsoniterScalabench
*
* ==Jmh Visualizer Report==
* bench / Jmh / run -prof gc -rf json -rff JsoniterScalabench-results.json .*JsoniterScalabench
*
* @see https://github.com/ktoso/sbt-jmh
*/
@Warmup(iterations = 15, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 15, time = 1, timeUnit = TimeUnit.SECONDS)
@State(Scope.Benchmark)
@BenchmarkMode(Array(Mode.Throughput))
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Fork(
jvmArgsAppend =
Array("-Xmx350m", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:-BackgroundCompilation", "-XX:-TieredCompilation"),
value = 1
)
class JsoniterScalaBench {
private val input = "348249875e105".getBytes()

@Benchmark
def parseDouble = {
FromJsoniterScala(input).transform(ToScala[Double])
}

}
23 changes: 23 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ lazy val bench = project
.dependsOn(
`weepickle-tests` % "compile;test",
`weejson-upickle`,
`weejson-jsoniter-scala`,
)
.enablePlugins(JmhPlugin)
.settings(
Expand Down Expand Up @@ -94,6 +95,7 @@ lazy val `weepickle-tests` = project
`weejson-argonaut`,
`weejson-circe`,
`weejson-json4s`,
`weejson-jsoniter-scala`,
`weejson-play-base`,
`weejson` % "compile;test->test",
`weepack` % "compile;test->test",
Expand Down Expand Up @@ -137,6 +139,27 @@ lazy val `weejson-jackson` = project
)
)

/**
* A very fast JSON parser.
*
* Uses MiMa, but not shaded. Package naming strategy across major versions is unknown.
*
* @see https://github.com/plokhotnyuk/jsoniter-scala
*/
lazy val `weejson-jsoniter-scala` = project
.dependsOn(`weepickle-core`)
.settings(
libraryDependencies ++= Seq(
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core" % "2.12.0"
),
mimaPreviousArtifacts := {
if (VersionNumber(version.value).matchesSemVer(SemanticSelector("<1.8.0")))
Set.empty
else
mimaPreviousArtifacts.value
}
)

lazy val `weejson-circe` = project
.dependsOn(weejson)
.settings(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.rallyhealth.weejson.v1.wee_jsoniter_scala

import com.github.plokhotnyuk.jsoniter_scala.core._
import com.rallyhealth.weejson.v1.wee_jsoniter_scala.WeePickleJsonValueCodecs.VisitorJsonValueEncoder
import com.rallyhealth.weepickle.v1.core.{FromInput, Visitor}

import java.io.InputStream
import java.nio.ByteBuffer

/**
* A very fast UTF-8-only JSON parser.
*
* This integration:
* - tracks paths and returns a JsonPointer on error
* - does not deduplicate string keys (as jackson-core does)
* - throws when a fixed depth limit is reached
*
* @see https://github.com/plokhotnyuk/jsoniter-scala
*/
object FromJsoniterScala extends FromJsoniterScala(ReaderConfig)

class FromJsoniterScala(config: ReaderConfig) {

def apply(
bytes: Array[Byte]
): FromInput = new FromInput {

override def transform[T](
to: Visitor[_, T]
): T = readFromArray(bytes, config)(readerCodec(to))
}

def apply(
in: InputStream
): FromInput = new FromInput {

override def transform[T](
to: Visitor[_, T]
): T = readFromStream(in, config)(readerCodec(to))
}

def apply(
buf: ByteBuffer
): FromInput = new FromInput {

override def transform[T](
to: Visitor[_, T]
): T = readFromByteBuffer(buf, config)(readerCodec(to))
}

private def readerCodec[J](
v: Visitor[_, J]
): JsonValueCodec[J] = new VisitorJsonValueEncoder[J](v, maxDepth = 64)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package com.rallyhealth.weejson.v1.wee_jsoniter_scala

import com.github.plokhotnyuk.jsoniter_scala.core.JsonWriter
import com.rallyhealth.weepickle.v1.core._

import java.time.Instant

class JsonWriterVisitor(
writer: JsonWriter
) extends JsVisitor[Any, JsonWriter] {

private[this] val arrVisitor = new ArrVisitor[Any, JsonWriter] {
override def subVisitor: Visitor[_, _] = JsonWriterVisitor.this

override def visitValue(
v: Any
): Unit = ()

override def visitEnd(): JsonWriter = {
writer.writeArrayEnd()
writer
}
}

private[this] val objVisitor = new ObjVisitor[Any, JsonWriter] {
writer.writeObjectStart()
override def visitKey(): Visitor[_, _] = StringVisitor

override def visitKeyValue(
v: Any
): Unit = writer.writeKey(v.toString)

override def subVisitor: Visitor[_, _] = JsonWriterVisitor.this

override def visitValue(
v: Any
): Unit = ()

override def visitEnd(): JsonWriter = {
writer.writeObjectEnd()
writer
}
}

override def visitArray(
length: Int
): ArrVisitor[Any, JsonWriter] = arrVisitor

override def visitObject(
length: Int
): ObjVisitor[Any, JsonWriter] = objVisitor

override def visitNull(): JsonWriter = {
writer.writeNull()
writer
}

override def visitFalse(): JsonWriter = {
writer.writeVal(false)
writer
}

override def visitTrue(): JsonWriter = {
writer.writeVal(true)
writer
}

override def visitFloat64StringParts(
cs: CharSequence,
decIndex: Int,
expIndex: Int
): JsonWriter = {
writer.writeNonEscapedAsciiVal(cs.toString)
writer
}

override def visitFloat64(
d: Double
): JsonWriter = {
writer.writeVal(d)
writer
}

override def visitFloat32(
d: Float
): JsonWriter = {
writer.writeVal(d)
writer
}

override def visitInt32(
i: Int
): JsonWriter = {
writer.writeVal(i)
writer
}

override def visitInt64(
l: Long
): JsonWriter = {
writer.writeVal(l)
writer
}

override def visitFloat64String(
s: String
): JsonWriter = {
writer.writeNonEscapedAsciiVal(s)
writer
}

override def visitString(
cs: CharSequence
): JsonWriter = {
writer.writeVal(cs.toString)
writer
}

override def visitChar(
c: Char
): JsonWriter = {
writer.writeVal(c)
writer
}

override def visitTimestamp(
instant: Instant
): JsonWriter = {
writer.writeVal(instant)
writer
}

override def visitBinary(
bytes: Array[Byte],
offset: Int,
len: Int
): JsonWriter = {
val trimmed = if (bytes.length != len) bytes.take(len) else bytes
writer.writeBase64Val(trimmed, true)
writer
}
}
Loading