병렬 스트림
컬렉션에 parallelSteram을 호출하면 병렬 스트림이 생성
병렬 스트림이란 각각의 스레드에서 처리할 수 있도록 스트림 요소를 여러 청크로 분할한 스트림으로 멀티코어 프로세서가 각각의 청크를 처리하도록 할당
순차 스트림을 병렬 스트림으로 변환하기
스트림에 parallel 메서드를 호출하면 기존의 함수형 리듀싱 연산이 병렬로 처리된다.
public long parallelSum(long n) {
return Stream.iterate(1L, i -> i + 1)
.limit(n)
.parallel()
.reduce(0L, Long::sum);
}
순차 스트림에 parallel을 호출해도 스트림 자체에는 변화가 일어나지 않고 내부적으로 이후 연산이 병렬로 수행되야 한다는 Boolean 플래그가 설정된다.
sequential(): 반대로 병렬 스트림을 순차 스트림으로 바꿔준다.
sequential()과 parallel()가 모두 있으면 마지막에 호출된 연산으로 결정된다.
스트림 성능 측정
병렬화를 하면 무조건 성능이 좋아질까?
//기본 반복문
public static long iterativeSum(long n) {
long result = 0;
for (long i = 1L; i <= n; i++) {
result += i;
}
return result;
}
//순차
public static long sequentialSum(long n) {
return Stream.iterate(1L, i -> i + 1).limit(n).reduce(0L, Long::sum);
}
//병렬
public static long parallelSum(long n) {
return Stream.iterate(1L, i -> i + 1).limit(n).parallel().reduce(0L, Long::sum);
}
위의 예시 코드에서 성능비교를 하면 기본반복 > 순차 스트림 > 병렬 스트림
순으로 성능이 좋게 나왔다.
이런 결과가 나온 이유
- 반복 결과로 박싱된 객체가 만들어져 숫자를 더하려면 언박싱을 해야함
- 반복 작업은 병렬로 수행할 수 있는 독립 단위로 나누기 어려움
위의 예시에서 리듀싱 시작 지점에 전체 리스트가 준비되지 않아 스트림을 분할할 수 없어서 순차 스트림과 같은 방식으로 처리되며 스레드 할당의 오버헤드만 증가하였다.
더 특화된 메서드 사용
아래 예제는 병렬 스트림을 사용 시 성능이 올라가는 경우다.
public long parallelRangedSum(long n) {
return LongStream.rangeClosed(1, n).parallel().reduce(0L, Long::sum);
}
위의 코드는 특정 범위의 숫자를 이용해야 할 때 사용하였던 LongStream의 경우 기본형을 사용하여 박싱, 언박싱이 일어나지 않는다.
범위가 주어져서 청크로 나누기 쉽다.
병렬 스트림의 올바른 사용법
공유된 상태를 바꾸는 경우 문제가 발생하는데 이를 피해야 한다.
병렬 스트림 효과적으로 사용하는법
- 양을 기준으로 병렬 스트림을 사용하는 것은 적절하지 않다.
- 확신이 없으면 바꿔서 측정하기
- 박싱 주의하기(기본형 특화 스트림 사용)
- 순서에 의존하는 연산은 병렬스트림에서 성능이 떨어짐(limit, findFirst)
- 전체 파이프라인 연산비용 고려하기
- 데이터가 적으면 병렬 스트림은 도움이 되지 않음
- 스트림을 구성하는 자료구조가 적절한지 확인
- 스트림의 특성과 중간연산이 스트림의 특성을 어떻게 바꾸는지에 따라 분해 과정의 성능이 달라질 수 있다.
- 최종 연산 병합 과정 비용이 많이 들면 결과를 합칠 때 얻는 이익보다 클 수 있다.
- 내부 인프라구조 살펴보기
Spliterator 인터페이스
iterator처럼 소스의 요소 탐색 기능을 제공하며 iterator와의 차이점은 spliterator는 병렬 작업에 특화되어 있다.
특성
characteristics라는 추상메서드도 정의. Spliterator 자체의 특성 집합을 포함하는 int 반환.
- ORDERED: 요소에 순서가 정해져 있어 요소를 탐색, 분할할 때 순서에 유의해야 함
- DISTINCT: x, y 두 요소를 방문 시 x.equals(y)는 항상 false
- SORTED: 탐색된 요소는 미리 정의된 정렬순서를 따름
- SIZED: 크기가 알려진 소스로 Spliterator를 생성했으므로 estimatedSize()는 정확한 값을 반환함
- NON-NULL: 탐색하는 모든 요소는 null이 아님
- IMMUTABLE: Spliterator의 소스가 불변. 요소를 탐색하는 동안 추가, 삭제하거나 고칠 수 없음
- CONCURENT: 동기화 없이 Spliterator의 소스를 여러 스레드에서 동시에 고칠 수 없음
- SUBSIZED: 이 Spliterator와 분할되는 모든 Spliterator는 SIZED특성을 가짐
참고문서
모던 자바 인 액션 - 라울 게이브리얼 우르마, 마리오 푸스코, 앨런 마이크로소포트
'프로그래밍 > Book' 카테고리의 다른 글
[모던 자바 인 액션] 9장. 리팩터링, 테스팅, 디버깅 (0) | 2022.03.01 |
---|---|
[모던 자바 인 액션] 8장. 컬렉션 API 개선 (0) | 2022.02.21 |
[모던 자바 인 액션] 6장. 스트림으로 데이터 수집 (0) | 2022.02.09 |
[모던 자바 인 액션] 5장. 스트림 활용 (0) | 2022.02.06 |
[모던 자바 인 액션] 4장. 스트림 소개 (0) | 2022.01.31 |