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

신명철·2022년 3월 14일
0

Effective Java

목록 보기
46/80

동시성 프로그래밍

자바로 동시성 프로그램을 작성하는 것은 쉬워지고 있짐나 올바르고 빠르게 작성하는 일은 어렵다. 동시성 프로그래밍을 할 때는 안전성과 응답 가능 상태를 유지하기 위해 애써야 한다. 병렬 스트림 파이프라인도 다를 바 없다.

병렬 스트림 파이프라인

병렬 스트림 파이프라인에는 주의해야할 점이 있다. 데이터 소스가 Stream.itearte 이거나 중간 연산으로 limit 을 쓰면 파이프라인 병렬화로 인한 성능 개선을 기대할 수 없다는 점이다. 오히려 스트림 라이브러리가 파이프라인을 병렬화하는 방법을 찾지 못해서 심각한 성능 저하가 일어날 수도 있다.

병렬 스트림 파이프라인은 스트림의 소스가 ArrayList, HashMap, HashSet, ConcurrentHashMap의 인스턴스거나 배열, int 범위, long 범위일 때 효과가 좋다. 이 자료구조들은 모두 데이터를 원하는 크기로 정확하고 손쉽게 나눌 수 있어서 다수의 스레드에게 작업을 분배하기 좋다는데 있다.

나누는 작업은 Spliterator가 담당하며 Spliterator 객체는 Stream이나 Iterable의 spliterator 메소드로 얻을 수 있다. 이 자료구조들의 특징은 참조 지역성이 뛰어나다는 것이다. 참조 지역성이 낮으면 스레드는 데이터가 주 메모리에서 캐시 메모리로 전송되어 오기를 기다리며 대기를 하게 된다. 참조 지역성은 다량의 데이터를 처리하는 벌크 연산을 병렬화할 때 아주 중요한 요소로 작용된다.

스트림 파이프라인의 종단연산

스트림 파이프라인의 종단 연산의 동작 방식 역시 병렬 수행 효율에 영향을 준다. 종단 연산의 작업량이 파이프라인 전체 작업에서 비중이 높으면서 순차적인 연산이라면 병렬 스트림 파이프라인의 효과는 적어진다. 가장 적합한 종단 연산은 reduction이다.

reduction은 파이프라인에서 만들어진 모든 원소를 하나로 합치는 작업으로 Stream의 reduce 메소드 중 하나, 혹은 min, max, count, sum과 같이 완성되는 메소드 중 하나를 선택해 수행한다. anyMatch, allMatch, noneMatch 처럼 조건에 맞으면 바로 반환되는 메소드도 병렬화에 적합하다. Stream의 collect 메소드는 적합하지 않다. 컬렉션들을 합치는 비용이 크기 때문이다.

병렬화의 부작용

병렬화에 대해서 잘 모른다면 사용을 안하는게 낫다. 스트림을 잘못 병렬화한다면 성능이 나빠질 뿐 아니라 결과 자체가 잘못되거나 예상하지 못한 동작이 발생하는 safety failure가 발생할수 있기 때문이다.

safety failure는 파이프라인이 사용하는 mappers, filters 혹은 개발자가 제공한 다른 함수 객체가 명세대로 동작하지 않았을 때 벌어질 수 있다. 그렇기 때문에 Stream 명세는 이때 사용되는 함수 객체에 관해 규약을 엄중히 걸어놓았다. 규약은 다음과 같다.

  • Stream의 reduce 연산에 건네지는 accumulator(누적기)와 combiner(결합기) 함수는 반드시 결합법칙을 만족해야 한다. ((a op b) op c = a op (b op c))
  • 간섭받지 않아야 한다 (non-interfering) - 파이프라인이 수행되는 동안 데이터소스가 변경되지 않아야한다.
  • 상태를 갖지 않아야 한다 (stateless)
    위의 요구사항을 지키지 못하더라도 순차적으로 실행하면 올바른 결과를 얻을 수 있다. 하지만 병렬로 수행하면 기대한 결과가 나오지 않을 수 있고, 실패할 수 있으니 주의해야 한다.

스트림 병렬화는 오직 성능 최적화 수단임을 기억해야한다. 보통 병렬 스트림 파이프라인도 공통의 포크-조인풀에서 수행되므로 잘못된 파이프라인 하나가 시스템의 다른 부분의 성능에까지 악영향을 줄 수 있음을 유념해야한다.

무작위 수들로 이루어진 스트림을 병렬화하려면 SplittableRandom 인스턴스를 이용하자. SplittableRandom은 이럴때 쓰고자 설계된 것이라 병렬화하면 성능이 선형적으로 증가한다. ThreadLocalRandom은 단일 스레드를 위해 설계를 했고 Random은 모든 연산을 동기화 하기 때문에 적합하지 않다.

profile
내 머릿속 지우개

0개의 댓글