28

可以做些什么呢?

我已经运行了一些测试,似乎 Scala Hashmap 比 Java HashMap 慢得多。请证明我错了!

对我来说,Hashmap 的重点是快速访问给定键的值。因此,当速度很重要时,我发现自己求助于使用 Java HashMap,这有点令人难过。我没有足够的经验可以肯定地说,但似乎你将 Java 和 Scala 混合得越多,你可能面临的问题就越多。

test("that scala hashmap is slower than java") {
    val javaMap = new util.HashMap[Int,Int](){
      for (i <- 1 to 20)
      put(i,i+1)
    }

    import collection.JavaConverters._
    val scalaMap = javaMap.asScala.toMap

    // check is a scala hashmap
    assert(scalaMap.getClass.getSuperclass === classOf[scala.collection.immutable.HashMap[Int,Int]])

    def slow = {
      val start = System.nanoTime()
      for (i <- 1 to 1000) {
        for (i <- 1 to 20) {
          scalaMap(i)
        }
      }
      System.nanoTime() - start
    }

    def fast = {
      val start = System.nanoTime()
      for (i <- 1 to 1000) {
        for (i <- 1 to 20) {
          javaMap.get(i)
        }
      }
      System.nanoTime() - start
    }

    val elapses: IndexedSeq[(Long, Long)] = {
      (1 to 1000).map({_ => (slow,fast)})
    }

    var elapsedSlow = 0L
    var elapsedFast = 0L
    for ((eSlow,eFast) <- elapses) {
      elapsedSlow += eSlow
      elapsedFast += eFast
    }

    assert(elapsedSlow > elapsedFast)

    val fraction : Double = elapsedFast.toDouble/elapsedSlow
    println(s"slower by factor of: $fraction")
}

我错过了什么吗?

答案摘要

截至目前,当比较 Java 8 和 Scala 2.11 时,Java HashMap 似乎在查找(对于少量键的情况下)比 Scala 产品更快——除了 LongMap(如果您的键是 Ints/Longs)。

性能差异并没有那么大,以至于在大多数用例中都很重要。希望 Scala 将提高他们的地图的速度。同时,如果您需要性能(使用非整数键),请使用 Java。

整数键,n=20
Long(60),Java(93),Open(170),MutableSc(243),ImmutableSc(317)

案例对象键,n=20
Java(195),AnyRef(230)

4

3 回答 3

32

首先:使用 nanoTime 进行 JVM 基准测试非常容易出错。使用微基准测试框架,例如ThymeCaliperJMH

第二:您正在将可变的Java 哈希映射与不可变的Scala 哈希映射进行比较。不可变集合可以非常快,但在某些情况下它们永远不会像可变数据结构那样快。

这是可变 Java 哈希映射与不可变 Scala 哈希映射的适当微基准:https ://gist.github.com/rklaehn/26c277b2b5666ec4b372

如您所见,scala 不可变映射比 java 可变映射快一点。请注意,一旦您使用更大的地图,情况就不会如此,因为不可变的数据结构必须做出一些妥协才能实现结构共享。我猜想在这两种情况下,主要的性能问题是将整数装箱为整数。

更新:如果你真的想要一个以整数作为键的可变哈希 hap,那么 scala 集合库中的正确选择是scala.collection.mutable.LongMap。这使用 long as 键,并且比通用 Map 具有更好的性能,因为它不必对值进行装箱。查看要点的结果。

更新 2:如果您的密钥从 AnyRef 扩展(例如字符串),则高性能可变映射的最佳选择是scala.collection.mutable.AnyRefMap

于 2015-02-26T16:03:01.880 回答
12

而不是调用applyie scalaMap(i),如果你这样做,scalaMap.get(i)那么它的速度就像javaMap.get(i)

代码来看,申请的代码是


def apply(key: A): B = get(key) match {
    case None => default(key)
    case Some(value) => value
  }

这表明 apply 方法首先调用该get方法,然后对其进行模式匹配。在每次调用的情况下都有一个额外的跳跃option确实会降低性能,并且已经在 SO 上进行了讨论(虽然找不到链接)

于 2015-02-26T14:58:08.050 回答
3

Scala 2.13(2019 年 6 月)确实引入了新的、更快的HashMap/Set实现

不可变 ( d5ae93e ) 和可变 ( #7348 ) 版本都被完全替换。- 在大多数情况下,它们的性能大大优于旧的实现。- 可变版本现在可以与 Java 标准库的实现相媲美。

对于不可变的HashSetand HashMap

重新实现基于Compressed Hash-Array Mapped Prefix-trees ( CHAMP )。

有关低级性能优化的更多详细信息和描述,请参阅 Steindorfer 和 Vinju 的论文“Optimizing Hash-Array Mapped Tries for Fast and Lean Immutable JVM Collections”(OOPSLA'15)(该论文的预印本可用)。

于 2019-06-11T08:06:22.027 回答