Java 8 및 람다를 사용하면 컬렉션을 스트림으로 반복하고 병렬 스트림을 사용하기가 쉽습니다. docs의 두 가지 예 , parallelStream을 사용하는 두 번째 예 :
myShapesCollection.stream()
.filter(e -> e.getColor() == Color.RED)
.forEach(e -> System.out.println(e.getName()));
myShapesCollection.parallelStream() // <-- This one uses parallel
.filter(e -> e.getColor() == Color.RED)
.forEach(e -> System.out.println(e.getName()));
주문에 신경 쓰지 않는 한 항상 병렬을 사용하는 것이 유리합니까? 더 많은 코어에서 작업을 빠르게 나누는 것이 더 빠르다고 생각할 것입니다.
다른 고려 사항이 있습니까? 병렬 스트림은 언제 사용해야하고 비 병렬은 언제 사용해야합니까?
(이 질문은 항상 병렬 스트림을 사용하는 것이 좋은 생각이 아니라 병렬 스트림을 사용하는 방법과시기에 대한 토론을 시작하도록 요청됩니다.)
답변
병렬 스트림은 순차 스트림에 비해 오버 헤드가 훨씬 높습니다. 스레드를 조정하는 데 상당한 시간이 걸립니다. 기본적으로 순차적 스트림을 사용하고 병렬 스트림 만 고려합니다.
-
처리 할 대량의 항목이 있습니다 (또는 각 항목을 처리하는 데 시간이 걸리고 병렬화 가능)
-
처음에 성능 문제가 있습니다
-
이미 다중 스레드 환경에서 프로세스를 실행하지 않습니다 (예 : 웹 컨테이너에서 이미 병렬로 처리 할 요청이 많은 경우 각 요청 내에 병렬 계층을 추가하면 긍정적 효과보다 부정적인 영향을 줄 수 있음) )
귀하의 예에서에 대한 동기화 된 액세스에 의해 성능이 어쨌든 구동 되며이 System.out.println()
프로세스를 병렬로 만들면 아무런 영향을 미치지 않거나 심지어 부정적인 영향을 미칩니다.
또한 병렬 스트림이 모든 동기화 문제를 마술처럼 해결하지는 않습니다. 프로세스에서 사용되는 술어 및 함수가 공유 자원을 사용하는 경우 모든 것이 스레드로부터 안전해야합니다. 특히, 부작용은 당신이 평행하게 갈 때 정말로 걱정해야 할 것들입니다.
어쨌든 측정하지 마십시오! 병렬 처리가 가치가 있는지 아닌지 측정 만 할 수 있습니다.
답변
Stream API는 실행 방식에서 추상화 된 방식으로 계산을 쉽게 작성하고 순차적 및 병렬 간을 쉽게 전환 할 수 있도록 설계되었습니다.
그러나 그것이 쉽다고해서 항상 좋은 생각을 의미하는 것은 아니며, 사실, 당신이 할 수 있기 때문에 모든 곳 을 버리는 것은 나쁜 생각 .parallel()
입니다.
첫째, 병렬 처리는 더 많은 코어를 사용할 수있는 경우 빠른 실행 가능성 외에 다른 이점을 제공하지 않습니다. 병렬 실행에는 순차적 인 작업보다 항상 더 많은 작업이 필요합니다. 문제를 해결하는 것 외에도 하위 작업의 디스패치 및 조정도 수행해야하기 때문입니다. 여러 프로세서에서 작업을 분할하여 더 빨리 답변을 얻을 수 있기를 바랍니다. 이것이 실제로 발생하는지 여부는 데이터 세트의 크기, 각 요소에 대해 수행하는 계산량, 계산의 특성을 포함하여 많은 일에 달려 있습니다 (특히 한 요소의 처리가 다른 요소의 처리와 상호 작용합니까?) , 사용 가능한 프로세서 수 및 해당 프로세서와 경쟁하는 다른 작업 수입니다.
또한, 병렬 처리는 종종 순차적 구현에 의해 숨겨지는 계산에서 비결정론을 종종 노출한다는 점에 유의하십시오. 때때로 이것은 중요하지 않거나 관련된 작업을 제한하여 완화 할 수 있습니다 (즉, 축소 연산자는 상태 비 저장 및 연관이어야 함).
실제로 병렬 처리로 인해 계산 속도가 빨라지고 때로는 계산 속도가 느려지지 않으며 때로는 속도가 느려질 수도 있습니다. 순차적 실행을 사용하여 먼저 개발 한 다음 병렬 처리를 적용하는 것이 가장 좋습니다.
(A) 실제로 성능 향상에 이점이 있다는 것을 알고
(B) 실제로 향상된 성능을 제공 할 것입니다.
(A)는 기술적 문제가 아닌 비즈니스 문제입니다. 성능 전문가 인 경우 일반적으로 코드를보고 (B)를 결정할 수 있지만 현명한 길은 측정하는 것입니다. (그리고 (A)를 확신 할 때까지 귀찮게하지 마십시오. 코드가 충분히 빠르면 뇌주기를 다른 곳에 적용하는 것이 좋습니다.)
병렬 처리의 가장 간단한 성능 모델은 “NQ”모델입니다. 여기서 N은 요소 수이고 Q는 요소 당 계산입니다. 일반적으로 성능 이점을 얻으려면 제품 NQ가 일부 임계 값을 초과해야합니다. “1에서 N까지의 숫자 추가”와 같은 Q가 낮은 문제의 경우 일반적으로 N = 1000에서 N = 10000 사이의 손익분기 점이 표시됩니다. Q 문제가 높을수록 더 낮은 임계 값에서 손익분기 점이 발생합니다.
그러나 현실은 매우 복잡합니다. 따라서 전문 지식을 얻을 때까지 순차적 처리로 인해 실제로 비용이 드는 시점을 파악한 다음 병렬 처리가 도움이되는지 측정하십시오.
답변
나는 중 하나 지켜 프리젠 테이션 의 브라이언 게츠 (람다 표현식에 대한 Java 언어 설계자 및 사양 리드) . 그는 병렬화를 진행하기 전에 고려해야 할 다음 4 가지 사항을 자세히 설명합니다.
분할 / 분해 비용
– 때로는 분할 작업보다 단순히 분할 비용 이 더 비쌉니다!
작업 파견 / 관리 비용
– 다른 스레드로 작업을 처리하는 데 많은 시간을 할애 할 수 있습니다.
결과 조합 비용
– 때로는 많은 양의 데이터를 복사해야합니다. 예를 들어, 숫자를 추가하는 것은 저렴하지만 세트를 병합하는 것은 비쌉니다.
지역
– 방에있는 코끼리. 이것은 모두가 놓칠 수있는 중요한 포인트입니다. 캐시 미스로 인해 CPU가 데이터를 기다리는 경우 캐시 미스를 고려해야합니다. 그러면 병렬화를 통해 아무것도 얻지 못합니다. 그렇기 때문에 다음 인덱스 (현재 인덱스 근처)가 캐시되고 CPU가 캐시 미스를 경험할 가능성이 적어짐에 따라 어레이 기반 소스가 최상의 병렬 처리를하는 이유입니다.
또한 병렬 속도 향상의 가능성을 결정하는 비교적 간단한 공식에 대해서도 언급합니다.
NQ 모델 :
N x Q > 10000
여기서
N = 데이터 항목 수
Q = 항목 당 작업량
답변
JB가 머리에 못을 박았다. 내가 추가 할 수있는 유일한 것은 Java 8이 순수한 병렬 처리를 수행하지 않는다는 것 입니다. 예, 기사를 쓰고 30 년 동안 F / J를 해왔으므로 문제를 이해하고 있습니다.
답변
다른 답변은 병렬 처리에서 조기 최적화 및 오버 헤드 비용을 피하기 위해 이미 프로파일 링을 다루었습니다. 이 답변에서는 병렬 스트리밍을위한 이상적인 데이터 구조 선택에 대해 설명합니다.
원칙적으로, 병렬 처리의 성능 향상을 통해 스트림에 가장 적합합니다
ArrayList
,HashMap
,HashSet
, 및ConcurrentHashMap
인스턴스; 배열;int
범위; 그리고long
범위. 이러한 데이터 구조의 공통점은 모두 원하는 크기의 하위 범위로 정확하고 저렴하게 분할 할 수있어 병렬 스레드간에 작업을 쉽게 분할 할 수 있다는 것입니다. 이 작업을 수행하기 위해 스트림 라이브러리가 사용하는 추상화는 spliterator이며spliterator
onStream
및 메소드에서 리턴됩니다Iterable
.이러한 모든 데이터 구조가 공통적으로 갖는 또 다른 중요한 요소는 순차적으로 처리 될 때 우수한 참조 위치를 제공한다는 점입니다. 순차 요소 참조는 메모리에 함께 저장됩니다. 이러한 참조에 의해 참조되는 객체는 메모리에서 서로 가까이 있지 않을 수 있으며, 이는 참조의 지역성을 감소시킵니다. 참조 영역은 대량 작업을 병렬화하는 데 매우 중요합니다. 스레드가 없으면 스레드가 많은 시간을 유휴 상태로 보내고 메모리에서 프로세서의 캐시로 데이터가 전송 될 때까지 대기합니다. 최상의 참조 위치를 가진 데이터 구조는 데이터 자체가 연속적으로 메모리에 저장되므로 기본 배열입니다.
출처 : 항목 # 48 Joshua Bloch의 스트림을 병렬, 효과적인 Java 3e로 만들 때주의 사용
답변
무한 스트림을 제한과 병렬화하지 마십시오. 다음과 같은 일이 발생합니다.
public static void main(String[] args) {
// let's count to 1 in parallel
System.out.println(
IntStream.iterate(0, i -> i + 1)
.parallel()
.skip(1)
.findFirst()
.getAsInt());
}
결과
Exception in thread "main" java.lang.OutOfMemoryError
at ...
at java.base/java.util.stream.IntPipeline.findFirst(IntPipeline.java:528)
at InfiniteTest.main(InfiniteTest.java:24)
Caused by: java.lang.OutOfMemoryError: Java heap space
at java.base/java.util.stream.SpinedBuffer$OfInt.newArray(SpinedBuffer.java:750)
at ...
사용하면 동일 .limit(...)
설명 :
Java 8, 스트림에서 .parallel을 사용하면 OOM 오류가 발생합니다.
마찬가지로 스트림이 정렬되어 있고 처리하려는 것보다 훨씬 많은 요소가있는 경우 병렬을 사용하지 마십시오.
public static void main(String[] args) {
// let's count to 1 in parallel
System.out.println(
IntStream.range(1, 1000_000_000)
.parallel()
.skip(100)
.findFirst()
.getAsInt());
}
병렬 스레드가 중요한 0-100 대신 많은 수의 범위에서 작동하여 시간이 오래 걸리기 때문에 훨씬 오래 실행될 수 있습니다.