Parallel Seq对于执行一系列语句有多大的好处?

5

我有一个使用List.par的小程序。

val x = List(1,2,3,4,5).par.map(y => {
    Thread.sleep(2000)
    println(y)
    y + 1
})

println(x)

输出:

3
1
4
5
2
ParVector(2, 3, 4, 5, 6)

数字将并行打印,但返回值始终保持其顺序。
我的目标是在SQL数据库中并行执行一系列insert语句。
目前我正在使用for comprehension。随着语句数量的增加,我想使用ParSeq。
但我担心它会导致性能下降。(如果map实现中有额外的代码来保持其顺序,那么这将是一个性能负担)。
请建议我如何做到这一点。

2
如果想要获得最好的性能,你应该寻找 SQL 批量插入而不是在短暂的线程中运行每个插入。 - francoisr
是的,但不幸的是,我不能使用批量插入,因为我在生产中使用相同的 SQL 数据库代码和内存数据库进行测试。 - Shantiswarup Tunga
2
@ShantiswarupTunga,我只是在分享我的意见,如果测试驱动了你的应用程序能够做什么,那么我认为需要重新考虑如何进行测试。尤其是对于 db wrappers(数据库包装器),我得出的结论是最好不要对它们进行单元测试,而是针对真实的数据库运行集成测试,并为开发测试简化流程,我建议使用Testcontainers - Luis Miguel Mejía Suárez
1个回答

6

文档("语义"部分)解释说,只有两种可能会导致乱序行为:

  1. 副作用操作可能导致不确定性。
  2. 非结合操作导致不确定性。

第一种情况可以通过println语句自行观察得到。第二种情况可以通过使用非结合二元操作,例如减法进行简单的测试:

val list = (1 to 100).toList
val a = list.par.reduce(_ - _)

println(a) 

试着运行上面的代码片段几次。

整数列表可以由多个工作线程并行映射,因为元素之间互不依赖。每个工作线程都可以在原地执行操作,而不影响任何其他元素。因此,即使起初可能不太直观,这种处理也会从并行化中受益(但要想注意到改进,您可能需要更多的元素)。

然而,如果使用非关联操作,同一列表就不能并行缩减,因为元素彼此有依赖,如下所示:

1 - (2 - (3 - 4))

或者

((1 - 2) - 3) - 4

这就是为什么集合的并行处理通常支持reducefold,但不支持foldLeftfoldRight


对我来说,保留顺序并不是必要的,因为它将进行插入操作。但是,如果在语句执行后,它试图重新排列到其原始顺序,那么这会导致性能开销。我的问题是,相比于For Comprehension,采用ParSeq是否是一个好的方法? - Shantiswarup Tunga
您还可以提出执行插入语句序列的良好方法建议。 - Shantiswarup Tunga
3
不要担心内部实现,它是原地进行操作的。没有重新排序,因为没有洗牌。想象一下排队等待的100个人的队列。另外100个人并行接近,每个新来者给队列中的一个人1美元并离开。现在队列中的每个人都比之前多了1美元,所有操作都是并行完成的,并且队列内的顺序被保留。 - slouc
3
我不能评论插入语句,因为这是一个更大的主题,我没有所有的细节(而且说实话,这是完全不同的问题)。但一般来说,使用.par 应该可以解决问题。而且,由于与数据库交互具有副作用,在任何情况下你的结果很可能会无序,所以不用担心映射列表的情况。 - slouc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接