이론 정리/java

자바 스트림 병렬성

철매존 2023. 12. 30. 18:07
728x90

병렬데이터 처리

스트림을 통한 순차 스트림을 사용하면 병렬 스트림으로 자연스럽게 바꿀 수 있다.

병렬 스트림

  • 각각의 스레드에서 처리할 수 있도록 스트림 요소를 여러 청크로 분할한 스트림
    • 그래서 병렬 스트림을 이용하면 모든 멀티코어 프로세서가 각각의 청크를 처리하도록 할당 가능
  • 예를 들어 n을 받아서 1~n까지 모두 더하는 경우 이를 무한스트림으로 할 수 있는데, 이거를 n이 커지면 병렬로 처리하는게 좋을 것이다.

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

  • 순차스트림에 parallel 메서드 호출하면 기존의 함수형 리듀싱 연산이 병렬로 처리됨
  • 저걸 쓰면 청크단위로 호출되는데, 스트림 자체에는 변화가 없고 내부적으로 불리언 플래그가 설정되는것
    • sequential은 반대로 병렬스트림을 순차스트림으로 바꾸는것
      • 두 메서드를 이용해서 병렬/순차 여부를 제어 가능
  • 병렬 스트림은 내부적으로 ForkJoinPool을 사용한다.

성능 측정

  • JMH라이브러리를 이용해 벤치마크 가능하다함
  • 벤치마크 해보면 순차적 스트림보다 빠를수도 있지만 느릴수도 있음
    • 그 이유는 두가지인데
      • 반복 결과로 박싱된 객체가 만들어지므로 숫자를 더하려면 언박싱을 해야한다.
      • 반복 작업은 병렬로 수행할 수 있는 독립 단위로 나누기 어렵다.
        • 예를들어 iterate는 본질적으로 순차적인데(이전꺼를 가지고 해야하니까) 여기서 쓰면 오히려 성능이 낮아짐
  • 특화된 메서드를 사용하는 것이 좋다.
    • 예를 들어 LongStream.rangeClosed 메서드의 경우는 iterate에 비해 두 가지 장점이 있는데
      • 기본형 long을 직접 사용해서 박싱/언박싱 오버헤드가 사라짐
      • 쉽게 청크로 분할할 수 있는 숫자 범위 생산 -> 예를 들어 1~20범위 숫자를 1-5, 6-10, 11-15, 16-20 이렇게

올바른 사용법

  • 병렬 스트림을 잘못 사용하면서 발생하는 많은 문제는 -> 공유된 상태를 바꾸는 알고리즘을 사용해서 발행한다.
    • 당연한거지만 순차적으로 수행하지 않으면 안되니까
  • 그리고 양을 기준으로 병렬 스트림 사용을 결정하는것도 적절치 않음
    • 그냥 순차/병렬에서 뭐가 나은지 측정해보자
  • 박싱을 주의하자
    • 기본형 특화 스트림 위주로 쓰는게 좋음
  • 몇몇 연산은 순차 스트림보다 성능이 떨어짐
    • limit이나 findFirst처럼 요소의 순서에 의존하는 경우 등
  • 전체 파이프라인 연산 비용 고려
    • 처리해야 하는 요소 수가 N이고 하나의 요소를 처리하는 데 드는 비용을 Q라 하면 비용은 N*Q으로 예상할 수 있음.
  • 소량의 데이터에서는 별 도움이 안된다.
    • 병렬화 과정에서 생기는 부가 비용이 있으니까
  • 스트림을 구성하는 자료구조가 적절한지?
  • 스트림의 특성과 파이프라인 중간 연산이 스트림의 특성을 어떻게 바꾸는지에 따라 분해 과정의 성능이 달라질 수 있다.
    • 예를 들어 SIZED 스트림은 정확히 같은 크기의 두 스트림으로 분할 가능해서 효과적
    • 근데 필터 연산은 예측이 어려워서 효과적인지 알 수 없음
  • 최종 연산의 병합 과정 비용을 살펴봐야한다.
    • 이 병합 과정이 비싸면 병렬 스트림 이점으로 얻은 이익이 상쇄될 수 있다.
  • 내부 인프라구조도 살펴봐야한다.

포크 조인

이거로 공부했었음

정리

  • 내부 반복을 이용하면 명시적이 아니어도 스트림으로 병렬처리 가능
  • 꼭 이게 빠른건 아니니까 측정을 잘 해보자
  • 가능하면 기본형 특화를 쓰는게 좋다.
  • 포크/조인 프레임워크는 병렬화될 수 있는 태스크를 작은 태스크로 분할한 다음에 이거를 각각의 스레드로 실행하여 그 서브태스크 결과를 합쳐 최종 결과로 생산!!
  • Spliterator은 탐색하려는 데이터를 포함하는 스트림을 어떻게 병렬화할 것인지 정의한다.