0%

병렬 데이터 처리와 성능

병렬 스트림 이란, 각각의 스레드에서 처리할 수 있도록 스트림 요소를 여러 청크로 분할한 스트림이다.
병렬 스트림을 이용하면 모든 멀티코어 프로세서가 각각의 청크를 처리하도록 할당할 수 있다.

순차 스트림을 병렬 스트림으로 변환하기

순차 스트림에 parallel 메서드를 호출하면 기존의 함수형 리듀싱 연산이 병렬로 처리된다.

1
2
3
4
5
6
public static long parallelSum(long n) {
return Stream.iterate(1L, i -> i + 1)
.limit(n)
.parallel() // 스트림을 병렬 스트림으로 변환
.reduce(0L, Long::sum);
}

병렬 스트림에서 사용하는 스레드 풀 설정
병렬 스트림은 내부적으로 ForkJoinPool을 사용한다. 기본적으로 ForkJoinPool은 프로세서 수, 즉 Runtime.getRuntime().availableProcessors()가 반환하는 값에 상응하는 스레드를 갖는다.

1
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "12");

전역 설정 코드로 이후의 모든 병렬 스트림연상에 영향을 준다. 일반적으로 기기의 프로세서 수와 같으므로 특별한 이유가 없다면 ForkJoinPool의 기본값을 그대로 사용할 것을 권장한다.

병렬 스트림 효과적으로 사용하기

  • 확신이 서지 않는다면 직접 측정하자.
  • 박싱을 주의하자. 자동박싱과 언박싱은 성은을 크게 저하시킬 수 있는 요소다. 기본형 특화 스트림을 사용하자.
  • 순차 스트림보다 병렬 스트림에서 성능이 떨어지는 연산이 있다. limit나 findFirst처럼 요소의 순서에 의존하는 연산을 병렬 스트림에서 수행하면 비싼비용을 치러야 한다.
  • 소량의 데이터에서는 병렬 스트림이 도움이 되지 않는다.
    스트림의 구성하는 자료구조가 적절한지 확인하라.
  • 최종 연산의 병합 과정 비용을 살펴보자. 병합 과정의 비용이 비싸다면 병렬 스트림으로 얻은 성능의 이익이 서브스트림의 부분결과를 합치는 과정에서 상쇄될 수 있다.
소스 분해성
ArrayList 훌륭함
LinkedList 나쁨
IntStream.range 훌륭함
Stream.iterate 나쁨
HashSet 좋음
TreeSet 좋음

포크/조인 프레임워크

포크/조인 프레임워크 는 병렬화할 수 있는 작업을 재귀적으로 작은 작업으로 분할한 다음에 서브태스크에 각가의 결과를 합쳐서 전체 결과를 만들도록 설계 되어 있다.
divide-and-conquer 알고리즘의 병렬화 버전이라 생각하면 된다.

fork/join

RecursiveTask 활용

스레드 풀을 이용하려면 RecursiveTask의 서브클래스를 만들어야 한다.
RecursiveTask를 정의하려면 추상 메서드 compute를 구현해야 한다.

아래 의사코드와 같이 구현하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Task extend RecursiveTask<V> {
@Override
protected V compute() {
if (분할가능한경우) {
분할;
재귀호출;
합침;
} else {
실제할일
}
}
}

// 호출시..
ForkJoinTask<V> task = new Task();
new ForkJoinPool().invoke(task);

Spliterator

Spliterator는 탐색하려는 데이터를 포함하는 스트림을 어떻게 병렬화할 것인지 정의한다.

자바8은 컬렉션 프레임워크에 포함된 모든 자료구조에 사용할 수 있는 디폴트 Spliterator 구현을 제공하고 있다.

1
2
3
4
5
6
public interface Spliterator<T> {
boolean tryAdvance(Consumer<? super T> action);
Spliterator<T> trySplit();
long estimateSize();
int characteristics();
}
Method Description
tryAdvance 탐색요소가 남아있으면 참 반환
trySplit 분할하여 Spliterator 생성
estimateSize 탐색해야할 요소 수

Reference : Java 8 in Action