java流performace用于查找列表中的最大元素

nan*_*itv 0 java performance java-8 java-stream

我编写了一个简单的程序来与流的性能进行比较,以查找整数的最大形式列表。令人惊讶的是,我发现“流方式”的性能是“通常方式”的1/10。难道我做错了什么?是否有条件限制哪种Stream方法无效?有人能对此行为做出一个很好的解释吗?

“流方式”花费了80毫秒“流方式”花费了15毫秒请在下面找到代码

public class Performance {

public static void main(String[] args) {

    ArrayList<Integer> a = new ArrayList<Integer>();
    Random randomGenerator = new Random();
    for (int i=0;i<40000;i++){
        a.add(randomGenerator.nextInt(40000));
    }
    long start_s = System.currentTimeMillis( );

            Optional<Integer> m1 = a.stream().max(Integer::compare);

    long diff_s = System.currentTimeMillis( ) - start_s;
    System.out.println(diff_s);


    int e = a.size();
    Integer m = Integer.MIN_VALUE;

    long start = System.currentTimeMillis( );
    for(int i=0; i < e; i++) 
      if(a.get(i) > m) m = a.get(i);

    long diff = System.currentTimeMillis( ) - start;
    System.out.println(diff);


}
Run Code Online (Sandbox Code Playgroud)

}

Tag*_*eev 5

是的,对于这种简单的操作,流比较慢。但是您的数字是完全无关的。如果您认为15毫秒的时间可以满足您的任务要求,那么有个好消息:预热流代码可以在0.1-0.2毫秒之内解决此问题,速度提高了70-150倍。

这是肮脏的基准测试:

import java.util.concurrent.TimeUnit;
import java.util.*;
import java.util.stream.*;

import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.annotations.*;

@Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Fork(3)
@State(Scope.Benchmark)
public class StreamTest {
    // Stream API is very nice to get random data for tests!
    List<Integer> a = new Random().ints(40000, 0, 40000).boxed()
                                  .collect(Collectors.toList());

    @Benchmark
    public Integer streamList() {
        return a.stream().max(Integer::compare).orElse(Integer.MIN_VALUE);
    }

    @Benchmark
    public Integer simpleList() {
        int e = a.size();
        Integer m = Integer.MIN_VALUE;
        for(int i=0; i < e; i++) 
            if(a.get(i) > m) m = a.get(i);
        return m;
    }
}
Run Code Online (Sandbox Code Playgroud)

结果是:

Benchmark               Mode  Cnt    Score    Error  Units
StreamTest.simpleList   avgt   30   38.241 ±  0.434  us/op
StreamTest.streamList   avgt   30  215.425 ± 32.871  us/op
Run Code Online (Sandbox Code Playgroud)

这是微秒。因此,Stream版本实际上比您的测试要快得多。不过,简单版本甚至更快。因此,如果您使用15 ms没问题,则可以使用您喜欢的这两个版本中的任何一个:两者都将执行得更快。

如果无论如何都希望获得最佳性能,则应摆脱装箱的Integer对象并使用原始数组:

int[] b = new Random().ints(40000, 0, 40000).toArray();

@Benchmark
public int streamArray() {
    return Arrays.stream(b).max().orElse(Integer.MIN_VALUE);
}

@Benchmark
public int simpleArray() {
    int e = b.length;
    int m = Integer.MIN_VALUE;
    for(int i=0; i < e; i++) 
        if(b[i] > m) m = b[i];
    return m;
}
Run Code Online (Sandbox Code Playgroud)

现在两个版本都更快:

Benchmark               Mode  Cnt    Score    Error  Units
StreamTest.simpleArray  avgt   30   10.132 ±  0.193  us/op
StreamTest.streamArray  avgt   30  167.435 ±  1.155  us/op
Run Code Online (Sandbox Code Playgroud)

实际上,由于涉及许多在不同时间JIT编译的中间方法,因此流版本的结果可能会有很大的不同,因此,在某些迭代之后,速度可能会沿任何方向改变。

顺便说一句,您的原始问题可以使用良好的旧Collections.max方法解决,而无需使用Stream API,如下所示:

Integer max = Collections.max(a);
Run Code Online (Sandbox Code Playgroud)

通常,您应该避免测试不能解决实际问题的人工代码。使用人工代码,您将获得人工结果,这些结果通常不会说出真实条件下的API性能。