독서 정리 - 모던 자바 인 액션

1 minute read

모던 자바 인 액션

[7장] 병렬 데이터 처리와 성능

자바 7 이전에는 데이터 컬렉션을 병렬로 처리하기 어려웠다. 적절한 동기화를 하고 마지막에 부분 결과를 합쳐야 한다. 7에서는 병렬화를 쉽게 수행하면서 에러를 최소화할 수 있는 포크/조인 프레임워크 기능을 제공했다.
병렬 스트림이 요소를 여러 청크로 분할하는 방법을 알아보자

7.1 병렬 스트림

parallelStream을 호출하면 병렬 스트림이 생성된다.

  1. 순차 스트림을 병렬 스트림으로 변환하기
    순차 스트림에 parallel 메서드를 호출하면 리듀싱 연산이 병렬로 처리된다.
    여러 청크로 분할된 리듀싱 연산을 병렬로 수행한다.
    sequntial로 병렬 스트림을 순차 스트림으로 바꿀 수 있다. parallel과 sequential 중 마지막으로 호출된 메서드가 영향을 미친다.
    병렬 스트림은 내부적으로 ForkJoinPool을 사용해 스레드를 생성하고 관리한다.
  2. 스트림 성능 측정
    메이븐 빌드를 이용해 JMH라는 라이브러리로 측정을 구현할 수 있다.
    언박싱의 비용도 생각해야한다. 반복 작업은 병렬로 수행할 수 있는 독립 단위로 나누기 어렵다. 는 단점이 있는 것을 성능 측정을 통해 알 수 있다.
    이 말은, 이전 연산의 결과에 따라 입력이 달라져서 iterate 연산을 청크로 분리하기 어려워진다는 것이다.
    병렬 프로그래밍을 잘 이해하지 않고 사용하면 괜히 스레드 할당 오버헤드만 증가할 수 있다.
    언박싱 비용 해결을 위해 LongStream.rangeClosed를 사용할 수 있다.
  3. 병렬 스트림의 올바른 사용법
    공유된 상태를 바꾸는 알고리즘 사용을 지양해야한다. 공유된 상태를 건드리거나 바꾸면 병ㄹ렬이 적용되면서 문제가 있을 수밖에 없다.
  4. 병렬 스트림 효과적으로 사용하기
    • 확신이 없으면 적절한 벤치마크로 직접 성능을 측정하는 것이 좋다.
    • 박싱과 언박싱을 항상 고려하자. 기본형 특화 스트림을 애용하자.
    • limit, findFirst처럼 순서에 의존하는 연산을 병렬에서 수행하면 비싸다. 순차를 원하면 병렬을 포기하자.
    • 전체 파이프라인 연산 비용을 고려하자. 요소 수가 N이고 비용이 Q이면 N*Q
    • 소량의 데이터는 병렬이 필요 없다.
    • 자료구조가 적절한지 확인하자.
    • 중간 연산이 특성을 어떻게 바꾸느냐에 따라 분해 과정의 성능이 달라질 수 있다. filter는 길이 예측이 안되므로 확신할 수 없고, sized와 같은 크기가 예측 가능한 메서드는 정확히 분할할 수 있으므로 병렬 처리가 쉽다.
    • 최종 연산의 병합 비용을 생각하자. 비싸다면 기껏 병렬로 얻은 이익이 없어질 수 있다.

7.2 포크/조인 프레임워크

작은 작업으로 나누고 각 결과를 합쳐 전체 결과를 만드는 것
스레드 풀의 작업자 스레드에 서브태스크를 분산 할당하는 ExecutorService 인터페이스가 있다.

  1. RecursiveTask 활용
    스레드 풀을 사용하려면 RecursiveTask의 서브클래스가 필요하다. 결과가 없으면 RecursiveAction 형식이고 Task를 정의하려면 추상 메서드 `protected abstract R comput();`의 구현이 필요하다. 이는 **분할 후 정복** 알고리즘의 병렬화 버전이다.