for-loop VS Stream

Subin Park·2022년 6월 27일
0

Stream

목록 보기
2/2

Stream을 사용하기 전에 검색을 해보며 이러한 글을 많이 보았다. Stream은 가독성만을 위한것이다, Stream은 성능면에서는 for-loop보다 떨어진다.,대규모 트래픽을 감당할때는 for-loop문이 좋다. 등등 대체로 Stream을 사용할 때에는 난발하지 않고 적재적소에 사용해야 한다는 평을 많이 본 것 같다.
이번 글은 Stream과 for-loop의 성능 차이 및 Stream 사용하는 이유에 대해 다뤄보기로 한다.

성능

for-loop와 Stream을 통해 각 특정 상황에서의 성능 비교를 진행하기로 하였다.

primary Type

// for-loop
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];
    }
}
// sequential stream
int m = Arrays.stream(ints).reduce(Integer.MIN_VALUE, Math::max);

500,000개의 원시타입 난수로 채워진 배열을 만들고 해당 배열의 최대값을 구한다.

단순한 원시타입을 반복문으로 처리 했을 시 for-loop가 Stream보다 15배 정도 더 빠르게 처리한다는 결과를 볼 수 있다.
Stream은 도입된지 얼마 안되었기 때문에 컴파일러 최적화가 부족해 이러한 결과를 나타내게 되었습니다.

wrapped Type

위의 실험에서 int[]을 ArrayList로 교체하고 다시 500,000개의 배열에 난수를 채워넣고 같은 과정을 반복해보았다.
결과는 아직도 for-loop가 우세하다. 하지만 기존 15배에서 1.2배까지 차이를 좁히게 되었다. 래퍼 클래스를 반복처리 하였을 시 원시 타입보다 훨씬 빠른 결과를 볼 수 있다.

왜일까?
그 이유는 ArrayList를 순환하며 heap에 저장되어 있는 값을 참조해야하기 때문에 직접 참조가 가능한 원시타입보다 순회 비용이 비싸지게 되어 for-loop와 Stream과의 성능 간극이 메꿔지게 된다.

expensive functionality

// for-loop
int[] a = ints;
int e = a.length;
double m = Double.MIN_VALUE;
for (int i = 0; i < e; i++) {
     double d = Sine.slowSin(a[i]);
     if (d > m) m = d;
}
// sequential stream
Arrays.stream(ints).mapToDouble(Sine::slowSin).reduce(Double.MIN_VALUE, Math::max);

slowSin()은 파라미터로 넘겨지는 메서드에 대해서 사인함수 값을 계산하고 이에 대한 테일러 급수를 계산하는 함수로 높은 계산 비용을 가지고 있다. 이를 통해 성능을 비교해보자.

slowSin() 함수를 사용하여 계산 비용이 커진 경우 매우 놀라운 결과를 볼 수 있다. slowSin() 함수 계산 비용이 순회 비용을 압도하여 Stream과 for-loop의 성능이 비슷해진 것이다.
이를 통해 순회비용과 계산 비용의 합이 충분히 크다면 Stream의 속도는 for-loop와 가까워진다는 것을 알 수 있다.

성능차이

단순 for-loop의 경우 오버헤드가 없는 단순한 인덱스 기반 메모리 접근이기 때문에 속도가 빠르다.
또한, stream은 2015년 이후에 도입된 반면 for-loop는 도입된지 40년이 넘는 시간이 지났기 때문에 컴파일러는 for-loop에 더 최적화가 잘 되어있을 수 밖에 없다.

Stream을 사용하는 이유

일단 for-loop를 사용하면 Stream을 사용하는 것과 같이 성능에 대해 크게 고민할 필요가 없어진다. 하지만 왜 Stream을 고집하는 것일까?

  • 코드의 가독성이 좋아진다. 중간 연산자를 통해 각각이 무슨 작업을 하는지 한눈에 알 수 있다.
  • parallelStream을 사용해 쉽게 병렬처리를 할 수 있다.
  • 순회 비용 및 내부 계산 비용이 커질 경우 Stream을 사용하면 for-loop와 큰 성능 차이 없이 사용할 수 있다.

이와같이 적절한 상황에 Stream을 사용하게 된다면 보는 사람도 편하고 코딩하는 사람도 편한 기술이 될 수 있겠다.

0개의 댓글