Stream.iterate 和 LongStream.rangeClosed 并行处理

分别采用 for循环、Stream.iterate及其并行版本、LongStream.rangeClosed及其并行版本 来测试顺序加法器函数对前一千万个自然数求和要用多久:

import java.util.function.Function;
import java.util.stream.LongStream;
import java.util.stream.Stream;

public class StreamTest7 {

    public static void main(String[] args) {
        int i = Runtime.getRuntime().availableProcessors();

        // for
        System.out.println(measureSumPerf(StreamTest7::iterativeSum, 10_000_000) + " msecs");
        // Stream.iterate
        System.out.println(measureSumPerf(StreamTest7::sequentialSum, 10_000_000) + " msecs");
        // parallel Stream.iterate
        System.out.println(measureSumPerf(StreamTest7::parallelSum, 10_000_000) + " msecs");
        // LongStream.rangeClosed
        System.out.println(measureSumPerf(StreamTest7::rangedSum, 10_000_000) + " msecs");
        // parallel LongStream.rangeClosed
        System.out.println(measureSumPerf(StreamTest7::parallelRangedSum, 10_000_000) + " msecs");
    }

    public static long parallelRangedSum(long n) {
        return LongStream.rangeClosed(1, n)
                .parallel()
                .reduce(0L, Long::sum);
    }

    public static long rangedSum(long n) {
        return LongStream.rangeClosed(1, n)
                .reduce(0L, Long::sum);
    }


    public static long parallelSum(long n) {
        return Stream.iterate(0L, i -> i + 1)
                .limit(n)
                .parallel()
                .reduce(0L ,Long::sum);
    }


    public static long sequentialSum(long n) {
        // 生成自然数无限流
        return Stream.iterate(0L, i -> i + 1)
                // 限制到前n个数
                .limit(n)
                // 对所有数字求和来归纳流
                .reduce(0L, Long::sum);
    }

    public static long iterativeSum(long n) {
        long result = 0;
        for (long i = 1L; i <= n; i++) {
            result += i;
        }
        return result;
    }

    public static long measureSumPerf(Function<Long, Long> adder, long n) {
        long fastest = Long.MAX_VALUE;
        for (int i = 0; i < 10; i++) {
            long start = System.nanoTime();
            long sum = adder.apply(n);
            long duration = (System.nanoTime() - start) / 1_000_000;
            if (duration < fastest) fastest = duration;
        }
        return fastest;
    }

}

执行结果:
执行结果

结果相当令人失望,Stream.iterate求和方法的并行版本比顺序版本要慢很多

这里实际上有两个问题:

  • iterate生成的是装箱的对象,必须拆箱成数字才能求和。
  • 我们很难把iterate分成独立块来并行执行

第二个问题更有意思,因为你必须意识到某些流操作比其他操作更容易并行化。具体来说,iterate很难分割成能够独立执行的小块,因为每次应用这个函数都需要依赖前一次应用的结果

整张数字列表在归纳过程中并没有准备好,因而无法有效地把流划分为小块来并行处理。把流标记成并行其实是给顺序处理增加了开销,它还要每次求和操作分到一个不同的线程上。

这说明并行编程可能很复杂,有时候甚至有点违反直觉。如果用得不对(比如采用了一个不易并行化的操作,如iterate)它甚至可能让程序的整体性能更差,所在调用那个看似神奇的parallel操作时,了解背后到底发生了什么是很有必要的。

那到底要怎么利用多核处理器,用流来高效地并行求和呢?

LongStream.rangeClosed这个方法与iterate相比有两个优点

  • LongStream.rangeClosed直接产生原始类型的long数字,没有装箱拆箱的开销
  • LongStream.rangeClosed会生成数字范围,很容易拆分为独立的小块。例如,范围1到20可分为1到5、6到10、11到15和16到20。

终于,我们得到了一个比顺序执行更快的并行归纳。在Stream内部分成了几块。因此可以对不同的快独立并行进行归纳操作,最后同一个归纳操作会将各个子流的部分归纳结果合并起来,得到整个原始流的归纳结果。

并行化并不是没有代价的。并行化过程本身需要对流做递归划分,把每个子流的归纳操作分配到不同的线程,然后把这些操作的结果合并成一个值。但在多个内核之间移动数据的代价也可能比你想的还要大,所以很重要的一点是要保证在内核中并行执行工作的时间比在内核之间传输数据的时间长,你必须确保用得对;如果结果错了,算得快就毫无意义了。

个人理解,LongStream.range 方法生成的数字范围默认增量是1,这样只需要知道开始和结束即可立即生成数字范围,而Stream.iterate 增量可以是1,也可以是其他的数值,生成一下个数必须依赖于上一个,即便增量是1生成逻辑并没有变

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值