[Effective Java] 7 - item48 스트림 병렬화는 주의해서 적용하라.
Java의 동시성 프로그래밍
자바는 스레드, 동기화, wait/notify
,
java.util.concurrent
라이브러리와 실행자(Executor
) (Java 5),
포크-조인(fork-join) 패키지 (Java 7),
parallel()
메서드 (Java 8) 등을 통해 동시성 프로그램을 지원한다.
동시성 프로그래밍을 할 때는 안정성(safety)과 응답 가능(liveness) 상태를 유지해야 함에 주의하자.
Stream
병렬화
사용하지 말아야 할 경우
public class ParallelMersennePrimes {
public static void main(String[] args) {
primes().map(p -> TWO.pow(p.intValueExact()).subtract(ONE))
.parallel() // 병렬화
.filter(mersenne -> mersenne.isProbablePrime(50))
.limit(20)
.forEach(System.out::println);
}
static Stream<BigInteger> primes() {
return Stream.iterate(TWO, BigInteger::nextProbablePrime);
}
}
무작정 성능을 향상시키기 위해 parallel()
를 사용하면,
위와 같이 아무것도 출력하지 못하면서 CPU는 90%
나 잡아먹는 상태가 무한히 계속되는 문제가 발생할 수 있다. 스트림 라이브러리가 이 파이프라인을 병렬화하는 방법을 찾아내지 못했기 때문이다.
- 데이터 소스가
Stream.iterate
거나 중간 연산으로limit
를 쓰면 파이프라인 병렬화로는 성능 개선을 기대할 수 없다.
사용해도 좋은 경우
스트림의 소스가 ArrayList
, HashMap
, HashSet
, ConcurrentHashMap
의 인스턴스거나 배열, int
범위, long
범위일 때 병렬화의 효과가 가장 좋다.
이 자료구조들은 모두 데이터를 원하는 크기로 정확하고 손쉽게 나눌 수 있어서 일을 다수의 스레드에 분배하기에 좋다. 또한 원소들을 순차적으로 실행할 때의 참조 지역성이 좋다.
또한, 종단 연산에서 reduce
, anyMatch
, allMatch
, noneMatch
등과 같이
- 파이프라인의 원소를 하나로 합치는 경우
- 조건에 맞으면 반환하는 경우 에도 병렬화에 적합하다.
하지만 위와 같은 조건을 만족한다고 하더라도, 파이프라인이 수행하는 진짜 작업이 병렬화에 드는 추가 비용을 상쇄하지 못한다면 실제 성능 향상은 미미할 수 있다. 따라서 병렬화를 고려할 때는 성능 지표 또한 고려하도록 하자.
Leave a comment