티스토리 뷰
728x90
본 포스팅은 이펙티브 자바 3/E
의 아이템 48. 스트림 병렬화는 주의해서 적용하라
를 읽고 정리한 내용이다.
아이템 45. 스트림은 주의해서 사용하라
기본적으로 스트림 파이프라인은순차적
으로 수행된다.
파이프라인을 병렬로 실행하려면 파이프라인을 구성하는 스트림 중 하나에서parallel 메서드
를 호출해주기만 하면 되나, 효과를 볼 수 있는 상황은 많지 않다.
1. 계산 결과가 정확하고 2. 성능도 좋아질 것이라는 확신이 없다면 스트림 병렬화를 적용하지 말자!!
계산량이 많거나 빅데이터를 처리하거나 성능 최적화가 필요한 상황에서 스트림 병렬화
는 매력적이게 보일 수 있다.
모든 동시성 프로그래밍에서는 안전성(safety)
과 응답 가능(liveness)
상태를 유지해야 하는데,
스트림을 잘못 병렬화하면 오히려 프로그램을 오작동
혹은 응답 불가
하게 만들거나 성능을 급격히 떨어뜨릴 수 있다.
소수 계산 프로그램 👍
n보다 작거나 같은 소수의 개수를 계산한다.
@Test
void countPrime() {
System.out.println(pi(100_000_000));
}
private long pi(long n) {
return LongStream.rangeClosed(2, n)
.parallel() // 스트림 병렬화
.mapToObj(BigInteger::valueOf)
.filter(i -> i.isProbablePrime(50))
.count();
}
// 스트림 병렬화 전
5761455
BUILD SUCCESSFUL in 2m 38s
// 스트림 병렬화 후
5761455
BUILD SUCCESSFUL in 40s
처음 20개의 메르센 소수를 생성하는 프로그램 👎
메르센 소수란 소수 가운데
2^n - 1
로 표현되는 소수이다.
@Test
void generateMersenne() {
primes().map(p -> TWO.pow(p.intValueExact()).subtract(ONE))
.filter(mersenne -> mersenne.isProbablePrime(50))
.limit(20)
.forEach(System.out::println);
}
static Stream<BigInteger> primes() {
return Stream.iterate(TWO, BigInteger::nextProbablePrime);
}
만약 계산 속도를 높이고 싶어 스트림 파이프라인의 parallel()
을 호출해버리면 프로그램은 응답 불가(liveness failure)
상태에 빠지게된다.
@Test
void generateMersenne() {
primes().map(p -> TWO.pow(p.intValueExact()).subtract(ONE))
.filter(mersenne -> mersenne.isProbablePrime(50))
.parallel() // 스트림 병렬화
.limit(20)
.forEach(System.out::println);
}
데이터 소스
parallel()
은 병렬로 실행하기 위해 데이터 소스를 원하는 단위로 나눠서 스레드에 분배한다.
- 나누는 작업은 Spliterator 가 담당한다.
병렬화 ❌
Stream.iterate
- 순차적으로 다음 요소를 반환해 원하는 단위로 자를 수 없다.
병렬화 ⭕️
ArrayList
,HashMap
,HashSet
,ConcurrentHashMap
,int
,long
- 데이터를 원하는 크기로 정확하고 쉽게 나눌 수 있다.
- 참조 지역성(locality of reference) 이 뛰어나다. (= 이웃한 원소의 참조들이 메모리에 연속해서 저장되어 있다.)
SplittableRandom
- 랜덤한 수들로 이루어진 스트림을 병렬화 할때 사용한다.
중간 연산
병렬화 ❌
limit
- 파이프라인 병렬화는 limit을 다룰 때, CPU 코어가 남는다면 원소를 몇 개 더 처리한 후 제한된 개수 이후의 결과를 버려도 아무런 해가 없다고 가정한다.
- 소수 찾기 처럼 점점 수행 시간이 길어지는 케이스의 경우
응답 불가 상태
에 빠질 수 있다.
종단 연산
병렬화 ⭕️
축소(reduction)
: 파이프라인에서 만들어진 모든 원소를 하나로 합치는 작업- Stream의
reduce
혹은min
,max
,count
,sum
- Stream의
- 조건에 맞으면 바로 반환되는 메서드
anyMatch
,allMatch
,noneMatch
병렬화 ❌
가변 축소(mutable reduction)
를 수행하는 Stream의collect
- 컬렉션들을 합치는 부담이 크다.
성능 점검 필요
- 스트림 병렬화는 오직 성능 최적화 수단임을 기억하자!!
- 병렬화에 드는 추가 비용이 있기 때문에
(스트림 안의 원소 수) * (원소당 수행되는 코드 줄 수) > 최소 수십만
은 되어야 성능 향상을 기대할 수 있다. - 운영 환경과 유사한 조건에서 수행해보면서 성능 지표를 확인한 후 운영 코드에 최종 반영한다.
Ref.
- 참조 지역성 참고 : https://en.wikipedia.org/wiki/Locality_of_reference
- Java8 Parallel Processing 참고 : https://java-8-tips.readthedocs.io/en/stable/parallelization.html
728x90
'Backend > Java' 카테고리의 다른 글
상속은 정말 나쁜가? (0) | 2022.03.17 |
---|---|
나는 개행(줄바꿈)이면 무조건 \n인줄 알았자나 (0) | 2022.03.16 |
제네릭(Generic)의 기본 개념 (0) | 2022.03.07 |
@ParameterizedTest에도 각각의 이름을 부여할 수 있다?? (2) | 2022.03.04 |
전략(Strategy) 패턴 : 여러 전략 제공 + 테스트 용이 = 일석이조 (0) | 2022.03.02 |
댓글
공지사항
최근에 올라온 글