そして、それについて何ができるでしょうか?
いくつかのテストを実行しましたが、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)
まず第一に、nanoTimeを使用してJVMベンチマークを実行すると、非常にエラーが発生しやすくなります。 Thyme 、 Caliper 、 [〜#〜] 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
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ですでに説明されています(リンクが見つかりません)
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)を参照してください( 論文のプレプリントは利用可能 )。