3

Scala 2.12 can automatically convert a lambda expression to an interface. E.g, I'm using:

import org.apache.kafka.common.serialization.{Deserializer, Serde, Serializer}
import scalapb.GeneratedMessageCompanion

class ProtoSerde[A <: scalapb.GeneratedMessage](implicit companion: GeneratedMessageCompanion[A])
  extends Serde[A] {
  override def serializer(): Serializer[A] = (_: String, data: A) => data.toByteArray

  override def deserializer(): Deserializer[A] = (_: String, data: Array[Byte]) => companion.parseFrom(data)
}

Will it automatically cache the instance of the created objects, so that a new object isn't allocated on every call. I.e. I want:

import org.apache.kafka.common.serialization.{Deserializer, Serde, Serializer}
import scalapb.GeneratedMessageCompanion

class ProtoSerde[A <: scalapb.GeneratedMessage](implicit companion: GeneratedMessageCompanion[A])
  extends Serde[A] {
  lazy val _serializer: Serializer[A] = (_: String, data: A) => data.toByteArray
  lazy val _deserializer: Deserializer[A] = (_: String, data: Array[Byte]) => companion.parseFrom(data)

  override def serializer(): Serializer[A] = _serializer

  override def deserializer(): Deserializer[A] = _deserializer
}

Will the compiler perform this optimization automatically, or do I have to do it myself?

Krzysztof Atłasik
  • 21,985
  • 6
  • 54
  • 76
Yair Halberstadt
  • 5,733
  • 28
  • 60

3 Answers3

3

def is not memoized to calling it will recompute value every time. It might be later optimized by JIT compiler.

But there's a simpler solution to make properties of class memoized. Scala allows changing def to val while you override parent methods. It is also possible to add lazy modifier on extending class:

class ProtoSerde[A <: scalapb.GeneratedMessage](implicit companion: GeneratedMessageCompanion[A])
  extends Serde[A] {
  override lazy val serializer: Serializer[A] = (_: String, data: A) => data.toByteArray

  override lazy val deserializer: Deserializer[A] = (_: String, data: Array[Byte]) => companion.parseFrom(data)
}
Krzysztof Atłasik
  • 21,985
  • 6
  • 54
  • 76
2

Compiler doesn't perform such optimizations. Conversion lambda expression to an interface doesn't mean saving instance of that interface as a singleton or any another way to save that. It will create this instance every time when you call this lambda. You need to do this optimization by yourself.

Boris Azanov
  • 4,408
  • 1
  • 15
  • 28
  • It is more complicated than this. In the end it is an implementation detail but not in all cases a class is even created and even when, it is possibly cached. See Translation of Lambda Expressions by Brian Goetz: http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html – michid May 24 '20 at 10:09
2

Perhaps it is best to benchmark in your particular circumstances. For example, jmh benchmark of the following simplified snippet

trait Quxable[A] {
  def qux(x: A): A
}

class FooImpl1 {
  def zar(): Quxable[Int] = (x: Int) => x + 1
}

class FooImpl2 {
  val _zar: Quxable[Int] = (x: Int) => x + 1
  def zar(): Quxable[Int] = _zar
}

// sbt "jmh:run -i 5 -wi 5 -f 2 -t 1 -prof gc bench.So61983239"

@State(Scope.Benchmark)
@BenchmarkMode(Array(Mode.Throughput))
class So61983239 {
  val end = 1000000
  val fooImpl1 = new FooImpl1
  val fooImpl2 = new FooImpl2

  @Benchmark def impl1 = (1 to end) map (x => fooImpl1.zar().qux(x))
  @Benchmark def impl2 = (1 to end) map (x => fooImpl2.zar().qux(x))
}

gives similar allocation rates (gc.alloc.rate) for both

[info] Benchmark                                           Mode  Cnt         Score         Error   Units
[info] So61983239.impl1                                   thrpt   10        76.866 ±       2.690   ops/s
[info] So61983239.impl1:·gc.alloc.rate                    thrpt   10      4790.899 ±     167.414  MB/sec
[info] So61983239.impl1:·gc.alloc.rate.norm               thrpt   10  68637392.621 ±       0.152    B/op
[info] So61983239.impl1:·gc.churn.PS_Eden_Space           thrpt   10      4793.715 ±     174.955  MB/sec
[info] So61983239.impl1:·gc.churn.PS_Eden_Space.norm      thrpt   10  68686591.527 ± 1866568.380    B/op
[info] So61983239.impl1:·gc.churn.PS_Survivor_Space       thrpt   10         6.216 ±       1.411  MB/sec
[info] So61983239.impl1:·gc.churn.PS_Survivor_Space.norm  thrpt   10     89091.175 ±   20368.810    B/op
[info] So61983239.impl1:·gc.count                         thrpt   10       234.000                counts
[info] So61983239.impl1:·gc.time                          thrpt   10      1055.000                    ms
[info] So61983239.impl2                                   thrpt   10        78.027 ±       0.889   ops/s
[info] So61983239.impl2:·gc.alloc.rate                    thrpt   10      4862.226 ±      54.507  MB/sec
[info] So61983239.impl2:·gc.alloc.rate.norm               thrpt   10  68637392.613 ±       0.162    B/op
[info] So61983239.impl2:·gc.churn.PS_Eden_Space           thrpt   10      4895.604 ±     148.716  MB/sec
[info] So61983239.impl2:·gc.churn.PS_Eden_Space.norm      thrpt   10  69105653.917 ± 1668136.154    B/op
[info] So61983239.impl2:·gc.churn.PS_Survivor_Space       thrpt   10         5.582 ±       2.634  MB/sec
[info] So61983239.impl2:·gc.churn.PS_Survivor_Space.norm  thrpt   10     78851.978 ±   37413.327    B/op
[info] So61983239.impl2:·gc.count                         thrpt   10       230.000                counts
[info] So61983239.impl2:·gc.time                          thrpt   10      1073.000                    ms
Mario Galic
  • 47,285
  • 6
  • 56
  • 98