基准性能测试:Fork/Join Framework vs. Parallel Streams vs. ExecutorService

大约十年前,Java只有通过第三方库才能实现并发。Java5中引入了java.util.concurrent作为语言的一部分。可以使用ExecutorService轻松的操作线程池。java.util.concurrent不断演进,在Java 7中基于ExecutorService引入了Fork/Join框架。Java8的streams相当于提供了一个简单的方法来使用Fork/Join。来看看它们的对比。 我们有俩个任务,一个cpu密集型和另一个io密集型,并测试了4个具有相同功能的不同场景:一个重要的因素是使用的线程数量。我们使用的机器有8个核心,所以测试了4、8、16和32个线程的变化。

为一个6G大小5.8兆行的文本文件编排索引

这个测试中,我们生成了一个巨大的文本文件,并有一个类似编排索引的实现。结果如下: 输入图片说明

单线程执行:176,267msec,接近3分钟。

1. 较少的线程会使cpu未充分利用,过多的线程会增加开销。

4-16线程之间,因为有些线程在文件IO中阻塞,添加线程数量比添加核数更好。当使用32线程时,由于额外的开销,性能变差。

2. Parallel Streams是性能最好的,几乎比直接使用Fork/Join快了一秒。

我们看到parallel streams不仅有语法糖(比如lambda表达式),性能也比Fork/Join和ExecutorService更好。

3.但是,Parallel Streams也是唯一有超过30秒的情况

这提醒我们,如果是使用较小的线程数,直接用Fork/Join比用parallel streams更好。

4.处理IO密集型任务时不要使用pool的默认大小。

当使用Parallel Streams pool的默认池大小时,与核心数相同的线程(8个)比16个线程版本差了将近2秒。这与IO线程阻塞有关,有空闲的线程。所以引入更多的线程可以更好利用CPU,而其他线程则等待调度而不是空闲。 如何更改Parallel Streams的默认Fork/Join池大小?可以使用JVM参数更改公共Fork/Join池大小:

-Djava.util.concurrent.ForkJoinPool.common.parallelism = 16

(所有的Fork/Join任务都是使用一个公共的静态池,默认情况下是内核的数量。这里的好处是通过在没有使用的时间段内回收其他任务的线程来减少资源的使用。) 或者,可以使用自定义的Fork / Join池。这将覆盖默认的公共Fork/Join池。

4.单线程性能比最佳结果差7.25倍。

使用并行提供了7.25倍的速度,因为机器有8个核心,它非常接近理论8倍的预测!我们可以将其余的部分归因于开销。

检查一个数字是否为质数

在这轮测试中,完全去掉IO,测试需要多长时间才能确定一个19位的数字是否是质数。算法没有使用任何优化,它是一个质数,所以每个实现的计算量相同。 输入图片说明

单线程执行:118,127msec,接近2分钟。

1. 8和16线程之间的差异很小。

除了Fork/Join,8和16线程的性能基本上是相似的。

2. 所有方法的最佳结果相似。

所有实现都有大约28秒的最佳结果。这并不意味着我们对使用哪种方法漠不关心。看看下面。

3. Parallel Streams比其他实现更好地处理线程过多。

运行16个线程的最好的结果是Parallel Streams。此外,Parallel Streams的其它结果也很好。

4. 单线程性能比最佳结果差4.2倍。

背景

为了运行这个测试,我们使用了一个有8个vCPU和15GB RAM的EC2 c3.2xlarge实例。vCPU意味着有超线程,所以实际上我们在这里有4个物理核心。就OS调度器而言,这里有8个内核。为了尽可能的公平,每个实现都运行了10次,结果取第2到第9的平均数。

猜你喜欢

转载自my.oschina.net/wangbo888/blog/1635744