import java.math.BigInteger;
import java.util.stream.Stream;
import static java.math.BigInteger.*;
public class MersennePrimes {
public static void main(String[] args) {
primes().map(p -> BigInteger.valueOf(2).pow(p.intValueExact()).subtract(ONE))
.filter(mersenne -> mersenne.isProbablePrime(50))
.limit(20)
.forEach(System.out::println);
}
// 소수를 무한히 반환하는 스트림
static Stream<BigInteger> primes() {
return Stream.iterate(BigInteger.valueOf(2), BigInteger::nextProbablePrime);
}
}
싱글 스레드: 12.5 sec (실제로 약 6.9초)
성능 향상을 목적으로 paralle()을 사용하여 병렬 스레드로 처리한다면?
아무것도 출력하지 못하거나 응답 불가 상태가 됨 (liveness failure)
데이터 소스가 Stream.iterate거나 중간 연산으로 limit를 쓰면 파이프라인 병렬화로는 성능 개선을 기대할 수 없다.
파이프라인 병렬화는 limit을 다룰 때 CPU 코어가 남는다면 원소를 몇 개 더 처리한 후 제대한 개수 이후의 결과를 버려도 된다고 가정한다.
4개의 코어가 있다고 가정하면 20번째 계산이 수행되는 시점에 (2^20) 코어 3개가 놀게된다. 이 때 21, 22, 23번째 계산이 병렬로 수행되는데 이 계산들이 20번째 계산보다 2배, 4배, 8배의 시간이 더 필요하다.
=> 즉, 계산이 끝나기 전에 놀고 있는 cpu가 계속 생겨나서 끝도 없이 계산이 반복된다.
스트림의 소스가 ArrayList, HashMap, HashSet, ConcurrentHashMap의 인스턴스거나 배열, int 범위, long 범위일 때 병렬화의 효과가 가장 좋다.
데이터를 원하는 크기로 정확하고 손쉽게 나눌 수 있어서 다수의 스레드에 분배하기 좋다.
원소들을 순차적으로 실행할 때 참조 지역성(local of reference)이 뛰어나다.
데이터를 주 메모리에서 캐시 메모리로 전송되는 시간이 절약되기 때문에
단, 참조들이 가리키는 실제 객체가 메모리에서 서로 떨어져 있을 경우엔 참조 지역성이 나빠짐
기본 타입의 배열이 참조 지역성이 가장 좋음 (참조가 아닌 데이터 자체가 연속해서 저장되기 때문에)
자바 멀티 스레드 역사
parallel
)자바로 동시성 프로그램 작성은 쉬워졌지만, 올바르고 빠르게 작성하는 일은 여전히 어려운 작업이다. 동시성 프로그래밍을 할 때는 안전성(safety) 와 응답 가능(liveness) 상태를 유지해야한다.
메르센 소수 예제
데이터 소스가 Stream.iterate거나 중간 연산으로 limit를 쓰면 파이프라인 병렬화로는 성능 개선을 기대할 수 없다.
파이프라인 병렬화는 limit을 다룰 때 CPU 코어가 남는다면 원소를 몇 개 더 처리한 후 제대한 개수 이후의 결과를 버려도 된다고 가정한다.
4개의 코어가 있다고 가정하면 20번째 계산이 수행되는 시점에 (2^20) 코어 3개가 놀게된다. 이 때 21, 22, 23번째 계산이 병렬로 수행되는데 이 계산들이 20번째 계산보다 2배, 4배, 8배의 시간이 더 필요하다. => 즉, 계산이 끝나기 전에 놀고 있는 cpu가 계속 생겨나서 끝도 없이 계산이 반복된다.
스트림의 소스가 ArrayList, HashMap, HashSet, ConcurrentHashMap의 인스턴스거나 배열, int 범위, long 범위일 때 병렬화의 효과가 가장 좋다.
스트림 파이프라인의 종단 연산의 동작 방식이 병렬 수행 효율에 영향을 준다.
스트림을 잘못 병렬화하면 성능이 나빠질 뿐만 아니라 결과 제체가 잘못되거나 예상 못한 동작이 발생할 수 있다.
스트림 병렬화는 오직 성능 최적화 수단이다.