java/기초문법

스트림 reduce(0, type::sum) vs sum()

wugawuga 2023. 4. 24. 20:15

reduce(0, type::sum) 이랑 sum() 은 도대체 무슨 차이일까??

sum() 을 확인해 보면 IntPipeLine, LongPipeLine 클래스(각 스트림 인터페이스의 구현체)인
reduce(0, XXX::sum) 이라는 메소드의 반환 값을 반환한다


아래는 LongStream 인터페이스 구현체인 LongPipeLine 에서의 sum() 메소드

@Override
public final long sum() {
    // use better algorithm to compensate for intermediate overflow?
    return reduce(0, Long::sum);
}

 

sum() 메소드는 reduce(0, Long::sum) 을 반환하기 때문에 둘은 똑같다

개발자는 한 글자라도 더 키보드를 덜 치는 것을 선호하니
reduce(0, Long::sum) 을 대신하는 sum() 이라는 편의 메소드를 제공을 해주는 것일까???

 

List<Long> longList = new ArrayList<>();
for (long i = 0; i < 5_000_000; i++) {
    longList.add(i);
}
Long sum = longList.stream()
                   .reduce(0L, Long::sum);

long sum = longList.stream()
                   .mapToLong(value -> value)
                   .sum();

 

reduce(0, Long::sum)

위 메소드를 이용해 이 모든 요소를 더 해 반환해주는 것을 리듀싱 연산이라 부른다

리듀싱 연산 : 모든 스트림 요소를 처리해 값으로 도출하는 것을 의미 즉, 도출한다는 것은 최종 연산을 뜻한다


이때 놓치지 말아야할 개념은 오토 박싱오토 언박싱이다

long value = 1L;
Long wrapperValue = value;


위 코드를 보면 new Long(value) 라는 코드 없이 value 를 바로 래퍼 클래스인 Long 으로 치환이 되는데 이때 오토 박싱이라고 부른다

List<Long> 이라는 컬렉션 데이터의 요소를 하나하나 뽑아서 오토 언박싱 -> 연산 -> 오토 박싱 는 마법 같은 코드가 숨겨져 있다

 

정말 오토 언박싱 -> 연산 -> 오토 박싱 이 이루어지는 것일까? 아래의 코드를 살펴보자.

스트림 코드가 시작되고 (long a, long b) 를 매개변수로 하는 long 클래스 오토 언박싱이 이루어진다

 

valueOf 메소드를 거치게 되는데, 이때 마지막 줄을 보면 return new Long(l); 이라는 코드를 볼 수 있을 것이다

다시 래퍼클래스를 만들게 되면서 사용자도 모르게 오토 언박싱과 오토 박싱이 실행된다

 

여기서 의문점이 드는 것은 왜 회색줄일까?

jvm 이 실행될 때 자주 사용이 된다고 예상한 자료형들을 미리 캐시값을 가지고 있게 되는데

그래서 if 문을 타고 LongCache.cache[(int)l + offset]; 코드가 실행된다

 

다시 이 코드를 파고 들어가보면 이미 -128 ~ 127 크기의 배열이 생성되고 각 값들이 채워져있다

위에 코드를 다시 보면 반복문이 0 부터 시작하는데 127 까지는 오토 언박싱/박싱이 이루어지지 않고 이미 캐싱처리 된 배열에서 값을 꺼내어 쓰게 된다

 

sum()

sum()을 들여다보면 reduce(0, Long::sum) 인데 왜 sum() 이 더 빠를까??
sum() 메소드를 쓰기 위해선 mapToLong(value -> value) 이라는 메소드를 체이닝 해야하는데,

기존 List<Long> 요소인 Long 을 long 으로 대체한다기 보다 새로운 값(long) 을 만들어 사용한다


reduce() vs sum()

reduce(0,  Long::sum) 는

  1. long 으로 오토 언박싱 후
  2. sum 연산
  3. Long 래퍼 클래스로 오토 박싱이 이루어진다

 

mapToLong(value -> value).sum() 은

  1. 전체 요소를 long 으로 오토 언박싱 후
  2. sum 연산

 

그래서?

reduce(0, Long::sum) 는 Long 래퍼클래스를 반환하고,
sum()은 long 클래스를 반환한다.

둘 성능을 비교했을 때 적게는 2배 많게는 6배 가까이 날 때도 있었다

 

결론

각 타입 클래스 스트림 인터페이스가 제공하는 메소드가 있을 경우에 그 메소드를 사용하는 것이 바람직하다