이론 정리/java
자바 스트림 병렬성
철매존
2023. 12. 30. 18:07
728x90
병렬데이터 처리
스트림을 통한 순차 스트림을 사용하면 병렬 스트림으로 자연스럽게 바꿀 수 있다.
병렬 스트림
- 각각의 스레드에서 처리할 수 있도록 스트림 요소를 여러 청크로 분할한 스트림
- 그래서 병렬 스트림을 이용하면 모든 멀티코어 프로세서가 각각의 청크를 처리하도록 할당 가능
- 예를 들어 n을 받아서 1~n까지 모두 더하는 경우 이를 무한스트림으로 할 수 있는데, 이거를 n이 커지면 병렬로 처리하는게 좋을 것이다.
순차 스트림을 병렬 스트림으로 변환
- 순차스트림에
parallel
메서드 호출하면 기존의 함수형 리듀싱 연산이 병렬로 처리됨 - 저걸 쓰면 청크단위로 호출되는데, 스트림 자체에는 변화가 없고 내부적으로 불리언 플래그가 설정되는것
sequential
은 반대로 병렬스트림을 순차스트림으로 바꾸는것- 두 메서드를 이용해서 병렬/순차 여부를 제어 가능
- 병렬 스트림은 내부적으로
ForkJoinPool
을 사용한다.
성능 측정
- JMH라이브러리를 이용해 벤치마크 가능하다함
- 벤치마크 해보면 순차적 스트림보다 빠를수도 있지만 느릴수도 있음
- 그 이유는 두가지인데
- 반복 결과로 박싱된 객체가 만들어지므로 숫자를 더하려면 언박싱을 해야한다.
- 반복 작업은 병렬로 수행할 수 있는 독립 단위로 나누기 어렵다.
- 예를들어
iterate
는 본질적으로 순차적인데(이전꺼를 가지고 해야하니까) 여기서 쓰면 오히려 성능이 낮아짐
- 예를들어
- 그 이유는 두가지인데
- 특화된 메서드를 사용하는 것이 좋다.
- 예를 들어
LongStream.rangeClosed
메서드의 경우는iterate
에 비해 두 가지 장점이 있는데- 기본형 long을 직접 사용해서 박싱/언박싱 오버헤드가 사라짐
- 쉽게 청크로 분할할 수 있는 숫자 범위 생산 -> 예를 들어 1~20범위 숫자를 1-5, 6-10, 11-15, 16-20 이렇게
- 예를 들어
올바른 사용법
- 병렬 스트림을 잘못 사용하면서 발생하는 많은 문제는 -> 공유된 상태를 바꾸는 알고리즘을 사용해서 발행한다.
- 당연한거지만 순차적으로 수행하지 않으면 안되니까
- 그리고 양을 기준으로 병렬 스트림 사용을 결정하는것도 적절치 않음
- 그냥 순차/병렬에서 뭐가 나은지 측정해보자
- 박싱을 주의하자
- 기본형 특화 스트림 위주로 쓰는게 좋음
- 몇몇 연산은 순차 스트림보다 성능이 떨어짐
- limit이나 findFirst처럼 요소의 순서에 의존하는 경우 등
- 전체 파이프라인 연산 비용 고려
- 처리해야 하는 요소 수가 N이고 하나의 요소를 처리하는 데 드는 비용을 Q라 하면 비용은 N*Q으로 예상할 수 있음.
- 소량의 데이터에서는 별 도움이 안된다.
- 병렬화 과정에서 생기는 부가 비용이 있으니까
- 스트림을 구성하는 자료구조가 적절한지?
- ArrayList가 LinkedList보다 효율적인것 등(LinkedList는 분할을 위해 전체를 탐색해야하니까)
- 스트림의 특성과 파이프라인 중간 연산이 스트림의 특성을 어떻게 바꾸는지에 따라 분해 과정의 성능이 달라질 수 있다.
- 예를 들어
SIZED
스트림은 정확히 같은 크기의 두 스트림으로 분할 가능해서 효과적 - 근데 필터 연산은 예측이 어려워서 효과적인지 알 수 없음
- 예를 들어
- 최종 연산의 병합 과정 비용을 살펴봐야한다.
- 이 병합 과정이 비싸면 병렬 스트림 이점으로 얻은 이익이 상쇄될 수 있다.
- 내부 인프라구조도 살펴봐야한다.
포크 조인
이거로 공부했었음
정리
- 내부 반복을 이용하면 명시적이 아니어도 스트림으로 병렬처리 가능
- 꼭 이게 빠른건 아니니까 측정을 잘 해보자
- 가능하면 기본형 특화를 쓰는게 좋다.
- 포크/조인 프레임워크는 병렬화될 수 있는 태스크를 작은 태스크로 분할한 다음에 이거를 각각의 스레드로 실행하여 그 서브태스크 결과를 합쳐 최종 결과로 생산!!
- Spliterator은 탐색하려는 데이터를 포함하는 스트림을 어떻게 병렬화할 것인지 정의한다.