同様の操作(つまり、多くのマップを1つのマップにマージします。この質問を参照)について、Scalaのimmutable.Mapとmutable.Mapのパフォーマンス特性を比較したいと思いました。可変マップと不変マップの両方で同様の実装のように見えるものがあります(以下を参照)。
テストとして、1,000,000個の単一アイテムのMap [Int、Int]を含むリストを生成し、このリストをテスト対象の関数に渡しました。十分なメモリがあれば、結果は驚くべきものではありませんでした。mutable.Mapの場合は約1200ミリ秒、immutable.Mapの場合は約1800ミリ秒、mutable.Mapを使用した命令型実装の場合は約750ミリ秒です。それについてもコメントしてください。
少し驚いたのは、おそらく私が少し太っていたためです。IntelliJ8.1のデフォルトの実行構成では、両方の可変実装がOutOfMemoryErrorにヒットしましたが、不変コレクションはヒットしませんでした。不変テストは最後まで実行されましたが、非常にゆっくりと実行されました。約28秒かかります。最大JVMメモリを増やしたところ(約200MB、しきい値がどこにあるかわからない)、上記の結果が得られました。
とにかく、これが私が本当に知りたいことです:
なぜ可変の実装はメモリを使い果たしますが、不変の実装はそうではありませんか? 不変バージョンでは、可変実装が実行する前にガベージコレクターを実行してメモリを解放できると思いますが、これらのガベージコレクションはすべて、不変の低メモリ実行の遅さを説明していますが、より詳細な説明が必要ですそれより。
以下の実装。(注:これらが可能な限り最良の実装であるとは言いません。遠慮なく改善を提案してください。)
def mergeMaps[A,B](func: (B,B) => B)(listOfMaps: List[Map[A,B]]): Map[A,B] =
(Map[A,B]() /: (for (m <- listOfMaps; kv <-m) yield kv)) { (acc, kv) =>
acc + (if (acc.contains(kv._1)) kv._1 -> func(acc(kv._1), kv._2) else kv)
}
def mergeMutableMaps[A,B](func: (B,B) => B)(listOfMaps: List[mutable.Map[A,B]]): mutable.Map[A,B] =
(mutable.Map[A,B]() /: (for (m <- listOfMaps; kv <- m) yield kv)) { (acc, kv) =>
acc + (if (acc.contains(kv._1)) kv._1 -> func(acc(kv._1), kv._2) else kv)
}
def mergeMutableImperative[A,B](func: (B,B) => B)(listOfMaps: List[mutable.Map[A,B]]): mutable.Map[A,B] = {
val toReturn = mutable.Map[A,B]()
for (m <- listOfMaps; kv <- m) {
if (toReturn contains kv._1) {
toReturn(kv._1) = func(toReturn(kv._1), kv._2)
} else {
toReturn(kv._1) = kv._2
}
}
toReturn
}