From 0908a9c52f5a0c3375f0867ce384775bb219bc56 Mon Sep 17 00:00:00 2001 From: Jules Ivanic Date: Fri, 16 Aug 2024 20:49:44 +1000 Subject: [PATCH 1/4] Optimise `HttpContentCodec#lookup` --- .../src/main/scala/zio/http/codec/HttpContentCodec.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala b/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala index 88eeac74cf..6a91777c3a 100644 --- a/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala +++ b/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala @@ -139,9 +139,9 @@ final case class HttpContentCodec[A]( } def lookup(mediaType: MediaType): Option[BinaryCodecWithSchema[A]] = { - if (lookupCache.contains(mediaType)) { - lookupCache(mediaType) - } else { + val codec = lookupCache.getOrElse(mediaType, null) + if (codec ne null) codec + else { val codec = choices.collectFirst { case (mt, codec) if mt.matches(mediaType) => codec } lookupCache = lookupCache + (mediaType -> codec) codec From a2ce944098cb5742e81b901c622c02f059c247e4 Mon Sep 17 00:00:00 2001 From: Jules Ivanic Date: Fri, 16 Aug 2024 21:14:15 +1000 Subject: [PATCH 2/4] Prefer `HashMap` over `Map` --- .../src/main/scala/zio/http/codec/HttpContentCodec.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala b/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala index 6a91777c3a..084071c659 100644 --- a/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala +++ b/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala @@ -1,6 +1,6 @@ package zio.http.codec -import scala.collection.immutable.ListMap +import scala.collection.immutable.{HashMap, ListMap} import zio._ @@ -19,7 +19,11 @@ final case class HttpContentCodec[A]( choices: ListMap[MediaType, BinaryCodecWithSchema[A]], ) { self => - private var lookupCache: Map[MediaType, Option[BinaryCodecWithSchema[A]]] = Map.empty + /** + * Uses an `HashMap` instead of a `Map` to avoid allocation of an Option when + * calling `.getOrElse` + */ + private var lookupCache: HashMap[MediaType, Option[BinaryCodecWithSchema[A]]] = HashMap.empty /** * A right biased merge of two HttpContentCodecs. From 21db3b17b25cac932b6c19ab0f7b88be244ac37d Mon Sep 17 00:00:00 2001 From: Jules Ivanic Date: Thu, 22 Aug 2024 19:36:27 +1000 Subject: [PATCH 3/4] Implement Kyri suggestion See https://github.com/zio/zio-http/pull/3024#issuecomment-2294731892 --- .../src/main/scala-2/zio/http/syntax.scala | 12 ++++++++++++ .../src/main/scala-3/zio/http/syntax.scala | 17 +++++++++++++++++ .../scala/zio/http/codec/HttpContentCodec.scala | 10 ++++------ 3 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 zio-http/shared/src/main/scala-2/zio/http/syntax.scala create mode 100644 zio-http/shared/src/main/scala-3/zio/http/syntax.scala diff --git a/zio-http/shared/src/main/scala-2/zio/http/syntax.scala b/zio-http/shared/src/main/scala-2/zio/http/syntax.scala new file mode 100644 index 0000000000..f98c2ee9a1 --- /dev/null +++ b/zio-http/shared/src/main/scala-2/zio/http/syntax.scala @@ -0,0 +1,12 @@ +package zio.http + +/** + * Copied from: https://github.com/ghostdogpr/caliban/pull/2366 + */ +private[http] object syntax { + val NullFn: () => AnyRef = () => null + + implicit final class EnrichedImmutableMapOps[K, V <: AnyRef](private val self: Map[K, V]) extends AnyVal { + def getOrElseNull(key: K): V = self.getOrElse(key, NullFn()).asInstanceOf[V] + } +} \ No newline at end of file diff --git a/zio-http/shared/src/main/scala-3/zio/http/syntax.scala b/zio-http/shared/src/main/scala-3/zio/http/syntax.scala new file mode 100644 index 0000000000..018f037832 --- /dev/null +++ b/zio-http/shared/src/main/scala-3/zio/http/syntax.scala @@ -0,0 +1,17 @@ +package zio.http + +import scala.annotation.static + +/** + * Copied from: https://github.com/ghostdogpr/caliban/pull/2366 + */ +private[http] object syntax { + @static val NullFn: () => AnyRef = () => null + + extension [K, V <: AnyRef](inline map: Map[K, V]) { + transparent inline def getOrElseNull(key: K): V = map.getOrElse(key, NullFn()).asInstanceOf[V] + } +} + +// Required for @static fields +private final class syntax private diff --git a/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala b/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala index 084071c659..ce3c979519 100644 --- a/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala +++ b/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala @@ -19,11 +19,7 @@ final case class HttpContentCodec[A]( choices: ListMap[MediaType, BinaryCodecWithSchema[A]], ) { self => - /** - * Uses an `HashMap` instead of a `Map` to avoid allocation of an Option when - * calling `.getOrElse` - */ - private var lookupCache: HashMap[MediaType, Option[BinaryCodecWithSchema[A]]] = HashMap.empty + private var lookupCache: Map[MediaType, Option[BinaryCodecWithSchema[A]]] = Map.empty /** * A right biased merge of two HttpContentCodecs. @@ -143,7 +139,9 @@ final case class HttpContentCodec[A]( } def lookup(mediaType: MediaType): Option[BinaryCodecWithSchema[A]] = { - val codec = lookupCache.getOrElse(mediaType, null) + import zio.http.syntax._ + + val codec = lookupCache.getOrElseNull(mediaType) if (codec ne null) codec else { val codec = choices.collectFirst { case (mt, codec) if mt.matches(mediaType) => codec } From 8546913203354be369b2572fc9d9adf94c62ab2d Mon Sep 17 00:00:00 2001 From: Jules Ivanic Date: Fri, 23 Aug 2024 10:07:19 +1000 Subject: [PATCH 4/4] fmt --- zio-http/shared/src/main/scala-2/zio/http/syntax.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zio-http/shared/src/main/scala-2/zio/http/syntax.scala b/zio-http/shared/src/main/scala-2/zio/http/syntax.scala index f98c2ee9a1..fe56ec21ad 100644 --- a/zio-http/shared/src/main/scala-2/zio/http/syntax.scala +++ b/zio-http/shared/src/main/scala-2/zio/http/syntax.scala @@ -9,4 +9,4 @@ private[http] object syntax { implicit final class EnrichedImmutableMapOps[K, V <: AnyRef](private val self: Map[K, V]) extends AnyVal { def getOrElseNull(key: K): V = self.getOrElse(key, NullFn()).asInstanceOf[V] } -} \ No newline at end of file +}