내일배움캠프/TIL

2024 08 05 TIL 사실 스트림이 만능이 아니었다면...?

동성만능크리너 2024. 8. 5. 22:04

https://durururuk.tistory.com/15

 

2024 08 01 TIL 스트림최고

Java 기초 주간 중 팀 프로젝트가 시작되었다. 수강생들의 명부, 성적 등을 관리하는 프로그램을 만드는 건데, 그간 스트림, 람다식을 제대로 연습하지 못했어서 이번 기회에 마음껏 연습할 수 있

durururuk.tistory.com

 

얼마전 스트림을 배우고 스트림에 대한 무한한 신뢰와 애정이 있었는데, 오늘 알고리즘 문제를 풀고 다른 사람의 풀이를 보면서 댓글을 보다가 

'for문 썼으면 효율적일텐데 스트림 왜 썼냐' '보기엔 좋은데 코테 채점하는 입장에선 마이너스다.' 이런 말이 적혀있었다. 

 

분명 저번에 스트림을 배울 때는 "내부반복자이므로, 처리 속도가 빠르고 병렬 처리에 효율적이다." 라고 배웠어서 이해를 할 수 없었다.

그래서 찾아보니 이러한 이유로 스트림이 더 느릴 수 있다는 것이다. 

1. 오버헤드 : Stream API는 내부적으로 많은 오버헤드를 가지고 있다. 스트림의 생성, 중간 연산의 연결, 최종 연산의 실행 등에서 발생하는 오버헤드가 존재한다. 이러한 오버헤드가 단순한 for문보다 더 많은 비용을 초래할 수 있다.

2. 메모리 사용 : Stream API는 종종 람다 표현식과 내부적으로 객체를 생성하는 방식으로 작동한다. 이는 추가적인 메모리 소비를 일으킬 수 있으며, 가비지 컬렉션의 부담을 증가시킬 수 있다. 

3.박싱과 언박싱. 기본 타입을 스트림에서 처리할 때에는 박싱과 언박싱이 필요할 수 있다. 이 과정에서 성능 저하가 발생할 수 있다. 예를 들어, 'IntStream'과 같은 원시 타입 스트림을 사용하지 않고 일반 'Stream<Integer>'를 사용할 경우 박싱과 언박싱이 빈번하게 발생할 수 있다. 

 

따라서, Stream API가 내부 반복자를 사용한다고 해서 무조건 더 빠른 것은 아니며, 상황에 따라 for문보다 효율이 떨어질 수 있다고 한다. 

 

아직 나는 어떤 상황에서 어떻게 썼을 때 for문보다 스트림이 더 비효율적인지 잘 모르겠어서 한 번 GPT한테 for문이 스트림보다 효율적인 케이스는 무언가 하고 물어봤다. 

 

더보기

Stream보다 for문이 더 효율적인 케이스는 여러 가지가 있습니다. 다음은 몇 가지 대표적인 경우입니다:

 

1. 단순 반복 작업

    반복문이 단순히 컬렉션을 순회하면서 값을 읽고 쓰는 작업을 할 때는 for문이 더 효율적일 수 있습니다. 예를 들어, 배열의 모든 요소에 접근하여 값을 더하는 작업 등은 for문이 오버헤드가 적어 더 빠르게 처리될 수 있습니다.

 

2. 인덱스 기반 접근이 필요한 경우

    리스트의 특정 인덱스에 접근하거나 인덱스를 기반으로 연산을 수행할 때는 for문이 더 적합합니다. Stream API는 인덱스를 직접 다루는 것이 복잡할 수 있습니다.

 

3. 중간에 반복을 멈춰야 하는 경우 (early exit)

    조건을 만족할 때 반복을 멈춰야 하는 경우, for문이 더 유리합니다. Stream API에서도 findFirst나 anyMatch 같은 연산을 사용할 수 있지만, 복잡한 조건에서는 for문이 더 직관적이고 효율적입니다.

 

4. 부수 효과가 있는 연산

    외부 상태를 변경하는 작업 (예: 로그 기록, 외부 변수 수정 등)은 Stream API보다 for문이 더 직관적이고 효율적일 수 있습니다. Stream API는 기본적으로 부수 효과 없는 순수 함수형 프로그래밍을 지향합니다.

 

5. 성능이 중요한 경우 

    매우 큰 데이터셋을 처리하거나, 성능이 중요한 실시간 애플리케이션에서는 Stream API의 오버헤드가 문제가 될 수 있습니다. 이때는 for문을 사용하는 것이 더 빠를 수 있습니다.

 아니 뭐 중간 연산이 복잡하고 그런 경우에 오버헤드가 발생해서 스트림이 더 비효율적일 수 있다는 이야기는 그렇구나 하고 넘어갔는데 단순 반복 작업도 느리면 스트림의 장점은 도대체 뭐고 내부반복자라서 생기는 이점은 다 어디로 갔단 말인가???

 

이것도 찾아보니 정말 슬픈 이야기가 있었다. 

스트림은 내부적으로 반복을 처리하여 코드의 간결성을 높이고 병렬 처리를 쉽게 할 수 있도록 도와준다. 하지만 내부 반복자를 사용하면 추가적인 추상화 계층이 생기고, 이로 인해 성능 오버헤드가 발생할 수 있다.

 

그리고 JVM 의 JIT 컴파일러는 for문에 대해 더 많은 최적화를 수행할 수 있고, 스트림의 경우 이러한 최적화를 적용하기 어려운 경우가 많다고 한다. 결국 스트림의 장점은 코드의 가독성과 유지보수성을 높이는 것이지, 성능 최적화에는 그렇게 좋은 물건은 아니라는 것이다. 

 

스트림을 배우고 나서는 웬만하면 스트림으로 코드를 짜고 도저히 스트림으로 구현을 못하겠으면 for문을 사용하려고 했으나, 어쩌면 나는 반대로 생각하고 있었던 게 아닐까 하는 생각이 들었다. 정말 마음에 들었던 스트림이 사실 그 정도 급은 아니었다는 걸 알아서 너무 슬픈 하루다..