ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 병렬 데이터 처리와 성능
    Java/모던자바인액션요약 2022. 8. 29. 00:01
    728x90

    개요

    자바 7 이전에는 데이터 컬렉션을 병렬로 처리하기 어려웠습니다.

    자바 7은 포크/조인 프레임워크 기능을 제공하여 쉽게 병렬화를 수행하며 에러를 최소화할 수 있도록 합니다.

     

    하지만 자바 8의 스트림을 사용하면 순차 스트림을 병렬 스트림으로 자연스럽게 바꿀 수 있습니다.

    (동기화 문제 , 쓰레드는 몇 개로 지정할지 등을 고민하지 않아도 됩니다.)

     

    하지만 내부 처리과정이 어떻게 이루어지는지 알아야 스트림을 잘못 사용하는 상황을 피할 수 있습니다.

     

    병렬 스트림

    parallelStream을 호출하여 병렬 스트림을 만들 수 있습니다.

    병렬 스트림이란 각각의 스레드에서 스트림을 처리할 수 있도록 여러 청크로 분할한 스트림입니다.

     

    이때 sequential 메서드를 사용하면 병렬 스트림을 순차 스트림으로 바꿀 수도 있습니다.

    하지만 sequential 메서드와 parallel 메서드가 둘 다 사용될 경우에는 마지막에 호출된 메서드가 전체 파이프라인에 영향을 미칩니다.

     

    병렬 작업을 수행하는 스레드는 어디서 생성되며, 몇개 생성되고, 이 과정을 어떻게 커스터마이즈 할 수 있을까요?

    병렬 스트림은 내부적으로 ForkJoinPool을 사용합니다.

    기본적으로 ForkJoinPool은 프로세서 수, 즉 Runtime.getRuntime(0.availableProcessors()가 반환하는 값에 상응하는 스레드를 갖습니다.

     

    병렬로 작업을 수행하면 항상 속도는 빨라질까?

    박싱, 언박싱 비용

    병렬로 수행할 수 있는 독립 단위로 나누지 못하는 경우

     

    위의 2가지 요소를 잘 고려하지 않는다면 오히려 훨씬 느린 결과가 나올 수 있습니다.

     

    병렬 스트림 올바르기 사용하기

    - 공유된 가변 변수를 사용하면 안됩니다.

    (여러 스레드에서 공유하는 객체의 상태를 바꾸는 경우)

     

    - 어느 정도의 데이터 크기일 때 병렬 스트림으로 바꾸는 게 좋을까요?

    (확신이 없다면 직접 성능을 측정하자)

     

    - 박싱, 언박싱 비용은 성능을 크게 저하시킬 수 있는 요소입니다.

     

    - 스트림을 구성하는 자료구조가 적절한지 확인해라

    (LinkedList는 분할하기 위해서 모든 요소를 탐색해야 하지만 ArrayList는 그렇지 않다.)

    이유 : ArrayList는 인덱스로 접근이 가능하기 때문

     

    적절한 자료구조 선택예시

    ArrayList, IntStream.range 훌륭함

    HashSet, TreeSet 좋음

    LinkedList, Stream.iterate 나쁨

     

    - 최종 연산의 병합 과정 비용을 고려하라.

    (병합 과정이 비싸다면 병렬 스트림으로 얻는 성능이 상쇄됩니다.)

     

     

    포크/조인 프레임워크

    병렬 스트림은 내부적으로 ForkJoinPool을 사용합니다.

     

    포크/조인 프레임워크는 병렬화 할 수 있는 작업을 재귀적으로 작은 작업으로 분할한 다음에 서브 태스크 각각의 결과를 합쳐서 전체 결과를 만들어냅니다.

     

    분할 정복 알고리즘의 병렬화 버전으로 이해하면 좋습니다.

     

    포크/조인 프레임워크의 작업 훔치기

    만약 특정 스레드가 할당된 작업을 빠르게 처리한다면 다른 스레드의 큐의 꼬리에서 작업을 훔쳐옵니다.

    이를 통해 스레드 간의 작업 부하를 비슷한 수준으로 유지합니다.

     

    하지만 우리는 분할 로직을 개발하지 않도고 병렬 스트림을 이용할 수 있습니다.

    자동으로 스트림을 분할하는 기법이 존재하며 이 기법은 바로 Spliterator입니다.

     

    Spliterator 인터페이스

    분할할 수 있는 반복자라는 의미로 Spliterator가 어떻게 동작하는지 이해하면 병렬 스트림 동작과 관련된 통찰력을 얻을 수 있습니다.

     

    Collection Framework에 포함된 모든 자료구조에 사용할 수 있는 디폴트 Spliterator 구현이 제공됩니다.

     

    public interface Spliterator<T> {
    	boolean tryAdvance(Consumer<? super T> action);
    	Spliterator<T> trySplit();
    	long estimateSize();
    	int characteristics();
    }

     

    tryAdvance 메서드는 Spliterator의 요소를 순차적으로 소비하면서 탐색할 요소가 남아있으면 참을 반환합니다.

     

    trySplit 메서드는 자신이 반환한 요소를 분할해서 두 번째 Spliterator를 생성하는 메서드입니다.

    (trySplit 결과가 null 일 때까지 반복하며 호출합니다)

     

     

    estimateSize 메서드는 탐색해야 할 요소 수 정보를 제공합니다.

     

    characteristics 메서드는 Spliterator 자체의 특성 집합을 포함하는 int를 반환합니다.

    특성의 예시 : ORDERED, DISTINCT, SORTED, SIZED 등등

    반환 예시 : return ORDERED + SIZED + SORTED ;

     

     

     

     

     

     

    'Java > 모던자바인액션요약' 카테고리의 다른 글

    컬렉션 API 개선  (0) 2022.09.06
    스트림으로 데이터 수집  (0) 2022.08.23
    스트림 활용  (0) 2022.08.18
    스트림 소개  (0) 2022.08.15
    람다 표현식  (0) 2022.08.10

    댓글

Designed by Tistory.