web-dev-qa-db-ja.com

Scalaハッシュマップが遅いのはなぜですか?

そして、それについて何ができるでしょうか?

いくつかのテストを実行しましたが、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を除く(キーがInt/Longの場合)。

パフォーマンスの違いはそれほど大きくないため、ほとんどのユースケースで問題になります。うまくいけば、Scalaはマップの速度を向上させます。それまでの間、(整数以外のキーで)パフォーマンスが必要な場合はJavaを使用してください。

整数キー、n = 2
Long(60)、Java(93)、Open(170)、MutableSc(243)、ImmutableSc(317)

ケースオブジェクトキー、n = 2
Java(195)、AnyRef(230)

27
MS-H

まず第一に、nanoTimeを使用してJVMベンチマークを実行すると、非常にエラーが発生しやすくなります。 ThymeCaliper[〜#〜] jmh [〜#〜] などのマイクロベンチマークフレームワークを使用します

2番目:mutableJavaハッシュマップとimmutablescalaハッシュマップ。不変のコレクションは非常に高速ですが、可変のデータ構造ほど高速にならない場合があります。

可変の適切なマイクロベンチマークは次のとおりですJavaハッシュマップと不変scalaハッシュマップ: https://Gist.github.com/rklaehn/26c277b2b5666ec4b372

ご覧のとおり、scala不変マップはJava可変マップよりも少し高速です。大きくなると、これは当てはまらないことに注意してください。不変のデータ構造は、有効にするためにいくつかの妥協を行う必要があるため、マップします 構造共有 。どちらの場合も、パフォーマンスの主な問題は、整数への整数のボックス化であると思います。

更新:キーとしてintを使用した可変ハッシュハプが本当に必要な場合、scalaコレクションライブラリからの正しい選択は scala.collection.mutable.LongMap です。これはキーと同じくらい長く、値をボックス化する必要がないため、一般的なマップよりもはるかに優れたパフォーマンスを発揮します。要点の結果を参照してください。

更新2:キーがAnyRef(文字列など)から拡張されている場合、高性能mutableマップの最善の策は scala.collection.mutable.AnyRefMap

30
Rüdiger Klaehn

apply、つまりscalaMap(i)を呼び出す代わりに、scalaMap.get(i)を実行すると、javaMap.get(i)と同じくらい高速になります。

source から、applyのコードは


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

これは、applyメソッドが最初にgetメソッドを呼び出し、次にパターンが一致することを示しています。 optionの場合に呼び出しごとに追加のホップがあると、パフォーマンスが低下し、SOですでに説明されています(リンクが見つかりません)

12
mohit

Scala 2.13(2019年6月)は、新しく、より高速なHashMap/Set実装

不変( d5ae93e )バージョンと可変( #7348 )バージョンの両方が完全に置き換えられました。 -ほとんどのシナリオで、古い実装を大幅に上回っています。 -可変バージョンは、Java標準ライブラリの実装と同等に機能するようになりました。

不変のHashSetおよびHashMapの場合:

再実装は、Compressed Hash-Array Mapped Prefix-trees[〜#〜] Champ [〜# 〜])。

低レベルのパフォーマンス最適化の詳細と説明については、Steindorfer and Vinjuによる論文「OptimizingHash-Array Mapped Tries for Fast and Lean Immutable JVMCollections」(OOPSLA'15)を参照してください( 論文のプレプリントは利用可能 )。

1
VonC