Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[preview] String parsing improvements #314

Draft
wants to merge 19 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions benchmark/src/main/resources/big-string-array.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion benchmark/src/main/scala/spray/json/Common.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ import org.openjdk.jmh.annotations._
))
@BenchmarkMode(Array(Mode.Throughput))
@OutputTimeUnit(TimeUnit.SECONDS)
abstract class Common
abstract class Common
168 changes: 168 additions & 0 deletions benchmark/src/main/scala/spray/json/GithubIssuesCaseClassReading.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package spray.json

import com.fasterxml.jackson.databind.DeserializationFeature
import org.openjdk.jmh.annotations.Benchmark
import org.openjdk.jmh.annotations.Setup
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule

import scala.io.Source

object GithubIssuesPartialModel {
import DefaultJsonProtocol._
case class LabelsArrayElement(
id: Long,
url: String,
//node_id: String,
name: String,
color: String,
default: Boolean)

implicit val LabelsArrayElementFormat = jsonFormat5(LabelsArrayElement.apply _)

case class User(
login: String,
id: Long,
node_id: String /*,
//gists_url: String,
//organizations_url: String,
gravatar_id: String,
//url: String,

//repos_url: String,
//received_events_url: String,

//following_url: String,
site_admin: Boolean,
//subscriptions_url: String,
//starred_url: String,
//html_url: String,

`type`: String,
//events_url: String,
//avatar_url: String,
followers_url: String*/ )

implicit val UserFormat = jsonFormat3(User.apply _)

case class Pull_requestOptionElement(
diff_url: String,
html_url: String,
patch_url: String,
url: String)

implicit val Pull_requestOptionElementFormat = jsonFormat4(Pull_requestOptionElement.apply _)

case class RootArrayElement(
url: String,
repository_url: String,
//labels_url: String,
comments_url: String,
//events_url: String,
html_url: String,
id: Long,
//node_id: String,
number: Long,
title: String,
user: User,
//labels: Seq[LabelsArrayElement],
state: String,

locked: Boolean,
//assignee: Option[User],
assignees: Seq[User],
created_at: String,

//body: Option[String],
//milestone: Option[String],
//closed_at: Option[String],

updated_at: String,
author_association: String

//,
//comments: BigDecimal//,
//pull_request: Option[Pull_requestOptionElement]
)

implicit val RootArrayElementFormat = jsonFormat14(RootArrayElement.apply _)
}

class GithubIssuesCaseClassReading extends Common {
var jsonString: String = _
var jsonBytes: Array[Byte] = _

implicit val playJsonRootFormat = {
import GithubIssuesPartialModel._
import play.api.libs.json._
implicit val playJsonUserFormat: Format[User] = play.api.libs.json.Json.format
implicit val playJsonLabelFormat: Format[LabelsArrayElement] = play.api.libs.json.Json.format
play.api.libs.json.Json.format: Format[RootArrayElement]
}

implicit def circeRootFormat = {
import GithubIssuesPartialModel._
import io.circe._
import io.circe.generic.semiauto._
import io.circe.parser._
implicit def circeUserFormat: Decoder[User] = deriveDecoder
implicit def circeLabelFormat: Decoder[LabelsArrayElement] = deriveDecoder

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jrudolph please consider to cache derived decoders using val for better performance


deriveDecoder: Decoder[RootArrayElement]
}

implicit def upickleDefaultFormat = {
import GithubIssuesPartialModel._
import upickle.default.{ ReadWriter => RW, Reader => R, Writer => W }
implicit val userFormat: R[User] = upickle.default.macroR[User]
implicit val labelFormat: R[LabelsArrayElement] = upickle.default.macroR[LabelsArrayElement]
upickle.default.macroR[RootArrayElement]
}

val jacksonMapper: ObjectMapper with ScalaObjectMapper = new ObjectMapper with ScalaObjectMapper {
registerModule(DefaultScalaModule)
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
}

@Setup
def setup(): Unit = {
jsonString = Source.fromResource("github-akka-issues.json").mkString
jsonBytes = jsonString.getBytes("utf8") // yeah, a useless utf8 roundtrip
}

@Benchmark
def readSprayJsonViaASTPartial(): AnyRef = {
import DefaultJsonProtocol._
JsonParser(jsonBytes).convertTo[Seq[GithubIssuesPartialModel.RootArrayElement]]
}

@Benchmark
def readPlayJsonPartial(): AnyRef = {
import play.api.libs.json.Json
Json.fromJson[Seq[GithubIssuesPartialModel.RootArrayElement]](Json.parse(jsonBytes)).get
}

@Benchmark
def readJacksonPartial(): AnyRef = {
jacksonMapper.readValue[Array[GithubIssuesPartialModel.RootArrayElement]](jsonBytes)
//Json.fromJson[Seq[GithubIssuesPartialModel.RootArrayElement]](Json.parse(jsonBytes)).get
}

/*
Fails with "upickle.core.Abort: expected sequence got int32"
@Benchmark
def readUPickleDefaultBinaryPartial(): Unit =
upickle.default.readBinary[Seq[GithubIssuesPartialModel.RootArrayElement]](jsonBytes)

*/

@Benchmark
def readCircePartial(): AnyRef = {
import io.circe._
import io.circe.generic.semiauto._
import io.circe.parser._
decode[Seq[GithubIssuesPartialModel.RootArrayElement]](jsonString).right.get
}
}
11 changes: 7 additions & 4 deletions benchmark/src/main/scala/spray/json/GithubIssuesJsonAST.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package spray.json

import org.openjdk.jmh.annotations.Benchmark
import org.openjdk.jmh.annotations.Param
import org.openjdk.jmh.annotations.Setup

import scala.io.Source

import play.api.libs.json.Json

class GithubIssuesJsonAST extends Common {
@Param(Array("github-akka-issues.json", "big-string-array.json"))
var res: String = null

var jsonString: String = _
var jsonBytes: Array[Byte] = _
var sprayJsonAST: JsValue = _
Expand All @@ -17,19 +20,19 @@ class GithubIssuesJsonAST extends Common {

@Setup
def setup(): Unit = {
jsonString = Source.fromResource("github-akka-issues.json").mkString
jsonString = Source.fromResource(res).mkString
jsonBytes = jsonString.getBytes("utf8") // yeah, a useless utf8 roundtrip
sprayJsonAST = JsonParser(jsonString)
playJsonAST = Json.parse(jsonString)
upickleAST = ujson.read(jsonString)
circeAST = io.circe.parser.parse(jsonString).right.get
circeAST = io.circe.parser.parse(jsonString).right.get
}

@Benchmark
def readSprayJsonFromString(): Unit = JsonParser(jsonString)

@Benchmark
def readSprayJsonFromBytes(): Unit = JsonParser(jsonString)
def readSprayJsonFromBytes(): Unit = JsonParser(jsonBytes)

@Benchmark
def readPlayJson(): Unit = Json.parse(jsonString)
Expand Down
15 changes: 8 additions & 7 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ lazy val sprayJson =
)
.configurePlatforms(JVMPlatform)(_.enablePlugins(SbtOsgi))
.jvmSettings(
crossScalaVersions := Seq(scala213, scala212, scala211, scala210),
crossScalaVersions := Seq(scala212, scala213, scala211, scala210),
OsgiKeys.exportPackage := Seq("""spray.json.*;version="${Bundle-Version}""""),
OsgiKeys.importPackage := Seq("""scala.*;version="$<range;[==,=+);%s>"""".format(scalaVersion.value)),
OsgiKeys.importPackage ++= Seq("""spray.json;version="${Bundle-Version}"""", "*"),
Expand All @@ -78,17 +78,18 @@ lazy val sprayJsonJS = sprayJson.js
lazy val sprayJsonNative = sprayJson.native

lazy val benchmark = Project("benchmark", file("benchmark"))
.settings(
scalaVersion := scala212
)
.settings(crossScalaVersions := Seq(scala212))
.settings(noPublishSettings: _*)
.enablePlugins(JmhPlugin)
.dependsOn(sprayJsonJVM % "compile->test")
.settings(
libraryDependencies ++= Seq(
"com.lihaoyi" %% "upickle" % "0.6.7",
"io.circe" %% "circe-parser" % "0.10.0",
"com.typesafe.play" %% "play-json" % "2.7.0-M1"
"com.lihaoyi" %% "upickle" % "0.7.5",
"io.circe" %% "circe-parser" % "0.11.1",
"io.circe" %% "circe-core" % "0.11.1",
"io.circe" %% "circe-generic" % "0.11.1",
"com.typesafe.play" %% "play-json" % "2.7.4",
"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.9.9",
)
)

Expand Down
2 changes: 1 addition & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "0.6.0")
addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "0.6.0")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.27")
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.3.8")
addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.3.4")
addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.3.7")
addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.3")
Loading