scala并行集合处理的性能
Performance of scala parallel collection processing
我有需要一次处理数千条记录的场景。有时,它可能是数百条记录,可能高达 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
我做错了什么?这不是并行映射的正确方案吗?
问题是您正在执行的操作太快了(仅添加两个整数)以至于进行并行化的开销超过了收益。只有在操作速度较慢时,并行化才真正有意义。
这样想:如果你有 8 个朋友,你在一张纸上给每个人一个整数,让他们加一个,写下结果,然后还给你,你会在给他们下一个整数之前记录,你会花费很多时间来回传递消息,你本可以更快地完成所有添加。
此外:切勿对列表执行 .par
,因为并行化过程必须将整个列表复制到并行集合中,然后再将整个列表复制回来。如果您使用 Vector,那么它就不必做这些额外的工作。
如果您正在做基准测试,请考虑使用像 JMH 这样的东西来避免您可能遇到的所有可能的问题,如果您正在按照您的程序显示的方式进行测量。例如,JIT 可能会显着改变您的结果,但只会在一些迭代之后。
根据我的经验,如果输入不够大,并行集合通常较慢:如果输入较小,则初始拆分和最后的 "putting together" 不会得到回报。
因此再次进行基准测试,使用不同大小的列表(尝试 30 000、100 000 和 1 000 000)。
此外,如果您进行数值处理,请考虑使用Array
(而不是List
)和while
(而不是map
)。这些是底层 JVM 的 "more native"(= 更快),而在您的情况下,您可能正在测量垃圾收集器的性能。至于 Array
你可以存储操作的结果 "in place".
事实证明,并行化列表的开销比按顺序 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
我们可以推断(更好,猜测)对于具有 耗时 操作的大型集合,并行化集合的开销不会显着影响经过的时间,与顺序收集处理。
并行集合在执行需要一些时间的操作之前初始化线程。
因此,当您通过元素数量较少的并行集合执行操作或操作花费的时间较少时,并行集合的执行速度会变慢
我有需要一次处理数千条记录的场景。有时,它可能是数百条记录,可能高达 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
我做错了什么?这不是并行映射的正确方案吗?
问题是您正在执行的操作太快了(仅添加两个整数)以至于进行并行化的开销超过了收益。只有在操作速度较慢时,并行化才真正有意义。
这样想:如果你有 8 个朋友,你在一张纸上给每个人一个整数,让他们加一个,写下结果,然后还给你,你会在给他们下一个整数之前记录,你会花费很多时间来回传递消息,你本可以更快地完成所有添加。
此外:切勿对列表执行 .par
,因为并行化过程必须将整个列表复制到并行集合中,然后再将整个列表复制回来。如果您使用 Vector,那么它就不必做这些额外的工作。
如果您正在做基准测试,请考虑使用像 JMH 这样的东西来避免您可能遇到的所有可能的问题,如果您正在按照您的程序显示的方式进行测量。例如,JIT 可能会显着改变您的结果,但只会在一些迭代之后。
根据我的经验,如果输入不够大,并行集合通常较慢:如果输入较小,则初始拆分和最后的 "putting together" 不会得到回报。
因此再次进行基准测试,使用不同大小的列表(尝试 30 000、100 000 和 1 000 000)。
此外,如果您进行数值处理,请考虑使用Array
(而不是List
)和while
(而不是map
)。这些是底层 JVM 的 "more native"(= 更快),而在您的情况下,您可能正在测量垃圾收集器的性能。至于 Array
你可以存储操作的结果 "in place".
事实证明,并行化列表的开销比按顺序 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
我们可以推断(更好,猜测)对于具有 耗时 操作的大型集合,并行化集合的开销不会显着影响经过的时间,与顺序收集处理。
并行集合在执行需要一些时间的操作之前初始化线程。
因此,当您通过元素数量较少的并行集合执行操作或操作花费的时间较少时,并行集合的执行速度会变慢