在找到所需数量的结果时,在早期中止时过滤Scala的并行集合

时间:2011-11-09 23:12:59

标签: scala parallel-processing parallel-collections

给定 collection.parallel.mutable.ParHashMap (或任何其他并行集合)的非常大的实例,一旦给定(例如50)匹配数量,如何中止过滤并行扫描被发现了?

尝试在线程安全的“外部”数据结构中累积中间匹配或保持外部AtomicInteger的结果计数在4个核心上比使用常规 collection.mutable.HashMap <慢2到3倍/ strong>并将单个核心固定在100%。

我知道Par *集合上的发现存在会在“内部”中止。有没有办法概括这个以找到多个结果?

这些代码在ParHashMap上似乎仍然慢了2到3倍,条目约为79,000,并且还存在将更多填充 maxResults 结果的问题结果CHM(这可能是由于线程在 incrementAndGet 之后但在 break 之前被抢占,这允许其他线程添加更多元素)。更新:似乎减速是由于工作者线程在counter.incrementAndGet()上竞争,这当然违背了整个并行扫描的目的: - (

def find(filter: Node => Boolean, maxResults: Int): Iterable[Node] =
{
  val counter = new AtomicInteger(0)
  val results = new ConcurrentHashMap[Key,  Node](maxResults)

  import util.control.Breaks._

  breakable
  {
    for ((key, node) <- parHashMap if filter(node))
    {
      results.put(key, node)
      val total = counter.incrementAndGet()
      if (total > maxResults) break
    }
  }

  results.values.toArray(new Array[Node](results.size))
}

3 个答案:

答案 0 :(得分:2)

我首先要进行并行扫描,其中变量maxResults将是threadlocal。这将找到(maxResults * numberOfThreads)结果。

然后我会进行单线程扫描以将其减少到maxResults。

答案 1 :(得分:1)

我对你的案子进行了一次有趣的调查。

调查推理

我怀疑问题在于输入Map的可变性,我将尝试解释原因:HashMap实现将数据组织在不同的桶中,正如人们在维基百科上看到的那样。

Wikipedia HashMap

Java中的第一个线程安全集合,同步集合基于同步基础实现的所有方法,导致性能低下。进一步的研究和思考带来了更高性能的Concurrent Collection,例如ConcurrentHashMap,这种方法更聪明:为什么我们不用特定的锁保护每个桶?

根据我的感觉,出现性能问题是因为:

  • 当您并行运行过滤器时,某些线程会在同时访问同一个存储桶时发生冲突并且会锁定相同的锁定,因为您的地图是 mutable
  • 你拿着一个柜台,看看你有多少结果,而你可以实际检查你的大小 结果。如果你有一个线程安全的方法来构建一个集合,你也不需要一个线程安全的计数器。

调查结果

我开发了一个测试用例,我发现我错了。问题在于输出映射的并发性质。事实上,当你将元素放在地图中时,而不是在迭代它时,就是发生碰撞的地方。此外,由于您只需要值的结果,因此您不需要键和散列以及所有地图功能。测试是否删除了AtomicCounter并且仅使用result地图来检查是否收集了足够的元素版本,这可能会很有趣。

请注意Scala 2.9.2中的以下代码。我在另一篇文章中解释为什么我需要两个不同的并行和非并行版本的函数:Calling map on a parallel collection via a reference to an ancestor type

object MapPerformance {

  val size = 100000
  val items = Seq.tabulate(size)( x => (x,x*2))


  val concurrentParallelMap = ImmutableParHashMap(items:_*)

  val concurrentMutableParallelMap = MutableParHashMap(items:_*)

  val unparallelMap = Map(items:_*)


  class ThreadSafeIndexedSeqBuilder[T](maxSize:Int) {
    val underlyingBuilder = new VectorBuilder[T]()
    var counter = 0
    def sizeHint(hint:Int) { underlyingBuilder.sizeHint(hint) }
    def +=(item:T):Boolean ={
      synchronized{
        if(counter>=maxSize)
          false
        else{
          underlyingBuilder+=item
          counter+=1
          true
        }
      }
    }
    def result():Vector[T] = underlyingBuilder.result()

  }

  def find(map:ParMap[Int,Int],filter: Int => Boolean, maxResults: Int): Iterable[Int] =
  {

    // we already know the maximum size
    val resultsBuilder = new ThreadSafeIndexedSeqBuilder[Int](maxResults)
    resultsBuilder.sizeHint(maxResults)

    import util.control.Breaks._
    breakable
    {
      for ((key, node) <- map if filter(node))
      {
        val newItemAdded = resultsBuilder+=node
        if (!newItemAdded)
          break()

      }
    }
    resultsBuilder.result().seq

  }

  def findUnParallel(map:Map[Int,Int],filter: Int => Boolean, maxResults: Int): Iterable[Int] =
  {

    // we already know the maximum size
    val resultsBuilder = Array.newBuilder[Int]
    resultsBuilder.sizeHint(maxResults)

    var counter = 0
      for {
        (key, node) <- map if filter(node)
        if counter < maxResults
      }{
        resultsBuilder+=node
        counter+=1
      }

    resultsBuilder.result()

  }

  def measureTime[K](f: => K):(Long,K) = {
    val startMutable = System.currentTimeMillis()
    val result = f
    val endMutable = System.currentTimeMillis()
    (endMutable-startMutable,result)
  }

  def main(args:Array[String]) = {
    val maxResultSetting=10
    (1 to 10).foreach{
      tryNumber =>
        println("Try number " +tryNumber)
        val (mutableTime, mutableResult) = measureTime(find(concurrentMutableParallelMap,_%2==0,maxResultSetting))
        val (immutableTime, immutableResult) = measureTime(find(concurrentMutableParallelMap,_%2==0,maxResultSetting))
        val (unparallelTime, unparallelResult) = measureTime(findUnParallel(unparallelMap,_%2==0,maxResultSetting))
        assert(mutableResult.size==maxResultSetting)
        assert(immutableResult.size==maxResultSetting)
        assert(unparallelResult.size==maxResultSetting)
        println(" The mutable version has taken " + mutableTime + " milliseconds")
        println(" The immutable version has taken " + immutableTime + " milliseconds")
        println(" The unparallel version has taken " + unparallelTime + " milliseconds")
     }
  }

}

使用这段代码,我系统地将并行(输入映射的可变和不可变版本)比我机器上的不平行快3.5倍。

答案 2 :(得分:0)

您可以尝试获取迭代器,然后创建一个惰性列表(Stream),在其中过滤(使用谓词)并获取所需的元素数。因为这是一个非严格的,这个&#39;采取&#39;不评估元素的数量。 之后,您可以通过添加&#34; .par&#34;来强制执行。整个过程并实现并行化。

示例代码:

具有随机值的并行化映射(模拟并行哈希映射):

scala> myMap
res14: scala.collection.parallel.immutable.ParMap[Int,Int] = ParMap(66978401 -> -1331298976, 256964068 -> 126442706, 1698061835 -> 1622679396, -1556333580 -> -1737927220, 791194343 -> -591951714, -1907806173 -> 365922424, 1970481797 -> 162004380, -475841243 -> -445098544, -33856724 -> -1418863050, 1851826878 -> 64176692, 1797820893 -> 405915272, -1838192182 -> 1152824098, 1028423518 -> -2124589278, -670924872 -> 1056679706, 1530917115 -> 1265988738, -808655189 -> -1742792788, 873935965 -> 733748120, -1026980400 -> -163182914, 576661388 -> 900607992, -1950678599 -> -731236098)

获取迭代器并从迭代器创建一个Stream并对其进行过滤。 在这种情况下,我的谓词只接受对(地图的值成员)。 我想获得10个偶数元素,所以我需要10个元素,这些元素只有在我强制执行时才会被评估:

scala> val mapIterator = myMap.toIterator
mapIterator: Iterator[(Int, Int)] = HashTrieIterator(20)


scala> val r = Stream.continually(mapIterator.next()).filter(_._2 % 2 == 0).take(10)
r: scala.collection.immutable.Stream[(Int, Int)] = Stream((66978401,-1331298976), ?)

最后,我强制进行仅按计划获得10个元素的评估

scala> r.force
res16: scala.collection.immutable.Stream[(Int, Int)] = Stream((66978401,-1331298976), (256964068,126442706), (1698061835,1622679396), (-1556333580,-1737927220), (791194343,-591951714), (-1907806173,365922424), (1970481797,162004380), (-475841243,-445098544), (-33856724,-1418863050), (1851826878,64176692))

这样,您只需获得所需的元素数量(无需处理剩余的元素),并且您可以在没有锁定,原子或中断的情况下并行化该过程。

请将此与您的解决方案进行比较,看看它是否有用。