通过引用祖先类型在并行集合上调用映射

时间:2013-01-18 13:49:01

标签: scala parallel-collections

我尝试将其作为可选项顺序或并行运行map操作,例如使用以下代码:

val runParallel = true
val theList = List(1,2,3,4,5)
(if(runParallel) theList.par else theList) map println //Doesn't run in parallel

我注意到'map'操作并不像我预期的那样并行运行。虽然没有条件,但它会:

theList.par map println   //Runs in parallel as visible in the output

我期望成为(if(runParallel) theList else theList.par)theList这两种类型最接近的共同祖先的表达式theList.par的类型是一种可怕的类型,我不会粘贴在这里,但它是有趣的是(通过scala控制台:)

:type (if(true) theList else theList.par)

为什么并行集合上的map不能并行工作?

更新:这在SI-4843中讨论过,但是从JIRA票证中可以看出为什么Scala 2.9.x会发生这种情况。

1 个答案:

答案 0 :(得分:3)

对其发生原因的解释是一个很长的故事:在Scala 2.9.x中(我不知道其他版本)这些集合方法(如filter或map)依赖于CanBuildFrom机制。这个想法是你有一个隐含的参数,用于为新集合创建一个构建器:

def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = {
    val b = bf(repr)
    b.sizeHint(this) 
    for (x <- this) b += f(x)
    b.result
  }

由于这种机制,map方法仅在TraversableLike特征中定义,其子类不需要覆盖它。如您所见,在方法映射签名中有许多类型参数。让我们来看看琐碎的事情:

  • B,它是新集合的元素类型
  • A是源集合中元素的类型

让我们看一下更复杂的问题:

  • That是新类型的集合,可以与当前类型不同。一个经典的例子是当您使用toString映射例如BitSet时:

     scala> val a = BitSet(1,3,5)
     a scala.collection.immutable.BitSet = BitSet(1, 3, 5)
     scala>  a.map {_.toString}
     res2: scala.collection.immutable.Set[java.lang.String] = Set(1, 3, 5)
    

由于创建BitSet[String]地图结果是非法的,因此Set[String] 最后Repr是当前集合的类型。当您尝试在函数上映射集合时,编译器将使用类型参数解析合适的CanBuildFrom。

在合理的情况下,地图方法已在ParIterableLike中的并行集合中重写,如下所示:

 def map[S, That](f: T => S)(implicit bf: CanBuildFrom[Repr, S, That]): That = bf ifParallel { pbf =>
    executeAndWaitResult(new Map[S, That](f, pbf, splitter) mapResult { _.result })
  } otherwise seq.map(f)(bf2seq(bf))

正如您所看到的,该方法具有相同的签名,但它使用了不同的方法:它测试提供的CanBuildFrom是否是并行的,否则将回退到默认实现。 因此,Scala并行集合使用特殊的CanBuildFrom(并行集合)为地图方法创建并行构建器。

然而,当你做

时会发生什么
(if(runParallel) theList.par else theList) map println //Doesn't run in parallel

是在

的结果上执行的map方法
  (if(runParallel) theList.par else theList) 

其返回类型是两个类的第一个共同祖先(在这种情况下只是一定数量的特征混合了一起)。由于它是一个共同的祖先,因此类型参数Repr将是两个集合表示的某种共同祖先,我们称之为Repr1


<强>结论

当您调用map方法时,编译器应为该操作找到合适的CanBuildFrom[Repr, B, That]。由于我们的Repr1不是并行集合,因此不会有任何CanBuildFrom[Repr1,B,That]能够提供并行构建器。对于Scala集合的实现,这实际上是一个正确的行为,如果行为不同,则意味着非并行集合的每个映射也将并行运行。

这里的要点是,对于Scala集合在2.9.x中的设计方式,没有其他选择。如果编译器没有为并行集合提供CanBuildFrom,则映射将不是并行的。