我有一些场景,我需要一次处理数千条记录。有时,它可能是数百,可能高达30000条记录。我在考虑使用scala的并行集合。所以只是为了理解差异,我写了一个简单的pgm,如下所示:
object Test extends App{
val list = (1 to 100000).toList
Util.seqMap(list)
Util.parMap(list)
}
object Util{
def seqMap(list:List[Int]) = {
val start = System.currentTimeMillis
list.map(x => x + 1).toList.sum
val end = System.currentTimeMillis
println("time taken =" + (end - start))
end - start
}
def parMap(list:List[Int]) = {
val start = System.currentTimeMillis
list.par.map(x => x + 1).toList.sum
val end = System.currentTimeMillis
println("time taken=" + (end - start))
end - start
}
}
我预计并行运行会更快。但是,我得到的输出是
time taken =32
time taken=127
机器配置:
Intel i7 processor with 8 cores
16GB RAM
64bit Windows 8
我做错了什么?这不是并行映射的正确方案吗?
答案 0 :(得分:9)
问题在于您正在执行的操作如此之快(仅添加两个整数),执行并行化的开销不仅仅是好处。如果操作较慢,则并行化才有意义。
以这种方式思考:如果你有8个朋友并且你在纸上给每个人一个整数并告诉他们加一个,把结果写下来,然后把它还给你,你要记录之前给他们下一个整数,你花了很多时间来回传递消息,你可以把所有的事情加快起来。
另外:永远不要在列表上.par
,因为并行化过程必须将整个列表复制到并行集合中,然后将整个事件复制回来。如果你使用Vector,那么它不需要做额外的工作。
答案 1 :(得分:2)
并行化列表的开销比按顺序实际处理x + 1
操作更耗时。
然而,考虑这种修改,我们包括一个大约经过1毫秒的操作,
case class Delay() {
Thread.sleep(1)
}
并替换
list.map(x => x + 1).toList.sum
与
list.map(_ => Delay()).toList
现在val list = (1 to 10000).toList
(注意10000而不是100000),在四核8GB机器中,
scala> Util.parMap(list)
time taken=3451
res4: Long = 3451
scala> Util.seqMap(list)
time taken =10816
res5: Long = 10816
我们可以推断(更好地猜测)对于具有耗时操作的大型集合,与顺序集合处理相比,并行化集合的开销不会显着影响已用时间。
答案 2 :(得分:0)
如果您正在进行基准测试,请考虑使用JMH之类的东西来避免您可能遇到的所有问题,如果您按照程序显示的方式进行测量。例如,JIT可能会显着改变您的结果,但只能在一些迭代之后。
根据我的经验,并行收集通常较慢,如果输入不够大:如果输入很小,则初始拆分和最后的“放在一起”不会得到回报。
再次基准测试,使用不同大小的列表(尝试30 000,100 000和1 000 000)。
此外,如果您进行数字处理,请考虑使用Array
(而不是List
)和while
(而不是map
)。这些对于底层JVM来说“更天然”(=更快),而在您的情况下,您可能正在测量垃圾收集器的性能。对于Array
,您可以将操作结果存储在“就位”。
答案 3 :(得分:0)
并行集合在执行花费一些时间的操作之前初始化线程。
因此,当您通过具有少量元素的并行集合执行操作或操作需要较少时间并行集合将执行较慢