programing

Java 8: 스트림과 컬렉션의 퍼포먼스

prostudy 2022. 7. 24. 21:16
반응형

Java 8: 스트림과 컬렉션의 퍼포먼스

자바8은 처음입니다.아직 API에 대해 자세히 알지 못하지만 새로운 Streams API와 이전 컬렉션의 성능을 비교하기 위해 간단한 비공식 벤치마크를 작성했습니다.

는 이스 of of음, 음음 of of of of of of of of of of of of of of of of of of of of of of의 목록을 것으로 구성되어 .Integer하여 그 ListDouble.

코드는 다음과 같습니다.

    public static void main(String[] args) {
        //Calculating square root of even numbers from 1 to N       
        int min = 1;
        int max = 1000000;

        List<Integer> sourceList = new ArrayList<>();
        for (int i = min; i < max; i++) {
            sourceList.add(i);
        }

        List<Double> result = new LinkedList<>();


        //Collections approach
        long t0 = System.nanoTime();
        long elapsed = 0;
        for (Integer i : sourceList) {
            if(i % 2 == 0){
                result.add(Math.sqrt(i));
            }
        }
        elapsed = System.nanoTime() - t0;       
        System.out.printf("Collections: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));


        //Stream approach
        Stream<Integer> stream = sourceList.stream();       
        t0 = System.nanoTime();
        result = stream.filter(i -> i%2 == 0).map(i -> Math.sqrt(i)).collect(Collectors.toList());
        elapsed = System.nanoTime() - t0;       
        System.out.printf("Streams: Elapsed time:\t\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));


        //Parallel stream approach
        stream = sourceList.stream().parallel();        
        t0 = System.nanoTime();
        result = stream.filter(i -> i%2 == 0).map(i -> Math.sqrt(i)).collect(Collectors.toList());
        elapsed = System.nanoTime() - t0;       
        System.out.printf("Parallel streams: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));      
    }.

듀얼 코어 머신의 결과는 다음과 같습니다.

    Collections: Elapsed time:        94338247 ns   (0,094338 seconds)
    Streams: Elapsed time:           201112924 ns   (0,201113 seconds)
    Parallel streams: Elapsed time:  357243629 ns   (0,357244 seconds)

이 특정 테스트에서는 스트림 속도가 컬렉션보다 약 2배 느리고 병렬 처리가 도움이 되지 않습니다(혹은 잘못된 방법으로 사용하고 있는지 여부).

질문:

  • 이 시험은 공정한가요?제가 무슨 실수를 했나요?
  • 스트림 속도가 수집 속도보다 느립니까?이것에 대해 공식적인 벤치마크를 제대로 한 사람이 있나요?
  • 어떤 접근방식을 택해야 합니까?

갱신된 결과

@pveentjer의 조언에 따라 JVM 워밍업(1k 반복) 후 테스트를 1k회 실행했습니다.

    Collections: Average time:      206884437,000000 ns     (0,206884 seconds)
    Streams: Average time:           98366725,000000 ns     (0,098367 seconds)
    Parallel streams: Average time: 167703705,000000 ns     (0,167704 seconds)

이 경우 스트림의 퍼포먼스가 향상됩니다.런타임 중에 필터링 기능이 한두 번밖에 호출되지 않는 앱에서는 어떤 현상이 나타날지 궁금합니다.

  1. 을 중지합니다.LinkedList반복기를 사용하여 목록 중간에서 삭제하지 않는 것이 좋습니다.

  2. 벤치마크 코드를 수작업으로 작성하지 말고 JMH를 사용합니다.

적절한 벤치마크:

@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
@OperationsPerInvocation(StreamVsVanilla.N)
public class StreamVsVanilla {
    public static final int N = 10000;

    static List<Integer> sourceList = new ArrayList<>();
    static {
        for (int i = 0; i < N; i++) {
            sourceList.add(i);
        }
    }

    @Benchmark
    public List<Double> vanilla() {
        List<Double> result = new ArrayList<>(sourceList.size() / 2 + 1);
        for (Integer i : sourceList) {
            if (i % 2 == 0){
                result.add(Math.sqrt(i));
            }
        }
        return result;
    }

    @Benchmark
    public List<Double> stream() {
        return sourceList.stream()
                .filter(i -> i % 2 == 0)
                .map(Math::sqrt)
                .collect(Collectors.toCollection(
                    () -> new ArrayList<>(sourceList.size() / 2 + 1)));
    }
}

결과:

Benchmark                   Mode   Samples         Mean   Mean error    Units
StreamVsVanilla.stream      avgt        10       17.588        0.230    ns/op
StreamVsVanilla.vanilla     avgt        10       10.796        0.063    ns/op

예상했던 대로 스트림 구현이 상당히 느립니다.JIT는 모든 람다를 인라인화할 수 있지만 바닐라 버전만큼 완벽하게 간결한 코드를 생성하지는 않습니다.

자바 8 잘 되어 있는5의 각가 Java 5로 되어 있는 경우)의 속도를 수 .Iterable.forEach() ★★★★★★★★★★★★★★★★★」Collection.removeIf()콜)을 클릭합니다.스트림은 코딩의 편리성과 안전성에 더 가깝습니다.편리성 - 스피드 트레이드오프가 여기에 작용하고 있습니다.

1) 벤치마크를 사용하면 시간이 1초 미만임을 알 수 있습니다.즉, 부작용의 영향이 클 수 있습니다.그래서 나는 너의 과제를 10배로 늘렸다.

    int max = 10_000_000;

벤치마크를 실행했습니다.결과:

Collections: Elapsed time:   8592999350 ns  (8.592999 seconds)
Streams: Elapsed time:       2068208058 ns  (2.068208 seconds)
Parallel streams: Elapsed time:  7186967071 ns  (7.186967 seconds)

(편집 없음)int max = 1_000_000의 결과가 있었습니다.

Collections: Elapsed time:   113373057 ns   (0.113373 seconds)
Streams: Elapsed time:       135570440 ns   (0.135570 seconds)
Parallel streams: Elapsed time:  104091980 ns   (0.104092 seconds)

스트림 속도가 수집 속도보다 느립니다.결론: 스트림 초기화/값 전송에 많은 시간이 소요되었습니다.

2) 태스크 스트림을 늘린 후 속도가 빨라졌지만(괜찮습니다), 병렬 스트림이 너무 느려졌습니다.가잘 ??주의: 다음과 같은 기능이 있습니다.collect(Collectors.toList())명령대로 하겠습니다.단일 수집으로 수집하면 기본적으로 동시 실행 시 성능 병목현상과 오버헤드가 발생합니다.대체하여 오버헤드의 상대적 비용을 추정할 수 있습니다.

collecting to collection -> counting the element count

스트림의 경우 다음 방법으로 수행할 수 있습니다.collect(Collectors.counting())결과가 나왔습니다.

Collections: Elapsed time:   41856183 ns    (0.041856 seconds)
Streams: Elapsed time:       546590322 ns   (0.546590 seconds)
Parallel streams: Elapsed time:  1540051478 ns  (1.540051 seconds)

그건 큰 일을 위해서야!(int max = 10000000결론: 수집할 아이템을 수집하는 데 많은 시간이 소요되었습니다.가장 느린 부분은 목록에 추가하는 것입니다.BTW, 심플ArrayList에 사용됩니다.Collectors.toList().

    public static void main(String[] args) {
    //Calculating square root of even numbers from 1 to N       
    int min = 1;
    int max = 10000000;

    List<Integer> sourceList = new ArrayList<>();
    for (int i = min; i < max; i++) {
        sourceList.add(i);
    }

    List<Double> result = new LinkedList<>();


    //Collections approach
    long t0 = System.nanoTime();
    long elapsed = 0;
    for (Integer i : sourceList) {
        if(i % 2 == 0){
            result.add( doSomeCalculate(i));
        }
    }
    elapsed = System.nanoTime() - t0;       
    System.out.printf("Collections: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));


    //Stream approach
    Stream<Integer> stream = sourceList.stream();       
    t0 = System.nanoTime();
    result = stream.filter(i -> i%2 == 0).map(i -> doSomeCalculate(i))
            .collect(Collectors.toList());
    elapsed = System.nanoTime() - t0;       
    System.out.printf("Streams: Elapsed time:\t\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));


    //Parallel stream approach
    stream = sourceList.stream().parallel();        
    t0 = System.nanoTime();
    result = stream.filter(i -> i%2 == 0).map(i ->  doSomeCalculate(i))
            .collect(Collectors.toList());
    elapsed = System.nanoTime() - t0;       
    System.out.printf("Parallel streams: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));      
}

static double doSomeCalculate(int input) {
    for(int i=0; i<100000; i++){
        Math.sqrt(i+input);
    }
    return Math.sqrt(input);
}

코드를 조금 바꿔서 8개의 코어가 있는 mac book pro로 실행해 보니, 적당한 결과가 나왔습니다.

Collections: Elapsed time:      1522036826 ns   (1.522037 seconds)
Streams: Elapsed time:          4315833719 ns   (4.315834 seconds)
Parallel streams: Elapsed time:  261152901 ns   (0.261153 seconds)

당신이 하려고 하는 것을 위해, 저는 어쨌든 일반 Java API를 사용하지 않을 것입니다.많은 복싱/언박싱이 진행되고 있기 때문에 오버헤드가 크다.

개인적으로 많은 API는 많은 오브젝트 쓰레기가 생성되기 때문에 쓰레기라고 생각합니다.

double/int의 원시 어레이를 사용하여 싱글 스레드를 사용하여 성능을 확인합니다.

PS: 벤치마크를 실시하기 위해 JMH를 검토하는 것이 좋습니다.JVM 워밍업과 같은 일반적인 함정 중 몇 가지를 처리합니다.

Java 8 및 Java 11에 대한 흥미로운 결과입니다.리벤토프가 제공한 코드를 사용했지만 수정은 거의 없었습니다.

@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
@OperationsPerInvocation(BenchmarkMain.N)
public class BenchmarkMain {

    public static final int N = 10000;

    static List<Integer> sourceList = new ArrayList<>();
    static {
        for (int i = 0; i < N; i++) {
            sourceList.add(i);
        }
    }

    @Benchmark
    public List<Double> vanilla() {
        List<Double> result = new ArrayList<>(sourceList.size() / 2 + 1);
        for (Integer i : sourceList) {
            if (i % 2 == 0){
                result.add(Math.sqrt(i));
            }
        }
        return result;
    }

    @Benchmark
    public List<Double> stream() {
        return sourceList.stream()
                .filter(i -> i % 2 == 0)
                .map(Math::sqrt)
                .collect(Collectors.toCollection(
                    () -> new ArrayList<>(sourceList.size() / 2 + 1)));
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws IOException {
        org.openjdk.jmh.Main.main(args);

    }

}

Java 8:

# JMH version: 1.31
# VM version: JDK 1.8.0_262, OpenJDK 64-Bit Server VM, 25.262-b19
# VM invoker: /opt/jdk1.8.0_262/jre/bin/java
# VM options: <none>
# Blackhole mode: full + dont-inline hint
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
...
Benchmark              Mode  Cnt   Score   Error  Units
BenchmarkMain.stream   avgt   25  10.680 ± 0.744  ns/op
BenchmarkMain.vanilla  avgt   25   6.490 ± 0.159  ns/op

Java 11:

# JMH version: 1.31
# VM version: JDK 11.0.2, OpenJDK 64-Bit Server VM, 11.0.2+9
# VM invoker: /opt/jdk-11.0.2/bin/java
# VM options: <none>
# Blackhole mode: full + dont-inline hint
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
...
Benchmark              Mode  Cnt  Score   Error  Units
BenchmarkMain.stream   avgt   25  5.521 ± 0.057  ns/op
BenchmarkMain.vanilla  avgt   25  7.359 ± 0.118  ns/op

언급URL : https://stackoverflow.com/questions/22658322/java-8-performance-of-streams-vs-collections

반응형