Java parallelStream 最有效的列表类型是什么?
What is the most efficient List-Type for Java parallelStream?
我有一个 List<String> toProcess
我想用
进一步处理
toProcess.parallelStream().map(/*some function*/).collect(Collectors.toList());
哪种列表类型(如 LinkedList、ArrayList 等)是初始列表从多线程中获得最佳速度的最佳选择?
附加信息: 预期的元素数量范围为 10^3-10^5,但单个元素可能会变得相当大 (10^5-10 ^6 个字符)。
或者我可以在所有地方使用 String[]
,因为字符串的数量保证不会改变(results 将包含与 [=26 一样多的元素=]toProcess).
无论哪种方式,我都必须在最后按顺序遍历所有元素。目前我使用 foreach
循环来 assemble 最终结果。这可以很容易地更改为常规 for
循环。
考虑到上下文切换的成本和一般的多线程。在一种列表类型之间切换的性能提升通常真的微不足道。即使您使用次优列表 - 也没关系。
如果您 真的 关心,那么 ArrayList
因为缓存局部性 可能 会做得更好工作,但这取决于。
一般来说,ArrayList
比 LinkedList
对并行化更友好,因为数组很容易拆分成多个部分交给每个线程。
但是,由于您的终端操作是将结果写入文件,并行化可能对您根本没有帮助,因为您可能会受到 IO 的限制,而不是 CPU。
如果您确定输出元素的数量等于输入元素的数量,并且您对数组结果感到满意,那么一定要使用 toArray
而不是收集器。如果整个管道的大小都是固定的,目标数组将被预先分配正确的大小,并行操作将它们的结果直接存放到目标数组的正确位置:没有复制、重新分配或合并。
如果您想要 List
,您始终可以使用 Arrays.asList
包装结果,但当然您不能向结果添加或删除元素。
收藏家
如果上述条件之一不成立,那么您需要与收集器打交道,它们有不同的权衡。
收集器通过以 thread-confined 方式对中间结果进行操作来并行工作。然后将中间结果合并到最终结果中。有两个操作需要考虑:1) 将单个元素累加到中间结果中,以及 2) 将中间结果合并(或合并)为最终结果。
在 LinkedList
和 ArrayList
之间,ArrayList
可能更快,但您应该对其进行基准测试以确保。请注意,Collectors.toList
默认使用 ArrayList
,尽管这可能会在未来的版本中更改。
链表
累积的每个元素 (LinkedList.add
) 涉及分配一个新的列表节点并将其挂接到列表的末尾。将节点挂接到列表非常快,但这涉及到为每个流元素分配,随着累积的进行,这可能会产生少量垃圾 collections。
合并 (LinkedList.addAll
) 也相当昂贵。第一步是将源列表转换为数组;这是通过遍历列表的每个节点并将元素存储到临时数组中来完成的。然后,代码遍历这个临时数组并将每个元素添加到目标列表的末尾。如上所述,这会导致为每个元素分配一个新节点。因此,合并操作非常昂贵,因为它遍历源列表中的每个元素 两次 并为每个元素分配空间,这可能会引入垃圾 collection 开销。
数组列表
每个元素的累积通常涉及将其附加到 ArrayList
中包含的数组的末尾。这通常很快,但如果数组已满,则必须重新分配并复制到更大的数组中。 ArrayList
的增长策略是分配新数组比当前数组大 50%,因此重新分配的发生与添加的元素数量的对数成正比,这还算不错。但是,必须复制所有元素,这意味着可能需要多次复制较早的元素。
合并 ArrayList
可能比 LinkedList
便宜得多。将 ArrayList
转换为数组涉及将源中的元素批量复制(而不是 one-at-a-time)到临时数组中。必要时调整目标数组的大小(在这种情况下很可能),需要所有元素的批量复制。源元素然后 bulk-copied 从临时数组到目标,已经 pre-sized 容纳它们。
讨论
鉴于以上情况,ArrayList
似乎比 LinkedList
快。但是,即使 collection 到 ArrayList
也需要对许多元素进行一些不必要的重新分配和复制,可能需要多次。一个潜在的未来优化是 Collectors.toList
将元素累积到一个数据结构中,该数据结构针对 fast-append 访问进行了优化,最好是已经 pre-sized 以容纳预期元素数量的数据结构。支持快速合并的数据结构也是可能的。
如果您需要做的只是迭代最终结果,那么滚动您自己的具有这些属性的数据结构应该不会太困难。如果不需要 full-blown 列表,应该可以进行显着简化。它可以累积到 pre-sized 列表中以避免重新分配,合并只会将它们聚集到树结构或 list-of-lists 中。有关想法,请参阅 JDK 的 SpinedBuffer(私有实现 class)。
我有一个 List<String> toProcess
我想用
toProcess.parallelStream().map(/*some function*/).collect(Collectors.toList());
哪种列表类型(如 LinkedList、ArrayList 等)是初始列表从多线程中获得最佳速度的最佳选择?
附加信息: 预期的元素数量范围为 10^3-10^5,但单个元素可能会变得相当大 (10^5-10 ^6 个字符)。
或者我可以在所有地方使用 String[]
,因为字符串的数量保证不会改变(results 将包含与 [=26 一样多的元素=]toProcess).
无论哪种方式,我都必须在最后按顺序遍历所有元素。目前我使用 foreach
循环来 assemble 最终结果。这可以很容易地更改为常规 for
循环。
考虑到上下文切换的成本和一般的多线程。在一种列表类型之间切换的性能提升通常真的微不足道。即使您使用次优列表 - 也没关系。
如果您 真的 关心,那么 ArrayList
因为缓存局部性 可能 会做得更好工作,但这取决于。
一般来说,ArrayList
比 LinkedList
对并行化更友好,因为数组很容易拆分成多个部分交给每个线程。
但是,由于您的终端操作是将结果写入文件,并行化可能对您根本没有帮助,因为您可能会受到 IO 的限制,而不是 CPU。
如果您确定输出元素的数量等于输入元素的数量,并且您对数组结果感到满意,那么一定要使用 toArray
而不是收集器。如果整个管道的大小都是固定的,目标数组将被预先分配正确的大小,并行操作将它们的结果直接存放到目标数组的正确位置:没有复制、重新分配或合并。
如果您想要 List
,您始终可以使用 Arrays.asList
包装结果,但当然您不能向结果添加或删除元素。
收藏家
如果上述条件之一不成立,那么您需要与收集器打交道,它们有不同的权衡。
收集器通过以 thread-confined 方式对中间结果进行操作来并行工作。然后将中间结果合并到最终结果中。有两个操作需要考虑:1) 将单个元素累加到中间结果中,以及 2) 将中间结果合并(或合并)为最终结果。
在 LinkedList
和 ArrayList
之间,ArrayList
可能更快,但您应该对其进行基准测试以确保。请注意,Collectors.toList
默认使用 ArrayList
,尽管这可能会在未来的版本中更改。
链表
累积的每个元素 (LinkedList.add
) 涉及分配一个新的列表节点并将其挂接到列表的末尾。将节点挂接到列表非常快,但这涉及到为每个流元素分配,随着累积的进行,这可能会产生少量垃圾 collections。
合并 (LinkedList.addAll
) 也相当昂贵。第一步是将源列表转换为数组;这是通过遍历列表的每个节点并将元素存储到临时数组中来完成的。然后,代码遍历这个临时数组并将每个元素添加到目标列表的末尾。如上所述,这会导致为每个元素分配一个新节点。因此,合并操作非常昂贵,因为它遍历源列表中的每个元素 两次 并为每个元素分配空间,这可能会引入垃圾 collection 开销。
数组列表
每个元素的累积通常涉及将其附加到 ArrayList
中包含的数组的末尾。这通常很快,但如果数组已满,则必须重新分配并复制到更大的数组中。 ArrayList
的增长策略是分配新数组比当前数组大 50%,因此重新分配的发生与添加的元素数量的对数成正比,这还算不错。但是,必须复制所有元素,这意味着可能需要多次复制较早的元素。
合并 ArrayList
可能比 LinkedList
便宜得多。将 ArrayList
转换为数组涉及将源中的元素批量复制(而不是 one-at-a-time)到临时数组中。必要时调整目标数组的大小(在这种情况下很可能),需要所有元素的批量复制。源元素然后 bulk-copied 从临时数组到目标,已经 pre-sized 容纳它们。
讨论
鉴于以上情况,ArrayList
似乎比 LinkedList
快。但是,即使 collection 到 ArrayList
也需要对许多元素进行一些不必要的重新分配和复制,可能需要多次。一个潜在的未来优化是 Collectors.toList
将元素累积到一个数据结构中,该数据结构针对 fast-append 访问进行了优化,最好是已经 pre-sized 以容纳预期元素数量的数据结构。支持快速合并的数据结构也是可能的。
如果您需要做的只是迭代最终结果,那么滚动您自己的具有这些属性的数据结构应该不会太困难。如果不需要 full-blown 列表,应该可以进行显着简化。它可以累积到 pre-sized 列表中以避免重新分配,合并只会将它们聚集到树结构或 list-of-lists 中。有关想法,请参阅 JDK 的 SpinedBuffer(私有实现 class)。