我有点问题。我想使用scala.concurrent.ops.replicate来并行化我的程序。但我发现,算法实际上变得慢得多。 所以我写了一个小测试,但仍然得到了相同的结果。所以他们在这里。
序列号:完成约63秒
object SerTest {
def main(args: Array[String]) {
for(x <- 1 to 10){
for(i <- 1 to 4) {
for(j <- 1 to 100000) {
val a = BigInt(j).isProbablePrime(1000)
if(!a && j == 100000) println(i + " is ready")}}}}}
并发代码:完成大约161秒
object ParTest {
def main(args: Array[String]) {
for(x <- 1 to 10){
replicate(1,5) { i =>
for(j <- 1 to 100000) {
val a = BigInt(j).isProbablePrime(1000)
if(!a && j == 100000) println(i + " is ready")}}}}}
那么我所犯的那个完全明显且令人尴尬的错误在哪里? :)
编辑:哦,我在Quadcore-CPU上运行它。所以它实际上应该更快:)
编辑2:由于凯文赖特的回答,我稍微改变了程序,以便有更长的时间来运行。
答案 0 :(得分:3)
查看BigInteger.isProbablePrime的源代码(BigInt委托给java库)。它正在做大量的新BigInteger()因为那是一个不可变的类。
我的猜测是内存分配导致太多争用,从并行化中受益。您可以通过将一个简单的计算(例如将100MM数相乘)替换为主要测试来确认。或者,使用var longs而不是BigInt重写主要测试。
此外,ops.replicate会将操作生成新线程,而不是使用某种线程池。线程创建有一定的开销,但在这种情况下不足以成为问题。我个人更喜欢坚持使用更强大的java.util.concurrent库。
答案 1 :(得分:2)
查看示例代码,我猜你是从命令行直接跳到main方法。这是您在Java中进行微抽样的绝对最糟糕的方式!
您应该首先运行您的测试几次(在同一个VM调用中),至少足以使JVM在您思考之前已经正常预热并运行了30秒关于开始测量任何东西。这将确保它运行已编译(而非解释)的代码,并且已经完全优化。
您还需要了解启动线程的成本。对于短时间运行的循环,这将是一个令人望而却步的开销,并且将比循环本身消耗更多的时间!
<强>更新强>
以下定义来自ops.scala:
val defaultRunner: FutureTaskRunner = TaskRunners.threadRunner
def spawn(p: => Unit)(implicit runner: TaskRunner = defaultRunner): Unit = {...}
def replicate(start: Int, end: Int)(p: Int => Unit) {...}
所以使用的实际跑步者是一个隐含的,
或默认为TaskRunners.threadRunner
您可以尝试通过为代码添加前缀来更改此选项以使用线程池:
implicit val runner = TaskRunners.threadPoolRunner
或者相信以下内容也适用:
import concurrent.TaskRunners.threadPoolRunner
看看是否有任何区别
再想一想......
我认为该参数实际上不会传递给嵌套调用spawn
,如果您自己只复制该方法可能会更好(我目前在邮件列表上有一个关于此的查询) )。
为了您的方便,这里有完整,可怕,荣耀的方法:
def replicate(start: Int, end: Int)(p: Int => Unit) {
if (start == end)
()
else if (start + 1 == end)
p(start)
else {
val mid = (start + end) / 2
spawn { replicate(start, mid)(p) }
replicate(mid, end)(p)
}
}
(你仍然需要定义隐含的跑步者......)