Skip to content

Commit c7fcf21

Browse files
authored
Merge pull request #46 from note/performance-improvements
Performance improvements
2 parents 22eda93 + a7a23d3 commit c7fcf21

File tree

9 files changed

+148
-19
lines changed

9 files changed

+148
-19
lines changed

build.sbt

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,18 @@ lazy val dhalljMagnolia = (projectMatrix in file("modules/dhallj-magnolia"))
1616
.settings(commonSettings("dhallj-magnolia"))
1717
.settings(
1818
libraryDependencies ++= Seq(
19-
"org.dhallj" %% "dhall-scala-codec" % "0.10.0-M2",
20-
"org.scalameta" %% "munit" % "0.7.27" % Test,
21-
"org.dhallj" %% "dhall-javagen" % "0.10.0-M2" % Test
19+
"org.dhallj" %% "dhall-scala-codec" % "0.10.0-M2",
20+
"org.scalameta" %% "munit" % "0.7.27" % Test,
21+
"org.dhallj" %% "dhall-javagen" % "0.10.0-M2" % Test
2222
),
2323
libraryDependencies ++= {
2424
CrossVersion.partialVersion(scalaVersion.value) match {
2525
case Some((3, _)) =>
2626
Seq("com.softwaremill.magnolia1_3" %% "magnolia" % "1.2.7")
2727
case _ =>
2828
Seq(
29-
"com.softwaremill.magnolia1_2" %% "magnolia" % "1.1.3",
30-
"org.scala-lang" % "scala-reflect" % scala2Version % Provided,
29+
"com.softwaremill.magnolia1_2" %% "magnolia" % "1.1.3",
30+
"org.scala-lang" % "scala-reflect" % scala2Version % Provided,
3131
)
3232
}
3333
},
@@ -41,3 +41,12 @@ lazy val dhalljMagnolia = (projectMatrix in file("modules/dhallj-magnolia"))
4141
}
4242
)
4343
.jvmPlatform(scalaVersions = Seq(scala2Version, scala3Version))
44+
45+
lazy val benchmark = (projectMatrix in file("modules/benchmark"))
46+
.settings(commonSettings("benchmark"))
47+
.settings(
48+
scalaVersion := scala3Version
49+
)
50+
.enablePlugins(JmhPlugin)
51+
.dependsOn(dhalljMagnolia)
52+
.jvmPlatform(scalaVersions = Seq(scala3Version))
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package pl.msitko.dhallj.generic.decoder
2+
3+
import org.dhallj.syntax._
4+
import org.dhallj.codec.syntax._
5+
import org.openjdk.jmh.annotations._
6+
import org.openjdk.jmh.infra.Blackhole
7+
8+
import java.util.concurrent.TimeUnit
9+
10+
object BenchmarkInput {
11+
12+
val sumDecoder = pl.msitko.dhallj.generic.decoder.semiauto.deriveDecoder[TestSum]
13+
14+
val dhallInput =
15+
"<Use : {} | Tip : {} | Poem : {} | Weigh : {} | Foreign : {} | Discussion : {} | Badly : {} | Left : {} | Bound : {} | Result : {} | Bus : {} | Bark : {} | Buy : {} | Fight : {} | Strip : {} | Experience : {} | Proper : {} | Was : {}>.Experience {=}"
16+
17+
val parsed = dhallInput.parseExpr.getOrElse(throw new RuntimeException("can't be parsed as dhall")).normalize()
18+
}
19+
20+
// benchmark3/Jmh/run -i 3 -wi 3 -f1 -t1 -prof jfr pl.msitko.dhallj.generic.decoder.DecoderBench.*
21+
// benchmark3/Jmh/run -i 3 -wi 3 -f1 -t1 pl.msitko.dhallj.generic.decoder.DecoderBench.*
22+
@BenchmarkMode(Array(Mode.AverageTime))
23+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
24+
@State(Scope.Thread)
25+
class DecoderBench {
26+
import BenchmarkInput.*
27+
28+
@Benchmark
29+
def decode(bh: Blackhole): Unit =
30+
bh.consume(parsed.as[TestSum](sumDecoder))
31+
32+
}
33+
34+
sealed trait TestSum
35+
case object Left extends TestSum
36+
case object Weigh extends TestSum
37+
case object Buy extends TestSum
38+
case object Was extends TestSum
39+
case object Strip extends TestSum
40+
case object Foreign extends TestSum
41+
case object Bark extends TestSum
42+
case object Result extends TestSum
43+
case object Discussion extends TestSum
44+
case object Bus extends TestSum
45+
case object Proper extends TestSum
46+
case object Poem extends TestSum
47+
case object Use extends TestSum
48+
case object Bound extends TestSum
49+
case object Fight extends TestSum
50+
case object Badly extends TestSum
51+
case object Tip extends TestSum
52+
case object Experience extends TestSum

modules/dhallj-magnolia/src/main/scala-2/pl/msitko/dhallj/generic/GenericDecoder.scala

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import cats.Traverse
44
import cats.implicits.catsSyntaxEitherId
55
import cats.instances.either._
66
import cats.instances.list._
7+
78
import magnolia1._
89
import org.dhallj.ast._
910
import org.dhallj.codec.Decoder.Result
@@ -39,7 +40,7 @@ private[generic] object GenericDecoder {
3940
case RecordLiteral(recordMap) =>
4041
decodeAs(expr, recordMap)
4142

42-
case FieldAccess(UnionType(_), _) =>
43+
case FieldAccess(Extractors.IsUnionType(_), _) =>
4344
decodeAs(expr, Map.empty)
4445

4546
case other =>
@@ -69,14 +70,23 @@ private[generic] object GenericDecoder {
6970
}
7071

7172
override def decode(expr: Expr): Result[T] = expr match {
72-
case Application(FieldAccess(UnionType(_), t), arg) =>
73+
case Application(FieldAccess(Extractors.IsUnionType(_), t), arg) =>
7374
decodeAs(arg, t)
74-
case FieldAccess(UnionType(_), t) =>
75+
case FieldAccess(Extractors.IsUnionType(_), t) =>
7576
decodeAs(expr, t)
7677
case unexpected =>
7778
new DecodingFailure(s"${unexpected} is not a union", expr).asLeft
7879
}
7980

81+
// override def decode(expr: Expr): Result[T] = expr match {
82+
// case Application(FieldAccess(UnionType(_), t), arg) =>
83+
// decodeAs(arg, t)
84+
// case FieldAccess(UnionType(_), t) =>
85+
// decodeAs(expr, t)
86+
// case unexpected =>
87+
// new DecodingFailure(s"${unexpected} is not a union", expr).asLeft
88+
// }
89+
8090
override def isValidType(typeExpr: Expr): Boolean = true
8191

8292
override def isExactType(typeExpr: Expr): Boolean = false
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package pl.msitko.dhallj.generic
2+
3+
import org.dhallj.codec.DecodingFailure
4+
import org.dhallj.core.Expr
5+
6+
final case class MissingRecordField(override val target: String, missingFieldName: String, override val value: Expr)
7+
extends DecodingFailure(target, value):
8+
override def toString: String = s"Missing record field '$missingFieldName' when decoding $target"

modules/dhallj-magnolia/src/main/scala-3/pl/msitko/dhallj/generic/decoder/semiauto.scala

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,38 +7,38 @@ import org.dhallj.ast.*
77
import org.dhallj.codec.{Decoder, DecodingFailure, Encoder}
88
import org.dhallj.codec.Decoder.Result
99
import org.dhallj.core.Expr
10+
import pl.msitko.dhallj.generic.{Extractors, MissingRecordField}
1011
import scala.deriving.Mirror
1112

12-
final case class MissingRecordField(override val target: String, missingFieldName: String, override val value: Expr)
13-
extends DecodingFailure(target, value):
14-
override def toString: String = s"Missing record field '$missingFieldName' when decoding $target"
15-
1613
object semiauto:
1714

1815
inline def deriveDecoder[T](using m: Mirror.Of[T]): Decoder[T] =
1916
val derivation = new Derivation[Decoder]:
2017
override type Typeclass[T] = Decoder[T]
2118

2219
override def split[T](sealedTrait: SealedTrait[Decoder, T]): Decoder[T] =
20+
val typeClassByShortName = sealedTrait.subtypes.map(s => s.typeInfo.short -> s.typeclass).toMap
21+
2322
new Decoder[T]:
2423
private def decodeAs(expr: Expr, subtypeName: String) =
25-
sealedTrait.subtypes.find(_.typeInfo.short == subtypeName) match
26-
case Some(subtype) =>
27-
subtype.typeclass.decode(expr)
24+
typeClassByShortName.get(subtypeName) match
25+
case Some(typeclass) =>
26+
typeclass.decode(expr)
2827
case None =>
2928
DecodingFailure(s"$subtypeName is not a known subtype of ${sealedTrait.typeInfo.full}", expr).asLeft
3029

3130
override def decode(expr: Expr): Result[T] = expr match
32-
case Application(FieldAccess(UnionType(_), t), arg) =>
31+
case Application(FieldAccess(Extractors.IsUnionType(_), t), arg) =>
3332
decodeAs(arg, t)
34-
case FieldAccess(UnionType(_), t) =>
33+
case FieldAccess(Extractors.IsUnionType(_), t) =>
3534
decodeAs(expr, t)
3635
case unexpected =>
3736
DecodingFailure(s"${unexpected} is not a union", expr).asLeft
3837

3938
override def isValidType(typeExpr: Expr): Boolean = true
4039

4140
override def isExactType(typeExpr: Expr): Boolean = false
41+
end split
4242

4343
override def join[T](caseClass: CaseClass[Decoder, T]): Decoder[T] =
4444
new Decoder[T]:
@@ -59,7 +59,7 @@ object semiauto:
5959
override def decode(expr: Expr): Result[T] = expr match
6060
case RecordLiteral(recordMap) =>
6161
decodeAs(expr, recordMap)
62-
case FieldAccess(UnionType(_), _) =>
62+
case FieldAccess(Extractors.IsUnionType(_), _) =>
6363
decodeAs(expr, Map.empty)
6464
case other =>
6565
Left(DecodingFailure(caseClass.typeInfo.full, other))
@@ -73,6 +73,7 @@ object semiauto:
7373
false
7474

7575
override def isExactType(typeExpr: Expr): Boolean = false
76+
end join
7677

7778
derivation.derived
7879
end deriveDecoder

modules/dhallj-magnolia/src/main/scala-3/pl/msitko/dhallj/generic/encoder/semiauto.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package pl.msitko.dhallj.generic.encoder
22

33
import magnolia1.{CaseClass, Derivation, SealedTrait}
44
import org.dhallj.ast.{Application, FieldAccess, RecordLiteral, RecordType, UnionType}
5+
import org.dhallj.codec.Decoder.Result
56
import org.dhallj.codec.Encoder
67
import org.dhallj.core.Expr
78

89
import scala.deriving.Mirror
10+
import scala.jdk.CollectionConverters._
911

1012
object semiauto:
1113

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package pl.msitko.dhallj.generic
2+
3+
import org.dhallj.core.{Expr, ExternalVisitor}
4+
5+
import java.lang.{Iterable => JIterable}
6+
import java.util.{Map => JMap}
7+
8+
private[generic] object Extractors {
9+
10+
def unionExtractor = new ExternalVisitor.Constant[Option[JMap[String, Expr]]](None) {
11+
12+
override def onUnionType(fields: JIterable[JMap.Entry[String, Expr]], size: Int): Option[JMap[String, Expr]] = {
13+
val jmap = new java.util.HashMap[String, Expr]()
14+
fields.forEach(i => jmap.put(i.getKey, i.getValue))
15+
Some(jmap)
16+
}
17+
}
18+
19+
def fastUnionExtractor = new ExternalVisitor.Constant[Option[Unit]](None) {
20+
21+
override def onUnionType(fields: JIterable[JMap.Entry[String, Expr]], size: Int): Option[Unit] =
22+
Some(())
23+
}
24+
25+
object IsUnionType {
26+
27+
def unapply(expr: Expr): Option[Unit] =
28+
expr.accept(fastUnionExtractor).map(_ => ())
29+
}
30+
31+
}

modules/dhallj-magnolia/src/test/scala/pl/msitko/dhallj/generic/decoder/DecoderSpec.scala

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package pl.msitko.dhallj.generic.decoder
33
import org.dhallj.codec.{Decoder, Encoder}
44
import org.dhallj.codec.syntax._
55
import org.dhallj.syntax._
6+
import pl.msitko.dhallj.generic.MissingRecordField
67
import pl.msitko.dhallj.generic.example.akka.OnOrOff.Off
78
import pl.msitko.dhallj.generic.example.akka.{Akka, Http, OnOrOff, OnOrOff2, Preview, Server}
89
import pl.msitko.dhallj.generic.example.{AppConfig, DbConfig, Error, Error1, Error2, Errors, Fixtures, StatusCode}
@@ -29,7 +30,8 @@ trait DecoderSpec { self: munit.FunSuite with Fixtures =>
2930
| },
3031
| api1 = {
3132
| endpoint = {
32-
| host = "some.host"
33+
| host = "some.host",
34+
| ignoreThisField = "anything"
3335
| }
3436
| },
3537
| api2 = {
@@ -59,6 +61,19 @@ trait DecoderSpec { self: munit.FunSuite with Fixtures =>
5961
assertEquals(decoded, Errors(List(Error1("abc"), Error2(code = 123, code2 = 456))))
6062
}
6163

64+
test("Return MissingRecordField") {
65+
val in =
66+
"""
67+
|let Error = < Error1 : { msg : Text } | Error2 : { code : Natural, code2 : Natural } >
68+
|in { errors = [Error.Error1 { msg = "abc"}, Error.Error2 { code2 = 456 }] }
69+
|""".stripMargin
70+
71+
val res = in.parseExpr.getOr("Parsing failed").normalize().as[Errors]
72+
73+
val Left(mrf: MissingRecordField) = res
74+
assertEquals(mrf.missingFieldName, "code")
75+
}
76+
6277
test("Load union with empty parameter list as case object") {
6378
val decoded =
6479
"""

project/plugins.sbt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ val sbtSoftwareMillVer = "2.0.12"
22
addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-common" % sbtSoftwareMillVer)
33
addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-publish" % sbtSoftwareMillVer)
44
addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.9.0")
5+
addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.4")

0 commit comments

Comments
 (0)