woowacourse-study/2022-modern-java-in-action

스트림은 만능일까?

Opened this issue · 1 comments

문제

스트림의 단점에 대해 알아보자

선정 배경

전반적으로 스트림의 장점에 대해서만 언급을 하는데
그렇게 스트림이 좋다면 굳이 for문이 있을 이유는 없지 않을까?
그럼에도 for문, forEach문이 있다는 건 스트림의 단점이 있다는 것을 반증하기 때문이다

관련 챕터

  • [4장] 스트림 소개

Stream은 성능 상의 단점이 있을 수 있다

  • 상황 : 단순히 500,000개의 int형 배열에서 최대값을 찾는 경우, 단순 for문의 경우 0.36ms이었으나 스트림의 경우 5.35ms
  • 이유 : JVM이 for문 최적화를 상당히 오래했기 때문에 최적화가 더 잘되기 때문이다. 이에 반해 스트림의 경우는 상대적으로 기간이 짧기 때문에 최적화가 for문 만큼은 잘 안되기 때문이다.

  • 상황 : ArrayList에서는 for문이 6.55ms였고 스트림은 8.33ms로 격차가 상당히 줄어들었는데
  • 이유 : 이는 배열에 비해 ArrayList의 순회 비용이 훨씬 크기 때문에 for문의 최적화가 무색해지기 때문이다. 또한, Integer는 primitive type인 int와 달리 stack에 아닌 heap에 저장되기 때문에 JVM이 직접 참조를 하지 못하고 간접적으로 참조를 하기 때문에 ArrayList는 배열보다 훨씬 순회 비용이 크다고 할 수 있다

  • 상황 : 순회 비용도 크고 연산 비용도 큰 상황. ArrayList에서 각 원소에 대해 굉장히 무거운 함수 x를 실행시킬 때는 사실상 for문과 스트림은 각각 11.84ms, 11.85ms
  • 이유 : 연산 비용이 for문의 최적화를 압도하기 때문

그렇다면 병렬 스트림과 단일 스트림은?

  • 상황 : 단순히 500,000개의 int형 배열에서 최대값을 찾는 경우, 단일 스트림의 경우 5.35ms이었으나 병렬 스트림의 경우 3.35ms, ArrayList에서도 비슷한 결과
  • 이유 : 원래 소스를 나눠서 각 스레드에게 나누는 연산 비용이 그렇게 비싸지 않기 때문

  • 상황 : 그러나 원소가 500,000개의 LinkedList의 경우는 단일 스트림의 경우 12.74ms이었으나 병렬 스트림의 경우 19.57ms
  • 이유 : 원래 소스를 나눠서 각 스레드에게 나누는 연산 비용이 비싸지기 때문

  • 상황 : 순회 비용도 크고 연산 비용도 큰 상황에서는 항상 병렬 스트림이 단일 스트림보다 빠름
  • 이유 : 연산 비용의 병렬화를 통해 이점을 얻었기 때문. 그러나, 스레드의 갯수가 2개로 나눴을 때 속도가 정확히 2배로 빨리지지는 않는데, 그 이유는 알고리즘 자체의 오버헤드나 병렬 스트림에도 속도를 높이는 한계가 존재하기 때문이다

결론

스트림은 "항상" for문 보다 빠르지 않다! for문보다 스트림을 써야 하는 경우는 소스 컬렉션이 충분히 큰 경우거나 컴퓨팅 연산이 굉장히 큰 경우이다. 또한 병렬 스트림을 써야 하는 경우는 소스 컬렉션이 split하기 쉬운 구조여야 한다
그럼에도 불구하고 성능이 극단적으로 중요한 도메인(예를 들어 가상화폐 매매 거래)이 아니라면, 협업자들의 가독성을 위해 for문 보다는 느릴수도 있는 스트림을 쓰는 것이 더 효율적일 수 있다