4 回答

TA贡献1772条经验 获得超8个赞
使用有一些缺点Arrays.parallelSort
它使用
ForkJoinPool.commonPool()
并且会与默认使用它的其他功能战斗(例如parallel()
在流上)线程池
Arrays.parallelSort
的使用是不可配置的(只能通过增加公共池线程数量在全局级别上使用)它在小数据集上表现更差(数组通常包含很少的元素,JDK 甚至承认,例如,大多数元素
ArrayList
在其整个生命周期中都保持为空,这节省了相当多的内存和 CPU 时间,因为不实例化永远不会填充的数组)
另一个轶事场景:假设您实现了一些需要排序的纸牌游戏。将多个游戏并行执行非常容易,而不是将一次运行的排序机制并行化,后者可能只占用整个游戏循环的一小部分。您现在失去了一种简单的并行化方法(例如,在遗传算法的上下文中运行游戏时)。
但是,是的,如果您碰巧有大型数组并且排序是应用程序运行时的重要组成部分,请使用Arrays.parallelSort
.
编辑:如果给定的数组少于 4096 个元素,即使Arrays.parallelSort
切换到正常排序:这都是为了显示意图——如果可能的话,你想要一个并行排序,它与仅仅调用 . 具有不同的含义sort
。挑剔一点:它确实在小数组上表现更差,因为它必须额外检查数组是否包含少于 4096 个元素,以及一些关于公共池线程数的其他检查(开销当然可以忽略不计):) .

TA贡献1789条经验 获得超10个赞
stream()这与何时使用vs 的问题没有太大区别parallelStream()——这取决于您拥有多少数据。当然,大多数时间,当并行排序 10 个元素时,将被底层的线程框架(文档未指定)消耗,而不是排序本身。
但是您也想知道为什么引入 IMO 此类方法。硬件正在(已经移动?)转向许多CPU,而不是更多GHz,因此对于任何希望在未来 20 年内仍然存在的语言来说,并行处理只是一个正常过程。
至于你实际需要多少数据才能表现出性能,parallelSort再加上sort知道我们至少 MIN_ARRAY_SORT_GRAN + 1需要获得任何潜在的好处;编写一个适当的测试来证明对于这个特定的设置和运行,你至少需要X数字,并不那么复杂。您还必须考虑到某些数组可能已经排序(进一步解释),而某些数组可能完全未排序(5,4,3,2,1例如),这会给第二个数组带来一些惩罚。
获取一些随机数据并进行测试:
@Warmup(iterations = 10)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Measurement(iterations = 2, time = 2, timeUnit = TimeUnit.SECONDS)
public class ParallelSort {
public static void main(String[] args) throws Exception {
Options opt = new OptionsBuilder()
.include(ParallelSort.class.getName())
.build();
new Runner(opt).run();
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@Fork(1)
public int[] parallel(ParallelSortExecutionPlan plan) {
Arrays.parallelSort(plan.ints());
return plan.ints();
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@Fork(1)
public int[] nonParallel(ParallelSortExecutionPlan plan) {
Arrays.sort(plan.ints());
return plan.ints();
}
}
@State(Scope.Benchmark)
public class ParallelSortExecutionPlan {
@Param(value = {"10", "100", "1000", "10000", "100000", "1000000"})
private int howMany;
private int[] ints;
public static void main(String[] args) {
}
@Setup(Level.Invocation)
public void setUp() {
ints = new int[howMany];
for (int i = 0; i < howMany; ++i) {
ints[i] = ThreadLocalRandom.current().nextInt();
}
}
int[] ints() {
return ints;
}
}
请注意第二个类正在使用@Setup(Level.Invocation)(如果你知道一点JMH)——这是一个非常锋利的工具;Invocation但我使用它是因为我希望每个方法都有一个未排序的数组。否则,如果Trial将被使用例如 - 只有第一次调用将是一个未排序的数组,该@Benhcmark方法的所有其他调用都已经排序。为了好玩,您可以将该单行更改为@Setup(Level.Trial)例如并查看结果,它们将毫无意义。
运行此显示:
Benchmark (howMany) Mode Cnt Score Error Units
ParallelSort.nonParallel 10 avgt 2 128.847 ns/op
ParallelSort.parallel 10 avgt 2 116.656 ns/op
ParallelSort.nonParallel 100 avgt 2 1956.746 ns/op
ParallelSort.parallel 100 avgt 2 1963.335 ns/op
ParallelSort.nonParallel 1000 avgt 2 32162.611 ns/op
ParallelSort.parallel 1000 avgt 2 31716.915 ns/op
ParallelSort.nonParallel 10000 avgt 2 423531.663 ns/op
ParallelSort.parallel 10000 avgt 2 201802.609 ns/op
ParallelSort.nonParallel 100000 avgt 2 6503511.987 ns/op
ParallelSort.parallel 100000 avgt 2 1363169.661 ns/op
ParallelSort.nonParallel 1000000 avgt 2 69058738.586 ns/op
ParallelSort.parallel 1000000 avgt 2 13469112.930 ns/op
对我来说几乎是一个非常期待的输出。

TA贡献1876条经验 获得超6个赞
不,我会对足够小的阵列说不。设置线程的开销不会导致明显的加速。
关键是“够小”。它不会对所有问题都有相同的答案。
永远不应该应用教条,除非是在这个教条规则的情况下。就像我们唯一不应该容忍的是不宽容一样。某处存在波普尔悖论。

TA贡献1827条经验 获得超4个赞
除了公共池使用和可优化的最小大小等原因之外,如果您通常有许多事务需要并行进行排序,则您可能也不需要并行化单个排序。
在那种情况下,您可以通过拆分工作包来避免开销。(然而,具有可配置并行工作的可控执行器也适用于多线程提交——您只需增加停放线程和上下文切换的数量)
添加回答
举报