对于Scala 2.13,更新具有数百万次更新的LongMap、HashMap或TrieMap的最快方法是什么?

对于Scala 2.13,更新具有数百万次更新的LongMap、HashMap或TrieMap的最快方法是什么?,scala,parallel-processing,parallel.foreach,scala-2.13,Scala,Parallel Processing,Parallel.foreach,Scala 2.13,目标 我有一个可变地图[Long,Long],有数百万个条目。我需要用数百万次更新进行多次迭代更新。我想尽快做到这一点 背景 目前,最快的方法是使用单线程mutable.LongMap[Long]。此类型针对作为键的长类型进行了优化 其他映射类型看起来比较慢——但我可能没有正确地实现它们,因为我试图同时和/或并行地进行更新,但没有成功。并行更新映射可能没有实际发生,或者在Scala中不可能 按照从最快到最慢的顺序: 长地图[长](从上方) TrieMap[长,长] ParTrieMap[长,长]

目标

我有一个可变地图[Long,Long],有数百万个条目。我需要用数百万次更新进行多次迭代更新。我想尽快做到这一点

背景

目前,最快的方法是使用单线程mutable.LongMap[Long]。此类型针对作为键的长类型进行了优化

其他映射类型看起来比较慢——但我可能没有正确地实现它们,因为我试图同时和/或并行地进行更新,但没有成功。并行更新映射可能没有实际发生,或者在Scala中不可能

按照从最快到最慢的顺序:

  • 长地图[长](从上方)
  • TrieMap[长,长]
  • ParTrieMap[长,长]
  • HashMap[Long,Long]
  • ParHashMap[长,长]
  • ParMap[长,长]
  • 如果一个更快的方法是不可变的,这是可以的,但我不认为会是这样。对于这个用例,可变映射可能是最好的

    生成测试数据和测试时间的代码

    import java.util.Calendar
    import scala.collection.mutable
    
    object DictSpeedTest2 {
    
      //helper constants
      val million: Long = 1000000
      val billion: Long = million * 1000
    
      //config
      val garbageCollectionWait = 3
      val numEntries: Long = million * 10 //may need to increase JVM memory with something like: -Xmx32g
      val maxValue: Long = billion * million // max Long = 9223372036854775807L
                                             // this is       1000000000000000L
    
      def main(args: Array[String]): Unit = {
        //generate random data; initial entries in a; updates in b
        val a = genData(numEntries, maxValue, seed = 1000)
        val b = genData(numEntries, maxValue, seed = 9999)
    
        //initialization
        val dict = new mutable.LongMap[Long]()
        a.foreach(x => dict += (x._1 -> x._2))
    
        //run and time test
        println("start test: " + Calendar.getInstance().getTime)
        val start = System.currentTimeMillis
        b.foreach(x => dict += (x._1 -> x._2)) //updates
        val end = System.currentTimeMillis
    
        //print runtime
        val durationInSeconds = (end - start).toFloat / 1000 + "s"
        println("end test:  " + Calendar.getInstance().getTime + " -- " + durationInSeconds)
      }
    
      def genData(n: Long, max: Long, seed: Long): Array[(Long, Long)] = {
        val r = scala.util.Random
        r.setSeed(seed) //deterministic generation of arrays
        val a = new Array[(Long, Long)](n.toInt)
        a.map(_ => (r.nextInt(), r.nextInt()) )
      }
    }
    
    当前计时

    带有上述代码的LongMap[Long]将在我的2018 MacBook Pro上在以下时间完成:

    • 约3.5秒,含numEntries=1000万
    • 约100秒,含numEntries=1亿

    如果您不仅限于使用Scala/Java映射,而且为了获得优异的性能,您可以查看第三方库,这些库具有专门用于长/长键/值对的映射


    对于此类库,对于Int/Int对的基准测试结果并不过时。

    首先,这远不是测试两种实现性能的合适方法。看看合适的基准测试工具,比如ScalaMeter。第二,易变性+并发性/并行性将导致许多麻烦。这可能有助于描述您在此尝试建模的内容,以及为什么在此用例中性能对您来说太重要。但您仍然没有回答此可变映射的目的是什么以及为什么性能太重要。您是否考虑过将该状态移到应用程序之外。例如数据库,例如Mongo?或者更适合像Redis这样的分布式缓存?或者使用其他技术来处理大量的数据,比如Spark或Scio?是的,我指的更多的是在代码之外保留所有突变的能力,有时你可以用不同的方式优化算法。如果您使用AWS,Scio将不会为您工作,因为它与GCP中的数据流服务集成良好。PS:你也可以看看流媒体是否有帮助,看看fs2、Monix或Akka Streams。我对Akka演员非常熟悉,也涉猎过Streams库。我真的想保持这个模块中的状态。离开应用程序会减慢速度,需要重新进入一些状态。延迟在这里很重要。我导入了FastUtil,获得了几乎瞬间8倍的速度提升。那就足够了。前几天我找到了那篇文章。FastUtil可能正是我想要的。我打算今天晚些时候测试它。FastUtil真的很快。这里没有虚假广告。谢谢