Skip to content

Commit

Permalink
ZIO based parser experiments
Browse files Browse the repository at this point in the history
  • Loading branch information
Greg Zoller committed Nov 14, 2023
1 parent e48b358 commit ee5da35
Show file tree
Hide file tree
Showing 42 changed files with 2,798 additions and 374 deletions.
18 changes: 9 additions & 9 deletions benchmark/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ sbt "jmh:run -i 10 -wi 10 -f 2 -t 1 co.blocke.WritingBenchmark"

## Writing Performance:

| Benchmark | Mode | Count | Score | Error | Units |
|------------------|-------|----:|------------:|-------------:|-------|
| Hand-Tooled | thrpt | 20 | 2,575,393.513 | ± 178731.952 | ops/s |
| Circe | thrpt | 20 | 1,939,339.085 | ± 6279.547 | ops/s |
|**ScalaJack 8** | thrpt | 20 | **1,703,256.521** | ± 12260.518 | ops/s |
| ZIO JSON | thrpt | 20 | 818,228.736 | ± 3070.298 | ops/s |
| Argonaut | thrpt | 20 | 716,228.404 | ± 6241.145 | ops/s |
| Play JSON | thrpt | 20 | 438,538.475 | ± 16319.198 | ops/s |
| ScalaJack 7 | thrpt | 20 | 106,292.338 | ± 330.111 | ops/s |
| Benchmark | Mode | Count | Score | Error | Units |
|------------------|-------|-------:|----------------:|-------------:|-------|
| Hand-Tooled | thrpt | 20 | 2,575,393.513 | ± 178731.952 | ops/s |
| Circe | thrpt | 20 | 1,939,339.085 | ± 6279.547 | ops/s |
|**ScalaJack 8** | thrpt | 20 | **176,867,514.557** | ± 12260.518 | ops/s |
| ZIO JSON | thrpt | 20 | 818,228.736 | ± 3070.298 | ops/s |
| Argonaut | thrpt | 20 | 716,228.404 | ± 6241.145 | ops/s |
| Play JSON | thrpt | 20 | 438,538.475 | ± 16319.198 | ops/s |
| ScalaJack 7 | thrpt | 20 | 106,292.338 | ± 330.111 | ops/s |

### Interpretation

Expand Down
12 changes: 7 additions & 5 deletions benchmark/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ val compilerOptions = Seq(
"-feature",
"-language:existentials",
"-language:higherKinds",
"-unchecked",
"-Ywarn-dead-code",
"-Ywarn-numeric-widen",
"-Xfuture"
"-unchecked"
)

val circeVersion = "0.15.0-M1"
Expand Down Expand Up @@ -39,9 +36,14 @@ lazy val benchmark = project
libraryDependencies ++= Seq(
"org.playframework" %% "play-json" % "3.0.1",
"io.argonaut" %% "argonaut" % "6.3.9",
"co.blocke" %% "scalajack" % "826a30_unknown",
// "co.blocke" %% "scalajack" % "826a30_unknown", // Old-New
"co.blocke" %% "scalajack" % "e48b35_unknown", // New-New
"co.blocke" %% "scala-reflection" % "sj_fixes_edbef8",
"dev.zio" %% "zio-json" % "0.6.1",
"org.typelevel" %% "fabric-core" % "1.12.6",
"org.typelevel" %% "fabric-io" % "1.12.6",
"org.typelevel" %% "jawn-parser" % "1.3.2",
"org.typelevel" %% "jawn-ast" % "1.3.2",
// "io.circe" %% "circe-derivation" % "0.15.0-M1",
// "io.circe" %% "circe-jackson29" % "0.14.0",
// "org.json4s" %% "json4s-jackson" % "4.0.4",
Expand Down
31 changes: 16 additions & 15 deletions benchmark/src/main/scala/co.blocke/Argonaut.scala
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
package co.blocke

import argonaut._, Argonaut._
import org.openjdk.jmh.annotations._


implicit val CodecPet: CodecJson[Pet] =
casecodec3(Pet.apply, (a: Pet) => Option((a.name, a.species, a.age)))("name","species","age")
object ArgonautZ:
import argonaut._, Argonaut._

implicit val CodecFriend: CodecJson[Friend] =
casecodec3(Friend.apply, (a: Friend) => Option((a.name, a.age, a.email)))("name","age","email")
implicit val CodecPet: CodecJson[Pet] =
casecodec3(Pet.apply, (a: Pet) => Option((a.name, a.species, a.age)))("name","species","age")

implicit val CodecAddress: CodecJson[Address] =
casecodec4(Address.apply, (a: Address) => Option((a.street, a.city, a.state, a.postal_code)))("street","city","state","postal_code")
implicit val CodecFriend: CodecJson[Friend] =
casecodec3(Friend.apply, (a: Friend) => Option((a.name, a.age, a.email)))("name","age","email")

implicit val CodecPerson: CodecJson[Person] =
casecodec6(Person.apply, (a: Person) => Option((a.name, a.age, a.address, a.email, a.phone_numbers, a.is_employed)))("name", "age","address","email","phone_numbers","is_employed")
implicit val CodecAddress: CodecJson[Address] =
casecodec4(Address.apply, (a: Address) => Option((a.street, a.city, a.state, a.postal_code)))("street","city","state","postal_code")

implicit val CodecRecord: CodecJson[Record] =
casecodec4(Record.apply, (a: Record) => Option((a.person, a.hobbies, a.friends, a.pets)))("person", "hobbies", "friends", "pets")
implicit val CodecPerson: CodecJson[Person] =
casecodec6(Person.apply, (a: Person) => Option((a.name, a.age, a.address, a.email, a.phone_numbers, a.is_employed)))("name", "age","address","email","phone_numbers","is_employed")

implicit val CodecRecord: CodecJson[Record] =
casecodec4(Record.apply, (a: Record) => Option((a.person, a.hobbies, a.friends, a.pets)))("person", "hobbies", "friends", "pets")

trait ArgonautWritingBenchmark {
@Benchmark
def writeRecordArgonaut = record.asJson
}
trait ArgonautWritingBenchmark {
@Benchmark
def writeRecordArgonaut = record.asJson
}
97 changes: 42 additions & 55 deletions benchmark/src/main/scala/co.blocke/Benchmark.scala
Original file line number Diff line number Diff line change
@@ -1,52 +1,14 @@
package co.blocke

import org.openjdk.jmh.annotations._

import java.util.concurrent.TimeUnit

import co.blocke.scalajack.*

import io.circe.syntax.*
import io.circe.*
import io.circe.generic.semiauto.*

val record = ScalaJack.read[Record](jsData)

implicit val recordDecoder: Decoder[Record] = deriveDecoder[Record]
implicit val recordEncoder: Encoder[Record] = deriveEncoder[Record]

implicit val personDecoder: Decoder[Person] = deriveDecoder[Person]
implicit val personEncoder: Encoder[Person] = deriveEncoder[Person]

implicit val addressDecoder: Decoder[Address] = deriveDecoder[Address]
implicit val addressEncoder: Encoder[Address] = deriveEncoder[Address]

implicit val friendDecoder: Decoder[Friend] = deriveDecoder[Friend]
implicit val friendEncoder: Encoder[Friend] = deriveEncoder[Friend]

implicit val petDecoder: Decoder[Pet] = deriveDecoder[Pet]
implicit val petEncoder: Encoder[Pet] = deriveEncoder[Pet]



trait CirceReadingBenchmark{
val circeJson = record.asJson
def readRecordCirce = circeJson.as[Record]
}

// trait ScalaJackReadingBenchmark{
// def readRecordScalaJack = ScalaJack.read[Record](jsData)
// }

trait CirceWritingBenchmark {
@Benchmark
def writeRecordCirce = record.asJson
}
import ZIOZ.*
import zio.json._
val record = jsData.fromJson[Record] match
case Right(r) => r
case Left(_) => null.asInstanceOf[Record]

trait ScalaJackWritingBenchmark {
@Benchmark
def writeRecordScalaJack = ScalaJack.write(record)
}

trait HandTooledWritingBenchmark {
@Benchmark
Expand Down Expand Up @@ -77,20 +39,45 @@ trait HandTooledWritingBenchmark {
sb.toString
}

// @State(Scope.Thread)
// @BenchmarkMode(Array(Mode.Throughput))
// @OutputTimeUnit(TimeUnit.SECONDS)
// class ReadingBenchmark
// extends CirceReadingBenchmark
// with ScalaJackReadingBenchmark
@State(Scope.Thread)
@BenchmarkMode(Array(Mode.Throughput))
@OutputTimeUnit(TimeUnit.SECONDS)
class ReadingBenchmark
// extends CirceZ.CirceReadingBenchmark
extends ScalaJackZ.ScalaJackReadingBenchmark
// extends ZIOZ.ZIOJsonReadingBenchmark
// extends PlayZ.PlayReadingBenchmark
// extends FabricZ.FabricReadingBenchmark
// extends JawnZ.JawnReadingBenchmark

@State(Scope.Thread)
@BenchmarkMode(Array(Mode.Throughput))
@OutputTimeUnit(TimeUnit.SECONDS)
class WritingBenchmark
extends CirceWritingBenchmark
with ScalaJackWritingBenchmark
with HandTooledWritingBenchmark
with ArgonautWritingBenchmark
with PlayWritingBenchmark
with ZIOJsonWritingBenchmark
// extends CirceZ.CirceWritingBenchmark
extends ScalaJackZ.ScalaJackWritingBenchmark
// with HandTooledWritingBenchmark
// with ArgonautZ.ArgonautWritingBenchmark
// with PlayZ.PlayWritingBenchmark
// with ZIOZ.ZIOJsonWritingBenchmark


// "Old-New" ScalaJack
// [info] Benchmark Mode Cnt Score Error Units
// [info] ReadingBenchmark.readRecordScalaJack thrpt 20 30113.982 ± 97.701 ops/s
// [info] New-New ScalaJack thrpt 20 50908.982 ± 97.701 ops/s

// ScalaJack w/ZIO-based parser 635977.008
// ZIO (Fast!!) 568123.000 <-- How do they do this?! More than 2x faster than everyone else!
// Circe 279231.646
// Play 209756.408

// Jawn (parse only + AST) 336384.617
// ScalaJack JsonParser3 (parse only + AST) 279456.523
// Fabric (new!) (parse only + AST) 270706.567




// SJ StringBuffer : 1740040.225
// SJ FastStringBuffer :
34 changes: 34 additions & 0 deletions benchmark/src/main/scala/co.blocke/Circe.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package co.blocke

import org.openjdk.jmh.annotations._

object CirceZ:
import io.circe.syntax.*
import io.circe.*
import io.circe.generic.semiauto.*
import io.circe.parser.*

implicit val recordDecoder: Decoder[Record] = deriveDecoder[Record]
implicit val recordEncoder: Encoder[Record] = deriveEncoder[Record]

implicit val personDecoder: Decoder[Person] = deriveDecoder[Person]
implicit val personEncoder: Encoder[Person] = deriveEncoder[Person]

implicit val addressDecoder: Decoder[Address] = deriveDecoder[Address]
implicit val addressEncoder: Encoder[Address] = deriveEncoder[Address]

implicit val friendDecoder: Decoder[Friend] = deriveDecoder[Friend]
implicit val friendEncoder: Encoder[Friend] = deriveEncoder[Friend]

implicit val petDecoder: Decoder[Pet] = deriveDecoder[Pet]
implicit val petEncoder: Encoder[Pet] = deriveEncoder[Pet]

trait CirceReadingBenchmark{
@Benchmark
def readRecordCirce = parse(jsData).flatMap(_.as[Record])
}

trait CirceWritingBenchmark {
@Benchmark
def writeRecordCirce = record.asJson
}
17 changes: 17 additions & 0 deletions benchmark/src/main/scala/co.blocke/Fabric.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package co.blocke

import org.openjdk.jmh.annotations._

object FabricZ:
import fabric.*
import fabric.io.*

trait FabricReadingBenchmark{
@Benchmark
def readRecordFabric = JsonParser(jsData, Format.Json)
}

// trait CirceWritingBenchmark {
// @Benchmark
// def writeRecordCirce = record.asJson
// }
17 changes: 17 additions & 0 deletions benchmark/src/main/scala/co.blocke/Jawn.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package co.blocke

import org.openjdk.jmh.annotations._

object JawnZ:

import org.typelevel.jawn.ast.*

trait JawnReadingBenchmark{
@Benchmark
def readRecordFabric = JParser.parseFromString(jsData)
}

// trait CirceWritingBenchmark {
// @Benchmark
// def writeRecordCirce = record.asJson
// }
98 changes: 56 additions & 42 deletions benchmark/src/main/scala/co.blocke/PlayJson.scala
Original file line number Diff line number Diff line change
@@ -1,46 +1,60 @@
package co.blocke

import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
import org.openjdk.jmh.annotations._

implicit val friendWrites: Writes[Friend] = (
(JsPath \ "name").write[String] and
(JsPath \ "age").write[Int] and
(JsPath \ "email").write[String]
)(unlift((a: Friend) => Option((a.name, a.age, a.email))))

implicit val petWrites: Writes[Pet] = (
(JsPath \ "name").write[String] and
(JsPath \ "species").write[String] and
(JsPath \ "age").write[Int]
)(unlift((a: Pet) => Option((a.name, a.species, a.age))))

implicit val addressWrites: Writes[Address] = (
(JsPath \ "street").write[String] and
(JsPath \ "city").write[String] and
(JsPath \ "state").write[String] and
(JsPath \ "postal_code").write[String]
)(unlift((a: Address) => Option((a.street, a.city, a.state, a.postal_code))))

implicit val personWrites: Writes[Person] = (
(JsPath \ "namet").write[String] and
(JsPath \ "age").write[Int] and
(JsPath \ "address").write[Address] and
(JsPath \ "email").write[String] and
(JsPath \ "phone_numbers").write[List[String]] and
(JsPath \ "is_employed").write[Boolean]
)(unlift((a: Person) => Option((a.name, a.age, a.address, a.email, a.phone_numbers, a.is_employed))))

implicit val recordWrites: Writes[Record] = (
(JsPath \ "person").write[Person] and
(JsPath \ "hobbies").write[List[String]] and
(JsPath \ "friends").write[List[Friend]] and
(JsPath \ "pets").write[List[Pet]]
)(unlift((a: Record) => Option((a.person, a.hobbies, a.friends, a.pets))))

trait PlayWritingBenchmark {
@Benchmark
def writeRecordPlay = Json.toJson(record)
}
object PlayZ:
import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._

implicit val friendWrites: Writes[Friend] = (
(JsPath \ "name").write[String] and
(JsPath \ "age").write[Int] and
(JsPath \ "email").write[String]
)(unlift((a: Friend) => Option((a.name, a.age, a.email))))

implicit val petWrites: Writes[Pet] = (
(JsPath \ "name").write[String] and
(JsPath \ "species").write[String] and
(JsPath \ "age").write[Int]
)(unlift((a: Pet) => Option((a.name, a.species, a.age))))

implicit val addressWrites: Writes[Address] = (
(JsPath \ "street").write[String] and
(JsPath \ "city").write[String] and
(JsPath \ "state").write[String] and
(JsPath \ "postal_code").write[String]
)(unlift((a: Address) => Option((a.street, a.city, a.state, a.postal_code))))

implicit val personWrites: Writes[Person] = (
(JsPath \ "namet").write[String] and
(JsPath \ "age").write[Int] and
(JsPath \ "address").write[Address] and
(JsPath \ "email").write[String] and
(JsPath \ "phone_numbers").write[List[String]] and
(JsPath \ "is_employed").write[Boolean]
)(unlift((a: Person) => Option((a.name, a.age, a.address, a.email, a.phone_numbers, a.is_employed))))

implicit val recordWrites: Writes[Record] = (
(JsPath \ "person").write[Person] and
(JsPath \ "hobbies").write[List[String]] and
(JsPath \ "friends").write[List[Friend]] and
(JsPath \ "pets").write[List[Pet]]
)(unlift((a: Record) => Option((a.person, a.hobbies, a.friends, a.pets))))

implicit val friendReads: play.api.libs.json.Reads[co.blocke.Friend] = Json.reads[Friend]
implicit val petReads: play.api.libs.json.Reads[co.blocke.Pet] = Json.reads[Pet]
implicit val addressReads: play.api.libs.json.Reads[co.blocke.Address] = Json.reads[Address]
implicit val personReads: play.api.libs.json.Reads[co.blocke.Person] = Json.reads[Person]
implicit val recordReads: play.api.libs.json.Reads[co.blocke.Record] = Json.reads[Record]

trait PlayWritingBenchmark {
@Benchmark
def writeRecordPlay = Json.toJson(record)
}

// val playJS = Json.toJson(record)
trait PlayReadingBenchmark {
@Benchmark
def readRecordPlay = Json.fromJson[Record](Json.parse(jsData)) //Json.fromJson[Record](playJS)
}
Loading

0 comments on commit ee5da35

Please sign in to comment.