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