什么时候应采用流而不是传统循环来获得最佳性能?流是否利用分支预测?


问题内容

我刚刚阅读了有关Branch-Prediction的文章,并想尝试一下Java 8
Streams的工作原理。

但是,Streams的性能总是比传统循环差。

int totalSize = 32768;
int filterValue = 1280;
int[] array = new int[totalSize];
Random rnd = new Random(0);
int loopCount = 10000;

for (int i = 0; i < totalSize; i++) {
    // array[i] = rnd.nextInt() % 2560; // Unsorted Data
    array[i] = i; // Sorted Data
}

long start = System.nanoTime();
long sum = 0;
for (int j = 0; j < loopCount; j++) {
    for (int c = 0; c < totalSize; ++c) {
        sum += array[c] >= filterValue ? array[c] : 0;
    }
}
long total = System.nanoTime() - start;
System.out.printf("Conditional Operator Time : %d ns, (%f sec) %n", total, total / Math.pow(10, 9));

start = System.nanoTime();
sum = 0;
for (int j = 0; j < loopCount; j++) {
    for (int c = 0; c < totalSize; ++c) {
        if (array[c] >= filterValue) {
            sum += array[c];
        }
    }
}
total = System.nanoTime() - start;
System.out.printf("Branch Statement Time : %d ns, (%f sec) %n", total, total / Math.pow(10, 9));

start = System.nanoTime();
sum = 0;
for (int j = 0; j < loopCount; j++) {
    sum += Arrays.stream(array).filter(value -> value >= filterValue).sum();
}
total = System.nanoTime() - start;
System.out.printf("Streams Time : %d ns, (%f sec) %n", total, total / Math.pow(10, 9));

start = System.nanoTime();
sum = 0;
for (int j = 0; j < loopCount; j++) {
    sum += Arrays.stream(array).parallel().filter(value -> value >= filterValue).sum();
}
total = System.nanoTime() - start;
System.out.printf("Parallel Streams Time : %d ns, (%f sec) %n", total, total / Math.pow(10, 9));

输出:

  1. 对于排序数组:

    Conditional Operator Time : 294062652 ns, (0.294063 sec)
    

    Branch Statement Time : 272992442 ns, (0.272992 sec)
    Streams Time : 806579913 ns, (0.806580 sec)
    Parallel Streams Time : 2316150852 ns, (2.316151 sec)

  2. 对于未排序的阵列:

    Conditional Operator Time : 367304250 ns, (0.367304 sec)
    

    Branch Statement Time : 906073542 ns, (0.906074 sec)
    Streams Time : 1268648265 ns, (1.268648 sec)
    Parallel Streams Time : 2420482313 ns, (2.420482 sec)

我尝试使用相同的代码 清单
list.stream()代替Arrays.stream(array)
list.get(c),而不是array[c]

输出:

  1. 对于排序列表:

    Conditional Operator Time : 860514446 ns, (0.860514 sec)
    

    Branch Statement Time : 663458668 ns, (0.663459 sec)
    Streams Time : 2085657481 ns, (2.085657 sec)
    Parallel Streams Time : 5026680680 ns, (5.026681 sec)

  2. 对于未排序列表

    Conditional Operator Time : 704120976 ns, (0.704121 sec)
    

    Branch Statement Time : 1327838248 ns, (1.327838 sec)
    Streams Time : 1857880764 ns, (1.857881 sec)
    Parallel Streams Time : 2504468688 ns, (2.504469 sec)

我提到一些博客这个这个这表明相同的性能问题WRT流。

  1. 我同意这样的观点,在某些情况下,使用流进行编程非常好并且更容易,但是当我们在性能上失去优势时,为什么需要使用它们呢?有什么我想念的吗?
  2. 流在哪些情况下执行等于循环的情况?仅在定义的函数花费大量时间而导致循环性能可忽略不计的情况下吗?
  3. 在任何一种情况下,我都看不到流利用 分支预测 (我尝试使用排序流和无序流,但是没有用。与正常流相比,它对性能的影响是两倍以上)?

问题答案:

我同意这样的观点,在某些情况下,使用流进行编程非常好并且更容易,但是当我们在性能上失去优势时,为什么需要使用它们呢?

性能很少成为问题。通常需要将10%的流重写为循环才能获得所需的性能。

有什么我想念的吗?

使用parallelStream()更容易使用流,并且可能更高效,因为很难编写高效的并发代码。

流在哪些情况下执行等于循环的情况?仅在定义的函数花费大量时间而导致循环性能可忽略不计的情况下吗?

您的基准测试存在缺陷,因为该代码在启动时尚未编译。我将像JMH一样循环执行整个测试,或者使用JMH。

在任何情况下,我都看不到流利用分支预测

分支预测是CPU功能,而不是JVM或流功能。