Java ‐ 스트림 병렬화는 주의해서 사용하라[Effective Java Item 48] - thought-corner/Backend-PlayGround GitHub Wiki

  • 자바 8부터는 parallel 메서드만 한 번 호출하면 파이프라인을 병렬 실행할 수 있는 스트림을 지원했다.
  • 스트림의 소스가 ArrayList, HashMap, HashSet, ConcurrentHashMap의 인스턴스거나 배열, int 범위, long 범위일 때 병렬화의 효과가 가장 좋다.
  • 해당 자료구조들은 모두 데이터를 원하는 크기로 정확하고 손쉽게 나눌 수 있어서 일을 다수의 스레드에 분배하기 좋다.
  • 나누는 작업은 Spliterator가 담당하며, Spliterator 객체는 Stream이나 Iterablespliterator() 메서드로 얻어올 수 있다.
  • 이 자료구조들은 원소들을 순차적으로 실행할 때 참조 지역성이 뛰어나다.

스트림 병렬화의 문제점

  • 데이터 소스가 Stream.iterate거나 중간 연산으로 limit를 쓰면 파이프라인 병렬화로는 성능 개선을 기대할 수 없다.
    • limit을 다룰 때 CPU 코어가 남는다면 원소를 몇 개 더 처리한 후 제한된 개수 이후의 결과를 버려도 아무런 해가 없다고 가정한다.

스트림 파이프라인의 명세 규약

  • 스트림을 잘못 병렬화하면 성능이 나빠질 뿐만 아니라 결과 자체가 잘못되거나 예상치 못한 동작이 발생할 수 있다.
  • Stream 명세는 이 때 사용되는 함수 객체에 관한 엄중한 규약을 정의해놨다. 예를 들어, Streamreduct() 연산에 건네지는 accumulatorcombiner 함수는 반드시 결합 법칙을 만족하고 간섭받지 않고 상태를 갖지 않아야 한다.

스트림 병렬화를 적용하기 전에 고려 사항

  • 스트림 병렬화는 오직 성능 최적화 수단으로 성능 테스트를 통해 가치가 있는지 확인해야 한다.
  • 보통은 병렬 스트림 파이프라인도 공통의 포크-조인 풀에서 수행되므로 잘못된 파이프라인 하나가 다른 부분의 성능에까지 악영향을 미칠 수 있다.

스트림 병렬화가 좋을 떄

  • 조건이 잘 갖춰지면 parallel() 메서드 호출 하나로 거의 프로세서 코어 수에 비례하는 성능 향상을 만끽할 수 있다.
  • 스트림의 소스가 ArrayList, HashMap, HashSet, ConcurrentHashMap의 인스턴스나 배열, int, long 범위일 때 병렬화의 효과가 가장 좋다.
    • 자료구조들은 모두 데이터를 원하는 크기로 정확하고 손쉽게 나눌 수 있어 일을 다수의 쓰레드에 분배하기 좋다.
    • 순차적으로 실행할 때 참조 지역성이 뛰어나다. 이 참조 지역성이란, 메모리에 연속으로 저장되어 있다는 의미를 말하는데 가장 뛰어난 자료구조는 기본타입의 배열이다.

Random값 스트림 병렬화

  • ThreadLocalRandom : 단일 쓰레드에서 사용하기 위해 만들어졌다.
  • SplittableRandom : 무작위 수를 위한 스트림 병렬화를 위해 설계되었다.
  • Random : 모든 연산을 동기화하기 때문에 최악의 연산을 자랑한다.