백엔드 개발자 블로그

순차 스트림 vs 병렬 스트림 본문

ETC/트러블 슈팅

순차 스트림 vs 병렬 스트림

backend-dev 2024. 7. 24. 15:41

SSAFY Collection 수업을 통해 병렬처리 기능을 가진 Stream API의 존재를 알게되었습니다.

해당 수업에서 강사님이 병렬처리 스트림은 스트림을 쪼개고, 스레드를 할당하고, 최종 결과를 하나로 합치는 과정이 필요하기에 빠르지 않을 수 있다는 말씀을 하셨습니다.

저는 평소에 병렬처리는 무조건 빠를거라고 생각했었기에 어떤 경우에 병렬처리 스트림이 더 좋은지 알아보겠습니다.

 


다양한 경우의 수 

  • 요소의 수 
    • 수가 적은 경우 - 순차 처리 
  • 요소당 처리시간 긴 경우 - 병렬
    • 데이터 전송시간보다 오래 걸리는 작업만 병렬처리하기
  • 스트림 소스 종류
    • ArrayList : 분할이 쉬워서 병렬
    • LinkedList : 분할이 어려워서 순차
  • 코어의 수
    • 싱글코어인 경우 스레드 수만 증가하고 동시성 작업으로 진행되기 때문에 - 순차 
  • 병렬로 수행하기 어려운 스트림 모델
    • 이전 연산의 결과가 완료되어야 넘어갈 수 있는 경우 분할이 안되기에 - 순차 
  • 박싱의 최소화
    • 박싱과 언박싱은 성능을 크게 하락시키기 때문에 - 기본형 사용
  • 순서에 의존하는 연산 - 순차

포크/조인 프레임워크

Recursive Task

스레드 풀 사용 -> RecursiveTask<R>의 서브 클래스 구현, R은 병렬화를 통해 연산된 결과 -> copute() 추상메소드 구현 : 테스크를 서브 테스크로 분할하는 로직 + 더이상 분하이 불가능할 때 서브 테스크의 결과를 조합

 

주의점

join()은 두 서브 테스크를 시작한 뒤 호출

RecursiveTask내에는 ForkJoinPool의 invoke()를 사용하면 안되고 compute()나 fork()메소드 호출해야함

분리된 테스크 중 한 작업에만 compute() 호출 -> 한 테스크는 스레드를 재사용할 수 있어 오버헤드 감소

compute()를 

 

작업 훔치기

많이 분할할 수록 좋음 -> 스레드 간의 작업 부하를 비슷하게 유지할 수 있게 됨

작업이 끝날 때마다 큐의 헤드에서 다른 테스크를 가져와 작업처리

한 스레드에서 작업이 끝나면 다른 스레드의 꼬리에서 작업을 훔쳐옴

 

Spliterator

분할할 수 있는 반복자

Collection은 spliterator를 구현해놨음

 

분할과정

trySplit()을 호출하여 Splitarator 생성 x 2^n -> 결과가 null이면 종료