Scala缓存是否转换为功能接口
Does scala cache conversions to functional interfaces
Scala 2.12 可以自动将 lambda 表达式转换为接口。例如,我正在使用:
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)
}
它是否会自动缓存创建对象的实例,以便不会在每次调用时都分配一个新对象。 IE。我要:
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
}
编译器会自动执行此优化,还是我必须自己执行?
编译器不执行此类优化。将 lambda 表达式转换为接口并不意味着将该接口的实例保存为单例或任何其他保存方式。每次调用此 lambda 时,它都会创建此实例。你需要自己做这个优化。
def
没有记住调用它每次都会重新计算值。稍后可能会被 JIT 编译器优化。
但是有一个更简单的解决方案可以使 class 的属性记忆化。 Scala 允许在覆盖父方法时将 def
更改为 val
。也可以在扩展 class:
时添加 lazy
修饰符
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)
}
也许最好在您的特定情况下进行基准测试。例如,以下简化代码段的 jmh 基准测试
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))
}
两者的分配率相似 (gc.alloc.rate
)
[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
Scala 2.12 可以自动将 lambda 表达式转换为接口。例如,我正在使用:
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)
}
它是否会自动缓存创建对象的实例,以便不会在每次调用时都分配一个新对象。 IE。我要:
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
}
编译器会自动执行此优化,还是我必须自己执行?
编译器不执行此类优化。将 lambda 表达式转换为接口并不意味着将该接口的实例保存为单例或任何其他保存方式。每次调用此 lambda 时,它都会创建此实例。你需要自己做这个优化。
def
没有记住调用它每次都会重新计算值。稍后可能会被 JIT 编译器优化。
但是有一个更简单的解决方案可以使 class 的属性记忆化。 Scala 允许在覆盖父方法时将 def
更改为 val
。也可以在扩展 class:
lazy
修饰符
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)
}
也许最好在您的特定情况下进行基准测试。例如,以下简化代码段的 jmh 基准测试
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))
}
两者的分配率相似 (gc.alloc.rate
)
[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