모던 자바 인 액션 Chap 7. 병렬 데이터 처리와 성능

doxxx·2022년 6월 6일
0

모던자바인액션

목록 보기
4/14
post-thumbnail

스트림을 이용하면 순차 스트림을 병렬 스트림으로 자연스럽게 바꿀 수 있다.

7.1 병렬 스트림

  • 컬렉션의 parallelStream을 호출하면 병렬 스트림이 생성된다.

  • 각각의 스레드에서 처리할 수 있도록 스트림 요소를 여러 청크로 분할한 스트림

  • iterate(final T seed, final UnaryOperator<T> f)

  • parallel()sequential() 메서드를 이용하여 스트림을 병렬로 작동할지 순차로 실행할지 제어할 수 있다.

  • 병렬 스트림에서 사용하는 스레드 풀 설정 - java.lang.Runtime 클래스 알아보기

    • Process exec(String command): 명령(command)을 실행시키고, 실행시킨 프로세스의 래퍼런스를 반환한다.
    • static Runtime getRuntime(): Runtime 객체의 레퍼런스를 반환한다.
    • void exit(int status): 상태 값(status) 반환하면서 JVM을 종료시킨다.
    • long freeMemory(): JVM이 사용 가능한 메모리 양(bytes)을 반환한다.
    • long totalMemory(): JVM이 사용하고 있는 전체 메모리를 반환한다.
    • long maxMemory(): JVM이 사용할 수 있는 최대 메모리 양을 반환한다.
    • int availableProcessors(): JVM이 사용할 수 있는 프로세서수를 반환한다.

7.1.2 스트림 성능 측정

7.1에서 다룬 반복형, 순차 리듀싱, 병렬 리듀싱의 성능을 비교한다.

  • 병렬 스트림에서 발생 할 수 있는 문제점

    • 반복 결과로 박싱된 객체각 만들어지므로 숫자를 더하려면 언박싱을 해야한다
    • 반복 작업은 병렬로 수행할 수 있는 독립 단위로 나누기가 어렵다
      • 병렬로 수행될 수 있는 스트림 모델이 필요하다
  • 리듀싱 연산의 경우 리듀싱 과정 시작 시점에 전체 숫자 리스트가 준비되어야 한다.

    • 이유: 스트림을 병렬로 처리할 수 있도록 청크로 분할할 수 있어야 한다
    • 올바른 자료구조를 선택해야 병렬 실행도 성능을 발휘한다

병렬화는 스트림을 재귀적으로 분할해야 하고(청킹), 각 서브스트림을 서로 다른 스레드의 리듀싱 연산으로 할당하고,
이들 겨로가를 하나의 값으로 합쳐야한다.

멀티코어 간의 데이터 이동의 비용은 생각보다 크다. 코어 간에 데이터 전송 시간보다 훨씬 오래 걸리는 작업만 병렬로
작업하는 것이 낫다.

7.1.3 ~7.1.4 병렬 스트림 사용법

7.1.3에서는 잘못된 예시를 다루고 있다. 해결방법을 7.1.4에서 제공한다

class ParallelStreamExample {

    // side effect가 있는
    public static void main(String[] args) {

        final int[] sum = {0};
        IntStream.range(0, 1000).forEach(i -> {
            sum[0] += i;
        });
        System.out.println(sum[0]);

        final int[] sum2 = {0};
        IntStream.range(0, 1000)
            .parallel()
            .forEach(i -> {
                sum2[0] += i;
            });
        System.out.println(sum2[0]);
    }
}
// 결과값: 499500
//        486145

위의 병렬 스트림의 경우 레이스 컨디션이 발생

class ParallelStreamExample {

    // side effect가 없는 방법
    public static void main(String[] args) {
        IntStream.range(0, 1000).sum();
        IntSream.range(0, 1000).parallel().sum();
    }
}
  • 벤치마크해서 사용하기
  • 박싱 동작을 피할 수 있도록 자바8에서 제공하는 기본형 특화 스트림을 사용하자
  • 등등

7.2 포크/조인 프레임워크

서브태스크를 스레드 풀의 Executor 스레드에 분산 할당하는 ExecutorService인터페이스를 제공한다

7.2.1 RecursiveTask 활용

클래식한 예시로 피보나치 수가 있다.

public class Fibonacci extends RecursiveTask<Integer> {

    private final int n;

    Fibonacci(int n) {
        this.n = n;
    }

    protected Integer compute() {
        if (n <= 1) {
            return n;
        }
        Fibonacci f1 = new Fibonacci(n - 1);
        f1.fork();
        Fibonacci f2 = new Fibonacci(n - 2);
        f2.fork();
        return f2.compute() + f1.join();
    }
}
  • ForkJoinPool을 한 번만 인스턴스화 해서 정적 필드에 싱글톤으로 저장하여 사용한다.

7.2.2 포크/조인 프레임워크를 제대로 사용하는 방법

  • 병렬스트림, 스트림을 벤치마크 할 때는 웝업이 필요하다.
    • JIT 컴파일러에 의해 최적화 되기위해

7.3 Spliterator 인터페이스

분할할 수 있는 반복자를 의미한다.

병렬 스트림의 동작원리를 알 수 있다.

7.3.1 분할 과정

  • Spliterator는 trySplit()의 결과가 null을 반환할 때까지 스트림을 재귀적으로 분할한다.

7.3.2 커스텀 Spliterator 구현하기

책의 예시를 잘 따라가보자...

0개의 댓글

관련 채용 정보