Java ‐ 스트림 병렬화는 주의해서 사용하라[Effective Java Item 48] - thought-corner/Backend-PlayGround GitHub Wiki
- 자바 8부터는
parallel 메서드만 한 번 호출하면 파이프라인을 병렬 실행할 수 있는 스트림을 지원했다.
- 스트림의 소스가
ArrayList, HashMap, HashSet, ConcurrentHashMap의 인스턴스거나 배열, int 범위, long 범위일 때 병렬화의 효과가 가장 좋다.
- 해당 자료구조들은 모두 데이터를 원하는 크기로 정확하고 손쉽게 나눌 수 있어서 일을 다수의 스레드에 분배하기 좋다.
- 나누는 작업은
Spliterator가 담당하며, Spliterator 객체는 Stream이나 Iterable의 spliterator() 메서드로 얻어올 수 있다.
- 이 자료구조들은 원소들을 순차적으로 실행할 때 참조 지역성이 뛰어나다.
스트림 병렬화의 문제점
- 데이터 소스가
Stream.iterate거나 중간 연산으로 limit를 쓰면 파이프라인 병렬화로는 성능 개선을 기대할 수 없다.
limit을 다룰 때 CPU 코어가 남는다면 원소를 몇 개 더 처리한 후 제한된 개수 이후의 결과를 버려도 아무런 해가 없다고 가정한다.
스트림 파이프라인의 명세 규약
- 스트림을 잘못 병렬화하면 성능이 나빠질 뿐만 아니라 결과 자체가 잘못되거나 예상치 못한 동작이 발생할 수 있다.
Stream 명세는 이 때 사용되는 함수 객체에 관한 엄중한 규약을 정의해놨다. 예를 들어, Stream의 reduct() 연산에 건네지는 accumulator와 combiner 함수는 반드시 결합 법칙을 만족하고 간섭받지 않고 상태를 갖지 않아야 한다.
스트림 병렬화를 적용하기 전에 고려 사항
- 스트림 병렬화는 오직 성능 최적화 수단으로 성능 테스트를 통해 가치가 있는지 확인해야 한다.
- 보통은 병렬 스트림 파이프라인도 공통의 포크-조인 풀에서 수행되므로 잘못된 파이프라인 하나가 다른 부분의 성능에까지 악영향을 미칠 수 있다.
스트림 병렬화가 좋을 떄
- 조건이 잘 갖춰지면
parallel() 메서드 호출 하나로 거의 프로세서 코어 수에 비례하는 성능 향상을 만끽할 수 있다.
- 스트림의 소스가
ArrayList, HashMap, HashSet, ConcurrentHashMap의 인스턴스나 배열, int, long 범위일 때 병렬화의 효과가 가장 좋다.
- 자료구조들은 모두 데이터를 원하는 크기로 정확하고 손쉽게 나눌 수 있어 일을 다수의 쓰레드에 분배하기 좋다.
- 순차적으로 실행할 때 참조 지역성이 뛰어나다. 이 참조 지역성이란, 메모리에 연속으로 저장되어 있다는 의미를 말하는데 가장 뛰어난 자료구조는 기본타입의 배열이다.
Random값 스트림 병렬화
ThreadLocalRandom : 단일 쓰레드에서 사용하기 위해 만들어졌다.
SplittableRandom : 무작위 수를 위한 스트림 병렬화를 위해 설계되었다.
Random : 모든 연산을 동기화하기 때문에 최악의 연산을 자랑한다.