07. 병렬 데이터 처리와 성능
간단 정리#
- 내부 반복을 이용하면 병렬로 처리하기 쉬움
 - 항상 병렬처리가 빠른건 아님
 - 병렬처리 사용 시 성능은 직접 측정을 권장
 - 데이터가 아주 많거나, 요소당 처리시간이 길면 병렬성으로 이점을 누릴 수 있음
 - 기본형 특화 스트림, 오바른 자료구조 선택이 연산 병렬화 보다 성능적으로 더 큰 영향을 미칠 수 있음
 - 포크/조인 프레임워크는
- 병렬화 할 수 있는 태스크를 작은 태스크로 분할 후
 - 각 태스크를 개별 스레드에 실행하며
 - 마지막에 각 서브태스크의 결과를 합쳐 최종결과를 생산함
 
 - Spliterator는 스트림을 어떻게 병렬화할건지 정의함
 
병렬 스트림으로 데이터를 병렬 처리하기#
stream.parallel()은 내부적으로 청크단위로 나누어 계산- 스레드는 
ForkJoinPool을 이용해 관리 Runtime.getRumtime().availableProcessors()가 반환하는 값으로 세팅
- 스레드는 
 - 병렬화는 완전 공짜가 아님
- 스트림을 재귀적으로 분할
 - 각 서브스트림을 서로 다른 스레드의 리듀싱 연산으로 할당
 - 이들 결과를 하나의 값으로 합쳐야 함
 
 - 멑리코어간의 데이터 이동은 비싸다
 - 병렬 스트림이 올바로 동작하려면 
공유된 가변 상태를 피해야 함 
병렬 스트림 사용시 주의 사항#
- 확신이 서지 않으면 직접 측정
 - 박싱을 주의
 - 순차 스트림보다 병렬 스트림 성능이 떨어지느 연산이 있음
- limit, findFirst (unordered limit 은 괜찮음)
 
 - 스트림에서 수행하는 전체 파이프라인 연산 비용을 고려하라
 - 소량의 데이터에선 병렬스트림이 도움 안됨
 - 자료구조 적절한지 확인
- LikedList 보단 ArrayList 가 랜덤 엑세스 가능하므로 좋음
 
 - 중간 연산이 어떻게 스트림 요소를 바꾸는지 주의
 - 최종연산의 병합과정 비용을 살펴보라
 
분해성 좋은 스트림 소스#
- ArrayList (best)
 - IntStream.range (best)
 - HashSet (good)
 - TreeSet (good)
 
병렬 스트림의 성능 분석#
- 그런데 iterative가 가장 빠르다...?
 - 위의 예제 문제점
- 반복 결과로 박싱된 객체가 만들어지므로 숫자를 더하려면 언박싱을 해야함
 - 반복 작업은 병렬로 수행할 수 있는 독립 단위로 나누기가 어려움
 
 - 해결방안: 청크단위로 분할할 수 있고 primitive type을 사용해서 언박싱을 회피할 수 있도록 
LongStream.rangeClosed()를 사용 
- 100회 반복, n = 10_000_000 입력시 경과 시간
- iterativeSum: 660ms
 - sequentialSum: 16s 701ms
 - parallelSum: 51s 882ms
 - rangedSequentialSum: 886ms
 - rangedParallelSum: 287ms
 
 
포크/조인 프레임워크#
- Java 7
 - 병렬화 할 수 있는 작업을 재귀적으로 작은 작업으로 분할
 - 이후 서브 태스크 각각의 결과를 합쳐 전체 결과를 만들도록 설계
 - 자세한 구현은 예제에서 확인
 - forJoinSum 경과시간 (100회 반복, n = 10_000_000): 3s 4445ms
 - 느려졌다?
- 전체 스트림을 long[] 으로 변환하는 과정이 있었기 때문
 
 
포크/조인 사용 시 주의사항#
- join은 서브태스크가 종료될때 까지 호출자를 블럭시킴
 - RecursiveTask 내에서는 ForJoinPool의 invoke 메서드 사용 X
 - 서버태스크에 fork 메서드를 호출해 ForJoinPool의 일정을 조절할 수 있음
- 예제에서 left, right 둘다 fork하는 것 보단 한족에선 compute 로 재활용이 좋음
 
 - 병렬계산은 디버깅이 어려움
 - 순차 처리보다 무조건 빠르지 않음
 
작업 훔치기#
- 실제로는 코어 개수와 상관없이 적절한 크기로 분할된 많은 태스크를 forking 하는 것이 바람직
 - 작업 훔치기 (work stealing)
- ForJoinPool의 모든 스레드를 거의 공정하게 분할 함
 - 어떤 스레드가 작업이 끝나면 작업 대기 큐에서 바로 꺼내 작업함
 - 태스크 분할 시 작업 대기 큐로 바로 전송
 
 
Spliterator로 스트림 데이터 쪼개기#
- Java 8
 - 스트림을 자동으로 분할하는 기법 Spliterator
 - 분할할 수 있는 자동 반복자
 - 병렬 작업에 특화