Skip to content
This repository has been archived by the owner on Sep 27, 2022. It is now read-only.

Add jackson-cbor, jackson-smile, jsoniter-scala, and kryo-macros #8

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Expand Up @@ -3,3 +3,9 @@
A source code for the article "Scala Serialization" at [medium](https://medium.com/@dkomanov/scala-serialization-419d175c888a).

Recent charts for the article is at https://dkomanov.github.io/scala-serialization/.

To build and run benchmarks use the following command:

```sbt
sbt clean 'scala-serialization-test/jmh:run -prof gc -rf json -rff jmh-result.json .*'
```
57 changes: 57 additions & 0 deletions build.sbt
@@ -0,0 +1,57 @@
lazy val commonSettings = Seq(
organization := "com.komanov",
organizationHomepage := Some(url("https://komanov.com")),
homepage := Some(url("https://dkomanov.github.io/scala-serialization/")),
licenses := Seq(("MIT License", url("https://opensource.org/licenses/mit-license.html"))),
startYear := Some(2016),
scalaVersion := "2.11.12",
resolvers += Resolver.bintrayRepo("evolutiongaming", "maven"),
scalacOptions ++= Seq(
"-deprecation",
"-feature",
"-Xmax-classfile-name", "240",
//"-Xmacro-settings:print-serializers" //to log sources of serializaer which are generated by kryo-macros
//"-Xmacro-settings:print-codecs" //to log sources of codecs which are generated by jsoniter-scala
)
)

lazy val `scala-serialization-all` = project.in(file("."))
.aggregate(`scala-serialization`, `scala-serialization-test`)

lazy val `scala-serialization` = project
.settings(commonSettings: _*)
.settings(
fork in Test := true,
libraryDependencies ++= Seq(
"com.twitter" %% "util-core" % "18.3.0",
"commons-io" % "commons-io" % "2.6",
"com.evolutiongaming" %% "kryo-macros" % "1.1.8",
"com.github.plokhotnyuk.jsoniter-scala" %% "macros" % "0.21.3",
"com.fasterxml.jackson.core" % "jackson-databind" % "2.9.4",
"com.fasterxml.jackson.core" % "jackson-core" % "2.9.4",
"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.9.4",
"com.google.protobuf" % "protobuf-java" % "3.0.0",
"com.trueaccord.scalapb" %% "scalapb-runtime" % "0.5.45",
"org.scala-lang.modules" %% "scala-pickling" % "0.11.0-M2",
"io.suzaku" %% "boopickle" % "1.2.6",
"com.twitter" %% "chill" % "0.9.2",
"com.esotericsoftware" % "kryo-shaded" % "4.0.1", //for chill (should match with version for kryo-macros dependencies)
"org.apache.thrift" % "libthrift" % "0.11.0",
"org.slf4j" % "slf4j-simple" % "1.7.21", //for thrift
"com.twitter" %% "scrooge-core" % "18.3.0",
"org.specs2" %% "specs2-core" % "3.8.3" % Test,
"org.specs2" %% "specs2-matcher-extra" % "3.8.3" % Test,
"org.specs2" %% "specs2-mock" % "3.8.3" % Test,
"org.specs2" %% "specs2-junit" % "3.8.3" % Test
)
)

lazy val `scala-serialization-test` = project
.dependsOn(`scala-serialization`)
.enablePlugins(JmhPlugin)
.settings(commonSettings: _*)
.settings(
libraryDependencies ++= Seq(
"pl.project13.scala" % "sbt-jmh-extras" % "0.3.3"
)
)
5 changes: 5 additions & 0 deletions pom.xml
Expand Up @@ -26,6 +26,11 @@
<name>Scala-tools Maven2 Repository</name>
<url>http://scala-tools.org/repo-releases</url>
</repository>
<repository>
<id>bintray-evolutiongaming-maven</id>
<name>EvolutionGaming Bintray Repository</name>
<url>https://dl.bintray.com/evolutiongaming/maven</url>
</repository>
</repositories>

<pluginRepositories>
Expand Down
1 change: 1 addition & 0 deletions project/build.properties
@@ -0,0 +1 @@
sbt.version=1.1.1
2 changes: 2 additions & 0 deletions project/plugins.sbt
@@ -0,0 +1,2 @@
addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.3.3")
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.0")
Expand Up @@ -6,7 +6,7 @@ import com.komanov.serialization.converters._
import com.komanov.serialization.domain.{EventProcessor, Site, SiteEvent}
import org.openjdk.jmh.annotations._

@State(Scope.Benchmark)
@State(Scope.Thread) // Kryo modifies bytes during parsing, see: https://github.com/EsotericSoftware/kryo#threading
@BenchmarkMode(Array(Mode.AverageTime))
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 1, jvmArgs = Array("-Xmx2G"))
Expand Down Expand Up @@ -229,6 +229,10 @@ abstract class BenchmarkBase(converter: MyConverter) {

}

class KryoMacrosBenchmark extends BenchmarkBase(KryoMacrosConverter)

class JsoniterScalaBenchmark extends BenchmarkBase(JsoniterScalaConverter)

class JsonBenchmark extends BenchmarkBase(JsonConverter)

class ScalaPbBenchmark extends BenchmarkBase(ScalaPbConverter)
Expand Down
37 changes: 23 additions & 14 deletions scala-serialization/pom.xml
Expand Up @@ -18,63 +18,72 @@
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.11.7</version>
<version>2.11.12</version>
</dependency>
<dependency>
<groupId>com.twitter</groupId>
<artifactId>util-core_2.11</artifactId>
<version>6.34.0</version>
<version>18.3.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
<version>2.6</version>
</dependency>
<dependency>
<groupId>com.evolutiongaming</groupId>
<artifactId>kryo-macros_2.11</artifactId>
<version>1.1.8</version>
</dependency>
<dependency>
<groupId>com.github.plokhotnyuk.jsoniter-scala</groupId>
<artifactId>macros_2.11</artifactId>
<version>0.21.3</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.3</version>
<version>2.9.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.7.3</version>
<version>2.9.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-scala_2.11</artifactId>
<version>2.7.3</version>
<version>2.9.4</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.0.0-beta-2</version>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>com.trueaccord.scalapb</groupId>
<artifactId>scalapb-runtime_2.11</artifactId>
<version>0.5.31</version>
<version>0.5.45</version>
</dependency>
<dependency>
<groupId>org.scala-lang.modules</groupId>
<artifactId>scala-pickling_2.11</artifactId>
<version>0.11.0-M2</version>
</dependency>
<dependency>
<groupId>me.chrons</groupId>
<groupId>io.suzaku</groupId>
<artifactId>boopickle_2.11</artifactId>
<version>1.2.4</version>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>com.twitter</groupId>
<artifactId>chill_2.11</artifactId>
<version>0.8.0</version>
<version>0.9.2</version>
</dependency>
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.9.1</version>
<version>0.11.0</version>
</dependency>
<dependency>
<!-- for thrift -->
Expand All @@ -85,7 +94,7 @@
<dependency>
<groupId>com.twitter</groupId>
<artifactId>scrooge-core_2.11</artifactId>
<version>4.7.0</version>
<version>18.3.0</version>
</dependency>

<!-- test -->
Expand Down
@@ -0,0 +1,30 @@
package com.komanov.serialization.converters

import java.time.Instant

import com.github.plokhotnyuk.jsoniter_scala.core._
import com.github.plokhotnyuk.jsoniter_scala.macros._
import com.komanov.serialization.domain.{Site, SiteEvent}

/** https://github.com/plokhotnyuk/jsoniter-scala */
object JsoniterScalaConverter extends MyConverter {
private[this] val writerConfig = WriterConfig(preferredBufSize = 131072)
Copy link
Owner

Choose a reason for hiding this comment

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

Such size of a buffer seems like a hack. Other libraries do reallocations during serialization/deserialization.

Copy link
Author

Choose a reason for hiding this comment

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

It is not a hack, it is using a provided feature for a competitive advantage)

Copy link

Choose a reason for hiding this comment

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

@plokhotnyuk that's benchmarks-aware ad hocs
@dkomanov maybe you need to add 1Mb records ;)

Copy link
Author

Choose a reason for hiding this comment

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

Using of preallocated thread-local buffers with the capacity that is enough for expected workload is a normal practice for any parformance-aware parser/serializer.

BTW, jsoniter-scala is able to serialize huge messages efficiently using ~32K buffers in the writeToStream routine:
https://github.com/plokhotnyuk/jsoniter-scala/blob/master/jsoniter-scala-core/src/main/scala/com/github/plokhotnyuk/jsoniter_scala/core/package.scala#L190

private[this] val readerConfig = ReaderConfig(preferredBufSize = 131072, preferredCharBufSize = 131072)
private[this] implicit val instantCodec: JsonValueCodec[Instant] = new JsonValueCodec[Instant] {
override def nullValue: Instant = null

override def decodeValue(in: JsonReader, default: Instant): Instant = Instant.ofEpochMilli(in.readLong())

override def encodeValue(x: Instant, out: JsonWriter): Unit = out.writeVal(x.toEpochMilli)
}
private[this] implicit val siteCodec: JsonValueCodec[Site] = JsonCodecMaker.make[Site](CodecMakerConfig())
private[this] implicit val siteEventCodec: JsonValueCodec[SiteEvent] = JsonCodecMaker.make[SiteEvent](CodecMakerConfig())

def toByteArray(site: Site): Array[Byte] = writeToArray(site, writerConfig)

def fromByteArray(bytes: Array[Byte]): Site = readFromArray[Site](bytes, readerConfig)

def toByteArray(event: SiteEvent): Array[Byte] = writeToArray(event, writerConfig)

def siteEventFromByteArray(clazz: Class[_], bytes: Array[Byte]): SiteEvent = readFromArray[SiteEvent](bytes, readerConfig)
}
@@ -0,0 +1,81 @@
package com.komanov.serialization.converters

import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.io.{FastInput, FastOutput}
import com.evolutiongaming.kryo.Serializer
import com.komanov.serialization.domain._

/** https://github.com/evolution-gaming/kryo-macros */
case class KryoMacrosConverter(kryo: Kryo, in: FastInput, out: FastOutput) {
def read[A <: AnyRef](bs: Array[Byte])(implicit s: com.esotericsoftware.kryo.Serializer[A], m: Manifest[A]): A = {
in.setBuffer(bs)
kryo.readObject(in, m.runtimeClass.asInstanceOf[Class[A]], s)
}

def write[A <: AnyRef](a: A)(implicit s: com.esotericsoftware.kryo.Serializer[A]): Array[Byte] = {
out.clear()
kryo.writeObject(out, a, s)
out.toBytes
}
}

object KryoMacrosConverter extends MyConverter {
private[this] val pool = new ThreadLocal[KryoMacrosConverter] {
override def initialValue(): KryoMacrosConverter =
KryoMacrosConverter(new Kryo, new FastInput(), new FastOutput(131072, 131072))
}
private[this] implicit val domainSerializer = Serializer.make[Domain]
private[this] implicit val metaTagSerializer = Serializer.make[MetaTag]
private[this] implicit val entryPointSerializer = Serializer.makeCommon[EntryPoint] {
case 0 => Serializer.inner[DomainEntryPoint]
case 1 => Serializer.inner[FreeEntryPoint]
}
private[this] implicit val pageComponentDataSerializer = Serializer.makeCommon[PageComponentData] {
case 0 => Serializer.inner[TextComponentData]
case 1 => Serializer.inner[ButtonComponentData]
case 2 => Serializer.inner[BlogComponentData]
}
private[this] implicit val pageComponentPositionSerializer = Serializer.make[PageComponentPosition]
private[this] implicit val pageComponentSerializer = Serializer.make[PageComponent]
private[this] implicit val pageSerializer = Serializer.make[Page]
private[this] implicit val siteSerializer = Serializer.make[Site]
private[this] implicit val siteEventSerializer = Serializer.makeCommon[SiteEvent] {
case 0 => Serializer.inner[SiteCreated]
case 1 => Serializer.inner[SiteNameSet]
case 2 => Serializer.inner[SiteDescriptionSet]
case 3 => Serializer.inner[SiteRevisionSet]
case 4 => Serializer.inner[SitePublished]
case 5 => Serializer.inner[SiteUnpublished]
case 6 => Serializer.inner[SiteFlagAdded]
case 7 => Serializer.inner[SiteFlagRemoved]
case 8 => Serializer.inner[DomainAdded]
case 9 => Serializer.inner[DomainRemoved]
case 10 => Serializer.inner[PrimaryDomainSet]
case 11 => Serializer.inner[DefaultMetaTagAdded]
case 12 => Serializer.inner[DefaultMetaTagRemoved]
case 13 => Serializer.inner[PageAdded]
case 14 => Serializer.inner[PageRemoved]
case 15 => Serializer.inner[PageNameSet]
case 16 => Serializer.inner[PageMetaTagAdded]
case 17 => Serializer.inner[PageMetaTagRemoved]
case 18 => Serializer.inner[PageComponentAdded]
case 19 => Serializer.inner[PageComponentRemoved]
case 20 => Serializer.inner[PageComponentPositionSet]
case 21 => Serializer.inner[PageComponentPositionReset]
case 22 => Serializer.inner[TextComponentDataSet]
case 23 => Serializer.inner[ButtonComponentDataSet]
case 24 => Serializer.inner[BlogComponentDataSet]
case 25 => Serializer.inner[DomainEntryPointAdded]
case 26 => Serializer.inner[FreeEntryPointAdded]
case 27 => Serializer.inner[EntryPointRemoved]
case 28 => Serializer.inner[PrimaryEntryPointSet]
}

def toByteArray(site: Site): Array[Byte] = pool.get().write(site)

def fromByteArray(bytes: Array[Byte]): Site = pool.get().read[Site](bytes)

def toByteArray(event: SiteEvent): Array[Byte] = pool.get().write(event)

def siteEventFromByteArray(clazz: Class[_], bytes: Array[Byte]): SiteEvent = pool.get().read[SiteEvent](bytes)
}
Expand Up @@ -3,6 +3,8 @@ package com.komanov.serialization.converters
object Converters {

val all: Seq[(String, MyConverter)] = Seq(
"KryoMacros" -> KryoMacrosConverter,
"JsoniterScala" -> JsoniterScalaConverter,
"JSON" -> JsonConverter,
"ScalaPB" -> ScalaPbConverter,
"Java PB" -> JavaPbConverter,
Expand Down
Expand Up @@ -12,6 +12,8 @@ class SerializationTest extends SpecificationWithJUnit {

sequential

doTest("KryoMacros", KryoMacrosConverter)
doTest("JsoniterScala", JsoniterScalaConverter)
doTest("JSON", JsonConverter)
doTest("ScalaPB", ScalaPbConverter)
doTest("Java Protobuf", JavaPbConverter)
Expand Down
1 change: 1 addition & 0 deletions version.sbt
@@ -0,0 +1 @@
version in ThisBuild := "1.0-SNAPSHOT"