如何从Java集合创建一个Scala并行集合

5

将Java集合转换为Scala等效集合的最简单方法是使用JavaConversions,自Scala 2.8以来。这些隐式定义返回所包含Java集合的包装器。

Scala 2.9引入了并行集合,可以在集合上执行并行操作并稍后收集结果。这很容易实现,将现有集合转换为并行集合只需简单地执行以下操作:

myCollection.par

但是,使用 JavaConversions 将 Java 集合转换为 Scala 集合再使用 'par' 存在问题。正如 Parallel Collection Conversions 中所述,内在的串行集合通过评估所有值并将其添加到新的并行集合中而“强制”转换为新的并行集合:

其他集合,例如列表、队列或流,本质上是串行的,这意味着必须一个接一个地访问元素。这些集合通过将元素复制到类似的并行集合中转换为其并行变体。例如,函数式列表被转换为标准不可变并行序列,即并行向量。

当原始的 Java 集合旨在进行惰性评估时,这会导致问题。例如,如果只返回一个 Java Iterable,然后将其转换为 Scala Iterable,无法保证 Iterable 的内容是否打算急切地访问。因此,应该如何从 Java 集合创建并行集合而不支持评估每个元素的成本?使用并行集合并希望“获取”提供的前 n 个结果来并行执行它们可以避免这种成本。
根据Parallel Collection Conversions,有一系列的集合类型成本为常量,但是似乎无法保证这些类型可以通过 JavaConversions 创建(例如,可以创建 'Set',但这是一个 'HashSet' 吗?)。

1
请注意,最好使用JavaConverters而不是JavaConversions。如果使用后者,您可以执行.asScala.toList.par之类的操作。 - Sean Parsons
2个回答

4
首先,通过JavaConversion从Java集合获取的每个集合都不是默认可并行化的Scala集合 - 这意味着它将始终重新评估为其相应的并行集合实现。这是因为并行执行依赖于至少Splitters的概念 - 它必须可分割为更小的子集,不同的处理器可以在上面工作。
我不知道你的Java集合在数据结构意义上是什么样子的,但如果它是树状结构或下面的数组,其元素被惰性地评估,那么很可能你可以轻松地实现一个Splitter
如果您不想急切地force实现Java集合API的惰性集合,则您唯一的选择是为该特定惰性Java集合实现一种新类型的并行集合。在这个新实现中,您必须提供分裂迭代器的方法(也就是说,一个Splitter)。
一旦您实现了这个新的并行集合,它知道如何拆分您的数据结构,您应该为您特定的Java集合创建一个自定义的Scala包装器(此时只需要一点额外的样板文件,可以参考JavaConversions中的操作),并覆盖其par方法以返回您特定的并行集合。
如果您的Java集合是具有特别高效的get方法的序列(在Java中,是一个List),那么甚至可以对索引序列进行通用处理。您可以将Splitter实现为一个迭代器,在初始范围从0size - 1内调用get方法,并通过细分此范围来拆分它。
如果您这样做了,欢迎提交标准库的补丁。

1

并行处理需要随机访问,而java.lang.Iterable无法提供此功能。这是一种根本性的不匹配,无论进行多少次转换都无法轻松解决。

用非编程类比来说,你不能通过同时将一个人从新加坡送到英国,另一个人从澳大利亚送到新加坡来将一个人从澳大利亚送到英国。

或者在编程中,如果你正在处理实时数据流,你不能通过同时处理现在和五分钟前的数据来并行化处理它,否则会增加延迟。

你需要使用提供至少一些随机访问的东西,例如java.util.List.listIterator(Int)而不是Iterable。


我想我假设每个检索下一个元素的调用(即Iterable.iterator().next())都在一个线程内运行。 - Dan Gravell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接