[JAVA] stream은 얼마나 빠른가?
2023. 3. 6. 22:57
원문 :
https://devm.io/java/java-performance-tutorial-how-fast-are-the-java-8-streams-118830
개요
- stream은 JAVA 8 이후 큰 변화 중 하나다.
- stream의 주요 장점은 간결성과 병렬처리인데, 성능 측면에서는 어떨까 궁금해서 article을 찾아보게 되었다.
서론
- 아티클의 저자 또한 스트림의 도입으로 병렬 프로그래밍의 접근성을 높였다고 말한다.
- 그럼 과연 parallel stream operation이 squential operation 보다 항상 빠를까?에 대한 의문이 생겼다고 한다.
- 보통 병렬 실행이 순차 실행보다 빠를것이라고 예상한다.
- 실재로도 그럴것인가에 대한 측정을 해보았지만, 이 결과를 맹신해서는 안된다고 주의하고 있다.
실험 1 - stream과 for문 비교
- 실험 환경
- dual core
- 500,000만개의 랜덤 int value
- 이 중 최대값을 찾는 작업을 진행
//case - primitive type
//for 문
int[] a = ints;
int e = ints.length;
int m = Integer.MIN_VALUE;
for(int i=0; i < e; i++)
if(a[i] > m) m = a[i];
// stream
int m = Arrays.stream(ints)
.reduce(Integer.MIN_VALUE, Math::max);
- 결과
- for문 : 0.36ms
- seq stream : 5.35ms
//case - wrapped type
//for문
int m = Integer.MIN_VALUE;
for (int i : myList)
if (i>m) m=i;
//stream
int m = myList.stream()
.reduce(Integer.MIN_VALUE, Math::max);
- 결과
- for문 : 6.55ms
- seq stream : 8.33ms
실험 1 해석
- 놀랍게도 for문이 단순 sequential stream 보다 나은 성능을 보여주었다.
- for문이 빠른 이유는 40년 넘는 세월동안 JIT 컴파일러가 for문에 더 최적화 되어있을거란 분석이다.
- primitive type과, wrapped type일때 성능차이가 달라지는 이유는 다음과 같다고 설명한다.
- array element에 접근하는 것은 index 기반 메모리 접근이기 때문에 매우 빠르다.
- 반면 Collection의 경우 iterator기반이기 때문에 상대적으로 오버헤드가 발생한다
- 또한 boxing, unxboxing의 오버헤드 또한 발생한다.
- 하지만 우리는 계산적인 측면보다 메모리 접근에 초점을 둔 실험을 진행했다는 것을 염두해야한다.
- CPU 부담에 따라 결과가 언제든지 달라질 수 있다.
- 이 실험의 결론으로 가져가야 할 것은 '언제나 스트림이 빠른건 아닐지도 모른다'의 마음가짐 정도이다.
실험 2 - seq stream과 parallel stream 비교
- 그렇다면 스트림끼리의 성능 비교는 어떠할까?
- 똑같이 50만개의 int value로 실험 진행
- 듀얼코어이기 때문에 최대 두배의 성능을 기대한다.
- seq/par 비율이 2.0에 근접할 것을 기대한다.
//seq
int m = Arrays.stream(ints)
.reduce(Integer.MIN_VALUE, Math::max);
//parallel
int m = Arrays.stream(ints).parallel()
.reduce(Integer.MIN_VALUE, Math::max);
- 결과
- seq : 5.35ms
- parallel : 3.35 ms
- seq/par : 1.60
실험 2 해석
- 병렬이기 때문에 속도가 더 빠르게 나온것일까?
- 병렬 스트림 작업에 기여하는 여러 측면들이 있다.
- 스트림 소스의 분할이 어떻게 이루어지는가, 배열은 분할이 쉬운편이다.
- 상태 유지성을 말하는데, 위의 예시에서는 중간 연산이 많지않다.
- 만약 distinct()와 같은 중간 연산이 포함된다면 이 또한 성능에 영향을 미치게 될것이다.
- 스트림에 영향을 주는 요소가 많기 때문에, 단순히 위의 결과만 보고 스트림에 대해 오판해서는 안된다고 말한다.
저자의 첫 아티클이 2015년 7월 27일에 게시되었는데
이 후 무수한 폭격을 맞았는지... 같은해 11월 26일에 추가 아티클이 올라온다.
https://devm.io/java/follow-up-how-fast-are-the-java-8-streams-122522
여기서 저자는 이전 실험에 대한 항변(?)과 추가 실험을 진행하게 되는데
결국 위에서 말했던대로, 단순 순환 반복은 for문이 좀 더 최적화가 잘 되어있지 않을까 하는 의문과 원시타입과 래퍼클래스의 핸들링,CPU 부하가 그렇게 크지 않았다는 점을 포인트로 내용을 전개해가고 있다.
개인적으로 느낀점은
1. stream이 주는 간결성은 성능 외적인 장점이 많다는 것
2. 만약 내가 성능을 따진다면 단순 코드 구현적인 요소보다, 하드웨어적 요소 또한 고민을 해야한다는 것 (아키텍처에 대한 이해도 필요하다.)
'skill > JAVA' 카테고리의 다른 글
[Java] String, StringBuilder, StringBuffer 차이 (0) | 2021.08.23 |
---|---|
[JAVA][컬렉션 프레임워크] 2. Set, Map (0) | 2021.08.16 |
[JAVA] 람다 함수형 인터페이스 1 (0) | 2021.08.12 |
[JAVA][컬렉션 프레임워크] 1. List (0) | 2021.08.10 |
[JAVA] JVM 실행순서, 메모리구조 (0) | 2021.05.17 |