아이템 48. 스트림 병렬화는 주의해서 적용하라

wisdom·2022년 9월 23일
0

Effetctive Java

목록 보기
48/80
post-thumbnail

자바 8부터는 parallel 메서드만 한 번 호출하면 파이프라인을 병렬 실행할 수 있는 스트림을 지원한다.

그러나 스트림을 올바로 병렬화하는 것은 매우 어려운 작업이다.

스트림을 잘못 병렬화하면 (응답 불가를 포함해) 성능이 나빠질 뿐만 아니라 결과 자체가 잘못되거나 예상 못한 동작이 발생할 수 있다.

환경이 아무리 좋더라도 다음과 같은 경우 파이프라인 병렬화로는 성능 개선을 기대할 수 없다.

  • 데이터 소스가 Stream.iterate 인 경우
  • 중간 연산으로 limit 를 쓰는 경우

1. 병렬화가 적합한 경우

1) 스트림의 소스가 ArrayList, HashMap, HashSet, ConcurrentHashMap의 인스턴스거나 배열, int 범위, long 범위인 경우

  • 데이터를 원하는 크기로 정확하고 손쉽게 나눌 수 있어 다수의 스레드 분배하기 좋다.
  • 순차적으로 실행할 때 참조 지역성이 뛰어나다.
    참조 지역성이 가장 뛰어난 자료구조는 기본 타입 배열이다.

2) 종단 연산이 축소(reduction)이거나 매칭인 경우

  • 종단 연산에서 수행하는 작업량이 파이프라인 전체 작업에서 상당 비중을 차지하면서 순차적인 연산은 병렬 효과가 제한적이다.
  • 종단 연산 중 병렬화에 가장 적합한 것은 축소(reduction)이다.
    reduce 메서드 (중 하나), min, max, count, sum
  • 조건이 맞으면 바로 반환되는 메서드도 병렬화에 적합하다.
    anyMatch, allMatch, noneMatch

🔖 가변 축소 (mutable reduction)를 수행하는 Stream의 collect 메서드는 병렬화에 적합하지 않다.


2. 병렬화가 적합한 예제

조건이 잘 갖춰지면, parallel 메서드 호출 하나로 거의 프로세서 코어 수에 비례하는 성능 향상을 만끽할 수 있다.

// 소수 계산 스트림 파이프라인
static long pi(long n) {
    return LongStream.rangeClosed(2, n)
            .parallel() // 병렬화
            .mapToObj(BigInteger::valueOf)
            .filter(i -> i.isProbablePrime(50))
            .count();
}

여기서 parallel이 없어도 동작하지만 성능이 매우 향상된다. (저자 컴퓨터 기준 31초 → 9.2초)


3. 병렬화 시 주의사항

1) 파이프라인이 수행하는 진짜 작업이 병렬화에 드는 추가 비용을 상쇄하지 못한다면 성능 향상은 미미할 수 있다.

  • 데이터 소스 스트림이 효율적으로 나눠지고, 병렬화하거나 빨리 끝나는 종단 연산을 사용하고, 함수 객체들이 간섭하지 않아도 마찬가지다.

🔖 성능 향상 측정하는 간단한 방법

원소 수 x 원소당 수행되는 코드 줄 수 >= 수십만
스트림 안의 원소 수와 원소당 수행되는 코드 줄 수를 곱하여 얻은 값이 최소 수십만은 되어야 성능 향상을 맛볼 수 있다.

2) 운영 시스템과 흡사한 환경에서 테스트하는 것이 좋다.

  • 변경 전후로 반드시 성능을 테스트하여 병렬화를 사용할 가치가 있는지 확인해야 한다.
  • 이때, 잘못된 파이프라인 하나가 시스템의 다른 부분의 성능에까지 악영향을 줄 수 있음을 유념하자.

3) 무작위 수들로 이뤄진 스트림을 병렬화하려거든 ThreadLocalRandom (혹은 Random) 보다는 SplittableRandom 인스턴스를 이용하자.

  • SplittableRandom : 정확히 이럴 때 쓰고자 설계된 것이라 병렬화하면 성능이 선형으로 증가한다.
  • ThreadLocalRandom : 단일 스레드에서 쓰고자 만들어졌기 때문에 SplittableRandom 만큼 빠르지 않다.
  • Random : 모든 연산을 동기화하기 때문에 병렬 처리하면 최악의 성능을 보일 것이다.

📌 핵심 정리

계산도 올바로 수행하고 성능도 빨라질 거라는 확신 없이는 스트림 파이프라인 병렬화는 시도조차 하지 말라.
스트림을 잘못 병렬화하면 프로그램을 오동작하게 하거나 성능을 급격히 떨어뜨린다.
병렬화하는 편이 낫다고 믿더라도, 수정 후의 코드가 여전히 정확한지 확인하고 운영 환경과 유사한 조건에서 수행해보며 성능지표를 유심히 관찰하라.
그래서 계산도 정확하고 성능도 좋아졌음이 확실해졌을 때, 오직 그럴 때만 병렬화 버전 코드를 운영 코드에 반영하라.

profile
백엔드 개발자

0개의 댓글