我正在阅读有关无国籍的信息,并在doc中发现了这一点:
如果以下原因导致流管道结果可能不确定或不正确 流操作的行为参数是有状态的。一种 有状态的lambda(或其他实现了适当的 功能接口)是一个其结果取决于任何状态的接口 在执行流水线期间可能会发生变化。
现在,如果我有一个字符串列表(例如,strList
),然后尝试使用并行流通过以下方式从其中删除重复的字符串:
List<String> resultOne = strList.parallelStream().distinct().collect(Collectors.toList());
或者如果我们希望不区分大小写:
List<String> result2 = strList.parallelStream().map(String::toLowerCase)
.distinct().collect(Collectors.toList());
此代码是否有问题,因为并行流会拆分输入,并且在一个块中截然不同并不一定意味着在整个输入中截然不同?
distinct
是一个有状态操作,在有状态中间操作的情况下,并行流可能需要多次通过或大量缓冲开销。如果元素的顺序无关紧要,distinct
也可以更有效地实现。
同样按照doc:
对于有序流,不同元素的选择是稳定的(对于 重复的元素,该元素在遭遇中首先出现 保留顺序。)对于无序流,没有稳定性保证 制成。
但是在有序流并行运行的情况下,distinct可能不稳定-意味着在重复的情况下它将保留任意元素,而不必像distinct
那样保留第一个元素。
来自link:
在内部,distinct()操作保留一个Set,该Set包含 以前已经看到过的元素,但是它被埋在 操作,我们无法从应用程序代码中获取它。
因此,在并行流的情况下,可能会消耗整个流或可能使用CHM(例如ConcurrentHashMap.newKeySet()
)。对于有序的对象,最有可能会使用LinkedHashSet
或类似的结构。
答案 0 :(得分:11)
粗略指出doc
(重点增强)的相关部分:
中间操作又分为无状态操作和无状态操作 状态操作。无状态操作,例如过滤器和地图, 处理新的元素时不保留先前看到的元素的状态 元素-每个元素都可以独立于操作进行处理 在其他元素上。 状态操作,例如不同的和已排序的, 在处理时可能会合并先前看到的元素的状态 新元素
状态操作可能需要先处理所有输入 产生结果。例如,一个人不能从 对流进行排序,直到看到流的所有元素为止。 作为 结果,在并行计算下,一些包含有状态的管道 中间操作可能需要多次通过数据,或者可能 需要缓冲大量数据。仅包含管道 无状态中间操作可以一次处理, 无论是顺序的还是并行的,具有最少的数据缓冲
如果您进一步阅读(关于订购的部分):
流可能有也可能没有定义的遇到顺序。是否 流的遭遇顺序取决于来源和 中间作业。 某些流源(例如列表或 数组)在本质上是有序的,而其他数组(例如HashSet) 不是。一些中间操作(例如sorted())可能会施加 在原本无序的流上遇到订单,其他人可能会 使无序流呈现有序,例如BaseStream.unordered()。 此外,某些终端操作可能会忽略遇到顺序,例如 forEach()。
...
对于并行流,有时可以放宽排序约束 使执行效率更高。 某些汇总操作,例如 过滤重复项(distinct())或分组归约 如果(Collectors.groupingBy())可以更有效地实现 元素的顺序不相关。同样, 本质上与遇到顺序有关,例如limit()可能需要 缓冲以确保适当的订购,从而损害了 并行性。 如果流具有遇到顺序,但 用户并不特别在乎那个碰头顺序 使用unordered()对流进行排序可以改善并行 有状态或终端操作的性能。但是,大多数 流管道,例如上面的“块权重之和”示例, 即使在排序约束下仍然可以有效地并行化。
最后,
unordered()
,则distinct
不必担心对输出进行排序,因此效率很高解决方案是,如果您不担心定单并希望获得更高的性能,则将.unordered()
添加到流管道中。
List<String> result2 = strList.parallelStream()
.unordered()
.map(String::toLowerCase)
.distinct()
.collect(Collectors.toList());
A,Java中没有(可用的内置)并发哈希集(除非他们对ConcurrentHashMap
很聪明),所以我只能为您留下一个遗憾,即使用常规Java以阻塞的方式实现了distinct组。在这种情况下,我看不到做并行的区别有什么好处。
编辑:我讲得太早了。使用具有独特性的并行流可能会有一些好处。看来distinct
的实现比我最初想象的要聪明得多。参见@Eugene's answer。
答案 1 :(得分:3)
这不会有问题(问题有可能是错误的结果),但正如API注释所说
在并行管道中为distinct()保持稳定性是相对昂贵的
但是如果要关注性能并且如果stability不是问题(即结果相对于所处理的集合而言元素的顺序不同),则请遵循API的说明
使用BaseStream.unordered()删除排序约束 导致在 并行管道,
我想为什么不对distinct
的并行流和顺序流的性能进行基准测试
public static void main(String[] args) {
List<String> strList = Arrays.asList("cat", "nat", "hat", "tat", "heart", "fat", "bat", "lad", "crab", "snob");
List<String> words = new Vector<>();
int wordCount = 1_000_000; // no. of words in the list words
int avgIter = 10; // iterations to run to find average running time
//populate a list randomly with the strings in `strList`
for (int i = 0; i < wordCount; i++)
words.add(strList.get((int) Math.round(Math.random() * (strList.size() - 1))));
//find out average running times
long starttime, pod = 0, pud = 0, sod = 0;
for (int i = 0; i < avgIter; i++) {
starttime = System.currentTimeMillis();
List<String> parallelOrderedDistinct = words.parallelStream().distinct().collect(Collectors.toList());
pod += System.currentTimeMillis() - starttime;
starttime = System.currentTimeMillis();
List<String> parallelUnorderedDistinct =
words.parallelStream().unordered().distinct().collect(Collectors.toList());
pud += System.currentTimeMillis() - starttime;
starttime = System.currentTimeMillis();
List<String> sequentialOrderedDistinct = words.stream().distinct().collect(Collectors.toList());
sod += System.currentTimeMillis() - starttime;
}
System.out.println("Parallel ordered time in ms: " + pod / avgIter);
System.out.println("Parallel unordered time in ms: " + pud / avgIter);
System.out.println("Sequential implicitly ordered time in ms: " + sod / avgIter);
}
上面的内容是由open-jdk 8编译的,并在i3第六代(4个逻辑内核)上的openjdk的jre 8(没有特定于jvm的参数)上运行,我得到了这些结果
看起来像是经过一定的否定。在元素数量中,有序并行速度更快,而具有讽刺意味的是,无序并行速度最慢。其背后的原因(感谢@Hulk)是由于其实现方式(使用HashSet)。因此,通常的规则是,如果您有几个元素并且大量重复了几个数量级,您可能会受益于{ {1}}。
1)
parallel()
2)
Parallel ordered time in ms: 52
Parallel unordered time in ms: 81
Sequential implicitly ordered time in ms: 35
3)
Parallel ordered time in ms: 48
Parallel unordered time in ms: 83
Sequential implicitly ordered time in ms: 34
无序并行比两者慢两倍。
然后我将Parallel ordered time in ms: 36
Parallel unordered time in ms: 70
Sequential implicitly ordered time in ms: 32
提升到wordCount
,这些就是结果
1)
5_000_000
2)
Parallel ordered time in ms: 93
Parallel unordered time in ms: 363
Sequential implicitly ordered time in ms: 123
3)
Parallel ordered time in ms: 100
Parallel unordered time in ms: 363
Sequential implicitly ordered time in ms: 124
然后转到Parallel ordered time in ms: 89
Parallel unordered time in ms: 365
Sequential implicitly ordered time in ms: 118
1)
10_000_000
2)
Parallel ordered time in ms: 148
Parallel unordered time in ms: 725
Sequential implicitly ordered time in ms: 218
3)
Parallel ordered time in ms: 150
Parallel unordered time in ms: 749
Sequential implicitly ordered time in ms: 224
答案 2 :(得分:3)
您似乎错过了提供的文档和实际示例中的很多内容。
如果流操作的行为参数是有状态的,则流管道的结果可能不确定或不正确。
在您的示例中,您没有定义任何有状态的操作。文档中的有状态是指您定义的内容,而不是jdk
本身实现的内容-例如您的示例中的distinct
。但是无论哪种方式,您都可以定义正确的有状态操作,甚至Stuart Marks - working at Oracle/Java, provides such an example。
因此,无论您是否并行,在所提供的示例中您都还可以。
distinct
(并行)的昂贵部分来自于以下事实:内部必须有一个线程安全的数据结构,该结构应保留不同的元素;在jdk中,如果顺序无关紧要,则使用ConcurrentHashMap
;在顺序重要时,则使用LinkedHashSet
进行归约。
distinct
btw是一个非常聪明的实现,它查看流的源是否已经不同(在这种情况下,它是无操作的),或者查看数据是否已排序,在这种情况下它将对源进行更智能的遍历(因为它知道如果您已经看到一个元素,那么接下来的元素要么是您刚刚看到的元素,要么是另一个元素),或者在内部使用ConcurrentHashMap
等
答案 3 :(得分:1)
在javadocs中,parallelStream()
返回一个可能并行Stream,并将此集合作为源。 该方法允许返回顺序流。
性能:
ArrayList<T>
具有基于数组的内部数据表示。或者LinkedList<T>
,它需要更多的计算才能并行处理拆分。在这种情况下,ArrayList<T>
更好!stream.unordered().parallel().distinct()
的性能优于stream.parallel().distinct()
在并行管道中保持distinct()的稳定性是 相对昂贵(要求该操作作为完整 障碍,并有大量的缓冲开销。
因此,就您而言,应该没问题(除非您的List<T>
不在意订单)。请阅读下面的说明,
假设您在ArrayList中有4个元素, {“ a”,“ b”,“ a”,“ b”}
现在,如果您不要在调用parallelStream()
之前使用distinct()
,则仅保留位置0和1处的字符串。(保留顺序,顺序流)< / p>
否则,(如果您使用parallelStream().distinct()
),则可以将元素1和2 保留为不重复(它是不稳定的,但结果是相同的{“ a,” b“ },甚至可以是{“ b”,“ a”})。
不稳定的独特操作会随机消除重复项。
最后,
在并行计算下,某些管道包含有状态 中间操作可能需要多次通过数据,或者可能 需要缓冲重要数据