带有reduce()的Java parallelStream()没有提高性能

时间:2015-01-23 04:47:04

标签: java optimization concurrency java-8

为了测试Java 8的流和自动并行化的新实现,我运行了以下简单测试:

ArrayList<Integer> nums = new ArrayList<>();
for (int i=1; i<49999999; i++) nums.add(i);

int sum=0;
double begin, end;

begin = System.nanoTime();
for (Integer i : nums) sum += i;
end = System.nanoTime();

System.out.println( "1 core: " + (end-begin) );

begin = System.nanoTime();
sum = nums.parallelStream().reduce(0, Integer::sum);
end = System.nanoTime();

System.out.println( "8 cores: " + (end-begin) );

我认为总结一系列整数将能够充分利用所有8个核心,但输出看起来像这样:

1 core: 1.70552398E8
8 cores: 9.938507635E9

我知道nanoTime()在多核系统中存在问题,但我怀疑这是问题,因为我关闭了一个数量级。

我的操作是否如此简单,以至于reduce()所需的开销是否克服了多核的优势?

2 个答案:

答案 0 :(得分:2)

您的流示例有2个取消装箱(Integer.sum(int,int))和一个装箱(结果int必须转换回Integer)每个号码,而for循环只有一个拆箱。所以两者没有可比性。

当您计划使用整数进行计算时,最好使用IntStream

nums.stream().mapToInt(i -> i).sum();

这会给你一个类似于for循环的性能。我的机器上的并行流仍然较慢。

最快的选择是这个顺便说一句:

IntStream.rangeClosed(0, 49999999).sum();

更快的顺序,没有首先构建列表的开销。当然,它只是这个特殊用例的替代品。但它表明重新思考现有方法而不仅仅是“添加流”是值得的。

答案 1 :(得分:1)

要在所有中正确比较,您需要为这两个操作使用类似的开销。

    ArrayList<Integer> nums = new ArrayList<>();
    for (int i = 1; i < 49999999; i++)
        nums.add(i);

    int sum = 0;
    long begin, end;

    begin = System.nanoTime();
    sum = nums.stream().reduce(0, Integer::sum);
    end = System.nanoTime();

    System.out.println("1 core: " + (end - begin));

    begin = System.nanoTime();
    sum = nums.parallelStream().reduce(0, Integer::sum);
    end = System.nanoTime();

    System.out.println("8 cores: " + (end - begin));

这让我感动

1 core:  769026020
8 cores: 538805164

parallelStream()实际上更快。 (注意:我只有4个核心,但parallelSteam()并不总是使用所有核心)

另一件事是拳击和拆箱。有nums.add(i)的装箱,以及进入Integer::sum的所有内容的拆箱,需要两个int秒。我将此测试转换为数组以删除它:

    int[] nums = new int[49999999];
    System.err.println("adding numbers");
    for (int i = 1; i < 49999999; i++)
        nums[i - 1] = i;

    int sum = 0;
    System.err.println("begin");
    long begin, end;

    begin = System.nanoTime();
    sum = Arrays.stream(nums).reduce(0, Integer::sum);
    end = System.nanoTime();

    System.out.println("1 core: " + (end - begin));

    begin = System.nanoTime();
    sum = Arrays.stream(nums).parallel().reduce(0, Integer::sum);
    end = System.nanoTime();

    System.out.println("8 cores: " + (end - begin));

这给出了意想不到的时间:

1 core:   68050642
8 cores: 154591290

对于具有常规整数的线性减少,速度快得多(1-2个数量级),但是并行减少仅约1/4的时间并且最终变慢。 我不确定为什么会这样,但它确实很有趣!

进行了一些分析,结果是fork()执行并行流的方法非常昂贵,因为使用了ThreadLocalRandom,它为网络接口调用它的种子!这非常慢,这是parallelStream()慢于stream()的唯一原因!

我的一些VisualVM数据:(忽略await()时间,这是我使用的方法,因此我可以跟踪程序) 对于第一个示例:https://www.dropbox.com/s/z7qf2es0lxs6fvu/streams1.nps?dl=0 第二个例子:https://www.dropbox.com/s/f3ydl4basv7mln5/streams2.nps?dl=0

TL; DR:在您的Integer情况下,它看起来像是并行获胜,但是int情况会导致并行速度变慢。