【问题标题】:Cost of implicit conversion from java to scala collections从 java 到 scala 集合的隐式转换成本
【发布时间】:2015-10-28 01:59:11
【问题描述】:

我想知道从 java 集合到 scala 的隐式转换的成本。在this doc 中有几个隐式的双向转换,据说“从源类型转换为目标类型并再次返回将返回原始源对象”

我的结论是成本应该很小(包装),但仍然是多少?

我问这个问题是因为我在一些 scala 代码中使用了 java 集,当我导入 asScalaSet 时,它被隐式转换为 scala 集(我在某些地方确实需要它)。但是,对于极少的访问器(例如size())来说,这可能是一个随之而来的开销

有人知道吗?

【问题讨论】:

  • 我不知道编译器有多聪明,但似乎在很多情况下它应该能够避免分配包装类,像size 这样的内联调用,并且不会产生开销?
  • 这不是您问题的答案,但通常不推荐使用“魔术”JavaConversions,而倾向于使用更明确的JavaConverters,这需要您编写 .asScala 或 .asJava 进行转换明确的。
  • 好消息@RüdigerKlaehn。这确实很有用,因此至少我可以控制何时发生转换。并且来自python背景,我只能同意这一点(“显式优于隐式” - python的禅宗

标签: java scala performance collections type-conversion


【解决方案1】:

这是使用 Scala 2.13.1 CollectionConverters 的 jmh 基准测试结果

import org.openjdk.jmh.annotations._
import scala.jdk.CollectionConverters._
import java.{util => ju}

@State(Scope.Benchmark)
@BenchmarkMode(Array(Mode.Throughput))
class So31830028 {
  val size = 1000000
  val scalaSet: Set[Int] = (1 to size).toSet
  val javaSet: ju.Set[Int]  = (1 to size).toSet.asJava

  @Benchmark def scala = scalaSet.size
  @Benchmark def scalaAsJava = scalaSet.asJava.size
  @Benchmark def java = javaSet.size
  @Benchmark def javaAsScala = javaSet.asScala.size
}

sbt "jmh:run -i 10 -wi 5 -f 2 -t 1 bench.So31830028" 给出的地方

[info] Benchmark                     Mode  Cnt          Score          Error   Units
[info] So31830028.java              thrpt   20  356515729.840 ± 64691657.672   ops/s
[info] So31830028.javaAsScala       thrpt   20  270053471.338 ± 36854051.611   ops/s
[info] So31830028.scala             thrpt   20  448415156.726 ± 53674976.259   ops/s
[info] So31830028.scalaAsJava       thrpt   20  211808793.234 ± 57898858.737   ops/s

确实似乎有相当大的成本。

【讨论】:

  • 您的基准函数创建了集合,因此您对 [creation+wrapping] 进行了基准测试,显然创建比包装要长得多。您能否在之前创建一次初始集:class So318{ val set = (1 to 1000000).toSet; val javaSet = set.asJava; def scala = set.size; def scalaAsJava = javaSet.size }.
  • 另外,请注意我的问题是相反的:在 java 集上使用 scala 包装器的成本,而不是直接在 java 集上。
  • @Juh_ 确实你是对的。感谢您指出错误!请查看编辑后的答案以获取更新的结果。
【解决方案2】:

我决定从实际的角度回答你的问题。我使用以下简单的 JMH 基准测试了原始 scala 集合的每秒操作数并转换了一个(使用隐式转换)。

请在下面找到基准代码:

import org.openjdk.jmh.annotations._

import scala.collection.JavaConversions._

@State(Scope.Thread)
class WrapperBenchmark {

  val unwrappedCollection = (1 to 100).toSet
  val wrappedCollection: java.util.Set[Int] = (1 to 100).toSet[Int]

  @Benchmark
  def measureUnwrapped: Int = unwrappedCollection.size

  @Benchmark
  def measureWrapped: Int = wrappedCollection.size()
}

我使用 sbt 和 sbt-jmh 插件运行。请在下面找到结果:

[info] Benchmark                           Mode  Cnt          Score         Error  Units
[info] WrapperBenchmark.measureUnwrapped  thrpt  200  353214968.844 ± 1534779.932  ops/s
[info] WrapperBenchmark.measureWrapped    thrpt  200  284669396.241 ± 4223983.126  ops/s

所以基本上根据结果,确实有开销。我将尝试继续我的研究,并在稍后更新此问题时提供原因。

如果您希望我分享完整的 sbt 项目以供您未来的研究使用,请告诉我。

【讨论】:

  • Tx 用于鱼和鱼竿 :-) 我现在不会对此进行更多研究,但您给了我工具来回答我可能遇到的类似问题。随意分享这样一个用于简单功能基准测试的 sbt 项目。
猜你喜欢
  • 1970-01-01
  • 2011-01-10
  • 2011-06-08
  • 2016-08-18
  • 1970-01-01
  • 2010-10-15
  • 2018-09-25
  • 1970-01-01
相关资源
最近更新 更多