Cost of implicit conversion from java to scala col

2020-07-18 03:02发布

问题:

I'd like to know the cost of implicit conversion from java collection to scala ones. In this doc there are several implicit two-way conversions for which it is said that "converting from a source type to a target type and back again will return the original source object".

I conclude that the cost should be minor (wrapping), but still how much is it?

I ask this question because I use java sets in some scala code, which is implicitly converted to scala set as I import asScalaSet (I do need it in some places). However, it might be a consequent overhead for very little accessors such as size()

Does anyone know?

回答1:

I decided to answer your question from practical point of view. I used the following simple JMH benchmarks to test operations per second for original scala collection and converted one (using implicit conversion).

Please find below code of benchmark:

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()
}

I used sbt and sbt-jmh plugin for running. Please find results below:

[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

So basically according to results, there is overhead indeed. I will try to continue my research providing the reason why it is like this in later update to this question.

Please let me know if you want me to share complete sbt project for your future research.



回答2:

Here are jmh benchmark results using Scala 2.13.1 CollectionConverters

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
}

where sbt "jmh:run -i 10 -wi 5 -f 2 -t 1 bench.So31830028" gives

[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

Indeed there seems to be a rather significant cost.