[英]Scala fast way to parallelize collection
我的代码等效于此:
def iterate(prev: Vector[Int], acc: Int): Vector[Int] = {
val next = (for { i <- 1.to(1000000) }
yield (prev(Random.nextInt(i))) ).toVector
if (acc < 20) iterate(next, acc + 1)
else next
}
iterate(1.to(1000000).toVector, 1)
对于大量的迭代,它对集合进行操作,并产生值。 在迭代结束时,它将所有内容转换为向量。 最后,它继续进行下一个递归自调用,但是直到完成所有迭代后才能继续进行。 递归自调用的数量很少。
我想将其并称,因此我尝试在1.to(1000000)范围内使用.par
。 这使用了8个进程而不是1个,结果只快了两倍! .toParArray
仅比.par
快一点。 有人告诉我,如果我使用不同的东西(例如ThreadPool)可能会快得多-这很有意义,因为所有的时间都花在了构造next
,而且我假设将不同进程的输出连接到共享内存上不会导致即使是非常大的产出,其增长速度也将大大降低(这是一个关键的假设,可能是错误的)。 我该怎么做? 如果您提供代码,则将我给出的代码与之并列就足够了。
请注意,我提供的代码不是我的实际代码。 我的实际代码更长,更复杂(用于带有约束,BitSet和更多内容的TSP的Held-Karp算法),唯一显着的区别是在我的代码中, prev
的类型是ParMap
,而不是Vector
。
编辑更多信息:在我可以处理的最大样本量下,ParMap在最坏的迭代中有350k元素,否则通常为5k-200k(在对数刻度上变化)。 如果固有地需要大量时间将流程的结果连接到一个流程中(我认为这是正在发生的事情),那么我无能为力,但我相当怀疑这种情况。
在问题中提出的原始版本之后实施了几个版本,
rec0
是带有for循环的原始; rec1
使用par.map
代替for循环; rec2
跟在rec1
但是它为延迟生成器使用了并行集合ParArray
(以及对批量遍历操作的快速访问) ; rec3
是具有可变ArrayBuffer
的非惯用非并行版本。 从而
import scala.collection.mutable.ArrayBuffer
import scala.collection.parallel.mutable.ParArray
import scala.util.Random
// Original
def rec0() = {
def iterate(prev: Vector[Int], acc: Int): Vector[Int] = {
val next = (for { i <- 1.to(1000000) }
yield (prev(Random.nextInt(i))) ).toVector
if (acc < 20) iterate(next, acc + 1)
else next
}
iterate(1.to(1000000).toVector, 1)
}
// par map
def rec1() = {
def iterate(prev: Vector[Int], acc: Int): Vector[Int] = {
val next = (1 to 1000000).par.map { i => prev(Random.nextInt(i)) }.toVector
if (acc < 20) iterate(next, acc + 1)
else next
}
iterate(1.to(1000000).toVector, 1)
}
// ParArray par map
def rec2() = {
def iterate(prev: ParArray[Int], acc: Int): ParArray[Int] = {
val next = (1 to 1000000).par.map { i => prev(Random.nextInt(i)) }.toParArray
if (acc < 20) iterate(next, acc + 1)
else next
}
iterate((1 to 1000000).toParArray, 1).toVector
}
// Non-idiomatic non-parallel
def rec3() = {
def iterate(prev: ArrayBuffer[Int], acc: Int): ArrayBuffer[Int] = {
var next = ArrayBuffer.tabulate(1000000){i => i+1}
var i = 0
while (i < 1000000) {
next(i) = prev(Random.nextInt(i+1))
i = i + 1
}
if (acc < 20) iterate(next, acc + 1)
else next
}
iterate(ArrayBuffer.tabulate(1000000){i => i+1}, 1).toVector
}
然后对平均经过时间进行一些测试,
def elapsed[A] (f: => A): Double = {
val start = System.nanoTime()
f
val stop = System.nanoTime()
(stop-start)*1e-6d
}
val times = 10
val e0 = (1 to times).map { i => elapsed(rec0) }.sum / times
val e1 = (1 to times).map { i => elapsed(rec1) }.sum / times
val e2 = (1 to times).map { i => elapsed(rec2) }.sum / times
val e3 = (1 to times).map { i => elapsed(rec3) }.sum / times
// time in ms.
e0: Double = 2782.341
e1: Double = 2454.828
e2: Double = 3455.976
e3: Double = 1275.876
表明非惯用非并行版本证明了平均速度最快。 对于较大的输入数据,并行,惯用的版本可能会有所帮助。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.