[英]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.