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

 

다음은 메르센 소수를 생성하는 프로그램에서 속도를 높이기 위해서 스트림 파이프라인의 parallel()을 호출 해봤다고하자. 

import java.math.BigInteger;
import java.util.stream.Stream;

public class StreamPallelExample {
    
    public static void main(String[] args) {
        //20개의 메르센 소수 생성
        primes().map(p -> (BigInteger.TWO).pow(p.intValueExact()).subtract(BigInteger.ONE))
                .parallel()
                .filter(mersenne -> mersenne.isProbablePrime(50))
                .limit(20)
                .forEach(System.out::println);
    }

    static Stream<BigInteger> primes() {
        return Stream.iterate(BigInteger.TWO, BigInteger::nextProbablePrime);
    }

}

 

불행하게도 실행 결과 아무것도 출려되지 않는다. 그 이유는 스트림 라이브러리가 파이프라인을 병렬화하는 방법을 찾아내지 못했기 때문이다. 

 

그 이유는 데이터 소스가 Stream.iterate거나 중간 연산으로 limit를 쓰면 병렬화로 성능 개선을 이룰 수가 없다. limit를 사용할 경우 제한 갯수 이후의 결과를 버려도 Integrity는 깨지지 않음을 가정한다. 매 소수마다 새로이 계산을 하다보니 성능이 끔찍하게 나빠진다. 위의 코드는 이 2가지 문제를 전부다 가지고 있다.

 

■ 병렬화가 효과가 좋은 경우

아래 자료 구조들은 데이터를 원하는 크기로 정확하고 손쉽게 나눌 수 있어서 다수의 스레드에 일을 분배하기 좋다는 특징이 있다. 분배는 Spliterator가 담당하며, Stream이나 Iterable의 spliterator 메서드로 객체를 얻어올 수 있다.

 

또한 이 자료구조들은 참조 지역성이 뛰어나다. 이웃한 원소의 참조들이 메모리에 연속해서 저장되어있다. 참조 지역성이 낮으면 스레드는 데이터가 주 메모리에서 캐시 메모리로 전송되어 오기를 기다리며 대부분 시간을 멍하게 보내게 된다. 따라서 참조 지역성은 다량의 데이터를 처리하는 벌크 연산을 병렬화할 때 아주 중요한 요소로 작용한다.

  • ArrayList
  • HashMap
  • HashSet
  • ConcurrentHashMap
  • Array
  • int range
  • long range

■ 종단 연산과 병렬화

종단 연산에서 수행하는 작업량이 파이프라인 전체 작업에서 상당 비중을 차지하면서 순차적인 연산이라면 파이프라인 병렬 수행의 효과는 제한될 수밖에 없다.

 

아래 메서드들은 병렬화에 적합한데 원소 처리 순서에 따른 결과가 달라지지 않고 일을 나누기에 좋거나(reduce), 조건에 맞으면 바로 반환되는 메서드(anyMatch 등)도 일을 나누기 좋다.

  • reduce
  • min
  • max
  • count
  • sum
  • anyMatch
  • allMatch
  • nonMatch

 

반면에 collect 메서드는 가변 축소가 이뤄지기 때문에 병렬화에 적합하지 않다. 컬렉션들을 합치는 부담이 크기 때문이다.

 

■ 안전 실패

스트림을 잘못 병렬화하면 성능이 나빠질 뿐만 아니라 결과 자체가 잘못되거나 예상 못한 동작이 발생할 수 있다. 결과가 잘못되거나 오동작하는 것을 "안전 실패" 라고 한다.

 

Stream 명세는 이때 사용되는 함수 객체에 관한 엄중한 규약을 정의해놨다.

 

  • associative : 결합법칙을 만족
  • non-interfering : 파이프라인 외부에서 소스를 변경하지 않음
  • stateless : 무상태

 

■ 핵심정리

  • 계산도 올바르고 성능도 빨라질꺼라는 확신 없이 스트림 파이프라인 병렬화는 시도조차 하지 말아라
  • 병렬화하는 편이 낫다고 믿더라도, 수정 후의 코드가 여전히 정확하게 동작하는지 수행해보며 성능지표를 유심히 관찰하라. 그 후 운영에 코드를 반영하라
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기