Java 8中出现意外的并行流性能

nob*_*ody 3 java parallel-processing java-8 java-stream

使用spliterator()通过Iterable 创建的流时,我遇到了性能问题 .就像StreamSupport.stream(integerList.spliterator(), true).想要在正常的系列中证明这一点.请参阅下面的一些基准测试结果

问题:为什么从迭代创建的并行流比从ArrayList或IntStream创建的流慢得多?

从一个范围

 public void testParallelFromIntRange() {
    long start = System.nanoTime();
    IntStream stream = IntStream.rangeClosed(1, Integer.MAX_VALUE).parallel();
    System.out.println("Is Parallel: "+stream.isParallel());
    stream.forEach(ParallelStreamSupportTest::calculate);
    long end = System.nanoTime();
    System.out.println("ParallelStream from range Takes : " + TimeUnit.MILLISECONDS.convert((end - start),
            TimeUnit.NANOSECONDS) + " milli seconds");
}
Run Code Online (Sandbox Code Playgroud)

Is Parallel:true
范围内的ParallelStream Takes:490毫秒

来自Iterable

 public void testParallelFromIterable() {
    Set<Integer> integerList = ContiguousSet.create(Range.closed(1, Integer.MAX_VALUE), DiscreteDomain.integers());
    long start = System.nanoTime();
    Stream<Integer> stream = StreamSupport.stream(integerList.spliterator(), true);
    System.out.println("Is Parallel: " + stream.isParallel());
    stream.forEach(ParallelStreamSupportTest::calculate);
    long end = System.nanoTime();
    System.out.println("ParallelStream from Iterable Takes : " + TimeUnit.MILLISECONDS.convert((end - start),
            TimeUnit.NANOSECONDS) + " milli seconds");
}
Run Code Online (Sandbox Code Playgroud)

Is Parallel:
来自Iterable Takes的真正ParallelStream:12517毫秒

而如此琐碎的计算方法.

public static Integer calculate(Integer input) {
    return input + 2;
}
Run Code Online (Sandbox Code Playgroud)

Bri*_*etz 11

并非所有的分裂者都是平等的.分裂器的任务之一是将源分解为两部分,可以并行处理.一个好的分裂器会将源大致分成两半(并且能够以递归的方式继续这样做.)

现在,假设您正在编写一个仅由迭代器描述的源的分裂器.你能得到什么样的分解质量?基本上,您所能做的就是将源分为"第一"和"休息".那就差不多了.结果是一个非常"右重"的计算树.

从数据结构中获得的分裂器有更多可以使用; 它知道数据的布局,并且可以使用它来提供更好的分割,从而获得更好的并行性能.ArrayList的分裂器总是可以分成两半,并且保留每半数据中究竟有多少数据的知识.这非常好.来自平衡树的分裂器可以获得良好的分布(因为树的每一半都具有大约一半的元素),但是不如ArrayList分裂器那么好,因为它不知道确切的大小.LinkedList的分裂器和它一样糟糕; 它所能做的就是(首先,休息).从迭代器派生分裂器也是如此.

现在,一切都不一定丢失; 如果每个元素的工作量很高,你就可以克服坏分裂.但是如果你在每个元素上做了少量工作,那么你将受到分裂器分裂质量的限制.


Mis*_*sha 5

您的基准测试有几个问题.

  1. Stream<Integer>IntStream因为拳击开销无法比较.
  2. 您没有对计算结果做任何事情,这使得很难知道代码是否实际运行
  3. 您正在进行基准测试,System.nanoTime而不是使用适当的基准测试工具.

这是基于JMH的基准:

import com.google.common.collect.ContiguousSet;
import com.google.common.collect.DiscreteDomain;
import com.google.common.collect.Range;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.OptionsBuilder;

public class Ranges {

    final static int SIZE = 10_000_000;

    @Benchmark
    public long intStream() {
        Stream<Integer> st = IntStream.rangeClosed(1, SIZE).boxed();

        return st.parallel().mapToInt(x -> x).sum();
    }

    @Benchmark
    public long contiguousSet() {
        ContiguousSet<Integer> cs = ContiguousSet.create(Range.closed(1, SIZE), DiscreteDomain.integers());
        Stream<Integer> st = cs.stream();

        return st.parallel().mapToInt(x -> x).sum();
    }

    public static void main(String[] args) throws RunnerException {
        new Runner(
                new OptionsBuilder()
                .include(".*Ranges.*")
                .forks(1)
                .warmupIterations(5)
                .measurementIterations(5)
                .build()
        ).run();
    }
}
Run Code Online (Sandbox Code Playgroud)

并输出:

Benchmark                  Mode   Samples        Score  Score error    Units
b.Ranges.contiguousSet    thrpt         5       13.540        0.924    ops/s
b.Ranges.intStream        thrpt         5       27.047        5.119    ops/s
Run Code Online (Sandbox Code Playgroud)

IntStream.range因为ContiguousSetContiguousSet没有实现自己的Spliterator并且使用默认的,所以它的速度大约是两倍,这是完全合理的.Set