1️⃣ 요소 병렬 처리란?
스트림을 사용하면 내부 반복자(Internal Iterator)를 통해 데이터를 자동으로 분할하여 처리할 수 있다.
이러한 병렬 처리(Parallel Processing)는 요소를 여러 개의 스트림으로 나누고, 각 스트림을 여러 개의 스레드에서 동시에 실행하는 방식이다.
🔹 병렬 처리의 목적
- 대량의 데이터 처리 속도를 향상
- 멀티코어 CPU의 성능을 극대화
- 병렬 연산을 통해 빠른 결과 도출
✅ 하지만!
항상 병렬 처리가 빠른 것은 아니다.
적절한 상황에서 사용해야 효과적이다.
2️⃣ 동시성과 병렬성의 차이
💡 동시성(Concurrency)과 병렬성(Parallelism)의 개념을 정확히 이해해야 한다.
개념 | 설명 |
동시성 (Concurrency) | 하나의 코어에서 여러 작업을 번갈아 가며 실행하여 마치 동시에 실행되는 것처럼 보이는 방식 |
병렬성 (Parallelism) | 여러 개의 코어에서 여러 작업을 동시에 실행하는 방식 |
🔸 동시성 예시
→ 하나의 코어에서 작업1 → 작업2 → 작업3 → 작업1 … 순차적으로 실행됨.
→ 여러 작업이 번갈아 실행되므로 실제로는 동시에 실행되지 않지만 빠르게 전환되므로 동시 실행처럼 보임.
🔸 병렬성 예시
→ 코어 1에서 작업1 실행, 코어 2에서 작업2 실행
→ 진짜 동시에 여러 작업을 실행하는 방식
💡 즉, 병렬 처리는 여러 개의 CPU 코어를 활용하여 작업을 동시에 수행하는 것이며, 성능 향상이 목적이다. 🚀
3️⃣ 병렬 스트림 (Parallel Stream)
자바는 병렬 처리를 위해 **병렬 스트림(Parallel Stream)**을 제공한다.
🔹 병렬 스트림을 사용하는 방법
1. 컬렉션에서 병렬 스트림을 생성
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> parallelStream = list.parallelStream(); // 병렬 스트림 생성
2. 기존 스트림을 병렬 스트림으로 변환
Stream<Integer> stream = list.stream();
Stream<Integer> parallelStream = stream.parallel(); // 기존 스트림을 병렬 스트림으로 변환
✅ 두 방법의 차이
- parallelStream() → 처음부터 병렬 스트림 생성
- parallel() → 기존의 스트림을 병렬 스트림으로 변환
💡 기본적으로 스트림은 순차적으로 실행되지만, 위 방법을 사용하면 병렬 처리 가능!
4️⃣ 병렬 스트림의 원리
🔹 병렬 스트림은 내부적으로 포크 조인(Fork-Join) 프레임워크를 사용
포크(Fork) 단계
- 데이터를 분할하여 여러 개의 작은 데이터 덩어리(서브셋)로 나눈다.
- 각 덩어리는 별도의 스레드에서 실행된다.
조인(Join) 단계
- 각각의 결과를 모아서 최종 결과를 생성한다.
🔹 포크 조인(Fork-Join) 처리 과정
1️⃣ 원본 데이터를 여러 개로 분할
2️⃣ 여러 개의 코어에서 동시에 처리
3️⃣ 처리된 결과를 병합하여 최종 결과 생성
5️⃣ 순차 스트림과 병렬 스트림 성능 비교
대량 데이터를 대상으로 순차 스트림과 병렬 스트림의 성능을 비교해 보자.
📝 예제 코드: 1억 개의 난수 평균 구하기
import java.util.*;
import java.util.stream.*;
public class ParallelStreamExample {
public static void main(String[] args) {
// 1억 개의 랜덤 숫자 리스트 생성
Random random = new Random();
List<Integer> numbers = IntStream.range(0, 100_000_000)
.map(i -> random.nextInt(101)) // 0~100 사이 난수 생성
.boxed()
.collect(Collectors.toList());
// 순차 스트림 성능 테스트
long startTime = System.nanoTime();
double avg1 = numbers.stream()
.mapToInt(Integer::intValue)
.average()
.orElse(0);
long endTime = System.nanoTime();
System.out.println("순차 스트림 처리 시간: " + (endTime - startTime) / 1_000_000 + " ms");
// 병렬 스트림 성능 테스트
startTime = System.nanoTime();
double avg2 = numbers.parallelStream()
.mapToInt(Integer::intValue)
.average()
.orElse(0);
endTime = System.nanoTime();
System.out.println("병렬 스트림 처리 시간: " + (endTime - startTime) / 1_000_000 + " ms");
}
}
📌 실행 결과 예시
순차 스트림 처리 시간: 5200 ms
병렬 스트림 처리 시간: 1200 ms
✅ 병렬 스트림이 훨씬 빠르게 처리됨!
6️⃣ 병렬 스트림 사용 시 고려할 점
1️⃣ 요소 개수가 적으면 병렬 스트림을 사용하지 않는 것이 좋다.
- 병렬 처리는 데이터를 분할하는 과정과 스레드를 관리하는 오버헤드가 발생한다.
- 데이터 개수가 적을 경우 오히려 순차 스트림이 더 빠를 수 있다.
2️⃣ 요소 하나를 처리하는 시간이 짧다면 병렬 스트림의 효과가 미미하다.
- 한 요소당 처리 시간이 짧다면 병렬 처리의 효과가 크지 않다.
- 반대로, 한 요소당 처리 시간이 길다면 병렬 처리가 효과적이다.
3️⃣ 스트림 소스의 종류에 따라 성능이 다르다.
- ArrayList, 배열(Array) → 인덱스를 사용하여 빠르게 요소 분할 가능 → 병렬 스트림 효과적
- LinkedList, HashSet, TreeSet → 요소를 분할하는 비용이 크다 → 병렬 처리 효과 적음
4️⃣ CPU 코어 수가 많을수록 병렬 처리가 유리하다.
- CPU 코어 개수가 많으면 병렬 처리를 할 수 있는 스레드가 증가하여 성능 향상 가능.
✅ 정리
개념 | 설명 |
동시성 (Concurrency) | 하나의 코어에서 여러 작업을 번갈아 실행 (빠르게 교대하며 실행) |
병렬성 (Parallelism) | 여러 개의 코어에서 여러 작업을 동시에 실행 |
병렬 스트림 | 내부적으로 Fork-Join 프레임워크를 이용하여 데이터 분할 후 병렬 처리 |
사용 방법 | parallelStream(), stream().parallel() |
주의점 | 요소 수가 적거나 처리 속도가 빠르면 병렬 처리가 더 느릴 수 있음 |
✅ 요소 개수가 많고, 하나의 요소 처리 시간이 길다면 병렬 스트림을 사용하면 성능 향상 효과가 크다! 🚀
참조:
이것이 자바다 _ 신용권
'Web Programming Language > JAVA' 카테고리의 다른 글
스트림(Stream) 활용 - 요소 정렬, 루핑(유소 개별 처리), 매칭(조건 만족 여부) (0) | 2025.03.06 |
---|---|
스트림(Stream) 활용 - 요소 정렬, 루핑(유소 개별 처리), 매칭(조건 만족 여부) (1) | 2025.03.06 |
스트림(Stream) 활용 - 리소스로부터 스트림 얻기, 요소 걸러내기(필터링), 요소 변환(매핑) (0) | 2025.03.06 |
스트림(Stream) - 스트림, 내부 반복자, 중간 처리와 최종 처리 (0) | 2025.03.06 |
람다식(Lambda Expression) - 메소드 참조, 생성자 참조 (1) | 2025.03.05 |
댓글