병렬 스트림
컬렉션에 parallelStream을 호출하면 병렬 스트림이 생성된다.
병렬 스트림이란 각각의 스레드에서 처리할 수 있도록 스트림 요소를 여러 청크로 분할한 스트림이다.
병렬 스트림을 이용하면 모든 멀티코어 프로세서가 각각의 청크를 처리하도록 할당할 수 있다.
예시
만약 n을 입력받아 1부터 n까지 합계를 구하는 메서드를 구현한다고 하자.
1 | public long sequentialSum(long n){ |
parallel메서드는 순차 스트림을 병렬 스트림으로 만들고,
sequential메서드는 병렬 스트림을 순차 스트림으로 만든다.
근데 이 두 메서드는 마지막으로 호출된 메서드가 전체 파이프라인에 영향을 미친다.
파이프라인에 parallel, sequential parallel 이렇게 마구잡이로 써도, 결국 가장 마지막에 나온 메서드가 병렬/순차를 결정한다.
하지만 병렬 스트림을 사용한다고 무조건 성능이 좋을까?
병렬 스트림이 오히려 순차 스트림보다 성능이 별로 좋아지지 않는 경우가 있다.
위 예시도 실제로 돌려보면, 일반적인 반복문 버전, 순차적 스트림 버전, 병렬 스트림 버전 이렇게 세가지를 비교하면,
반복문이 제일 빠르고, 순차적 스트림, 병렬 스트림 순이다.
왜 그럴까?
일반적 반복문은 저수준으로 동작하고, 기본값을 박싱언박싱을 할 필요가 없어서 더 빠르다.
그렇다면 같은 스트림이더라도 왜 병렬이 더 느렸는가?
이는 두가지로 요약된다.
- 반복 결과로 박싱된 객체가 만들어지는데, 숫자를 더하려면 매번 언박싱을 해야함.
- 반복 잡업은 병렬로 수행할 수 있는 독립 단위로 나누기 어렵다.
즉 해당 연산을 할 스트림 요소가 얼마나 되는지 컴퓨터는 모르니, 이를 청크로 나눌 수 없다는 의미다.
더 특화된 메서드 사용
예를 들어 LongStream.rangeClosed 메서드를 사용하면 iterate 메서드에 비해 두가지 장점을 가진다.
- 기본형 long을 사용하므로 박싱언박싱 오버헤드가 사라짐.
- rangeClosed는 쉽게 청크로 분할할 수 있는 숫자 범위 제공한다.
즉 이런 예시를 보면 알 수 있듯이,
병렬화는 공짜가 아니다!
효과적인 병렬화를 이뤄낼려면,
- 스트림을 재귀적으로 분할해야 하고,
- 각 서브 스트림을 서로 다른 스레드 리듀싱 연산으로 할당하고,
- 이 결과를 하나의 값으로 합쳐야 한다.
- 게다가 코어 간 데이터 전송 시간보다 훨씬 오래 걸리는 작업만 병렬로 설계해야 한다.
병렬 스트림의 올바른 사용법
자 일단 기존의 방식대로 명령형 프로그래밍 방식으로, n까지 자연수를 더하면서 공유된 누적자를 바꾸는 프로그램이다.
1 | public long sideEffectSum(long n){ |
위 코드를 병렬적으로 처리한다면, 큰일난다.
공유된 total 변수에 다수의 스레드가 동시에 데이터에 접근하는 데이터 레이스 문제가 발생할 수 있다.
즉 병렬 스트림과 병렬 계산을 사용할 때는 공유된 가변 상태를 피해야 한다.
올바른 병렬 스트림을 위한 지침
- 박싱을 주의하라(자바 8부터 제공하는 IntStream, LongStream, DoubleStream을 애용하자.)
- 요소의 순서에 의존하는 연산은 병렬 스트림이 효과적이지 않다.(limit, findFirst 등.. 요소가 상관없다면 unordered로 비정렬 스트림으로 하자)
- 만약 한 요소 당 연산 비용이 높아지면, 병렬 스트림이 효과적이다.
- 소량 데이터는 병렬 스트림이 효과보기 힘들다.
- 스트림을 구성하는 자료구조가 적잘한지 확인하라.(모든 요소를 손쉽게 파악할 수 있는 ArrayList가 LinkedList보다 유리하다.)
- 스트림의 특성과 중간 연산에 따라 분해 과정 성능이 달라진다.(중간연산에서 스트림을 분해하는 경우, 병렬처리가 더 손쉬워진다.)
- 최종 연산의 병합 과정 비용이 높으면, 병렬 스트림에서 얻은 이익이 각 서브스트림의 최종 연산 병합에서 상쇄된다.
포크/조인 프레임워크
포크/조인 프레임워크는 병렬화할 수 있는 작업을 재귀적으로 작은 작업으로 분할한 다음,
서브 태스크 결과를 합쳐서 전체 결과를 만들도록 설계됐다.
서브태스크를 스레드 풀의 작업자 스레드에 분산 할당하는 ExecutorService 인터페이스를 구현한다.