Why Java Streams Have No Place in Production — Here’s the Brutal Truth Nobody Admits

Introduction:

medium.com

 

Stream 성능문제에 관한 글

본 글에서는 For loop 연산에서 Stream으로 변경하니 더 많은 메모리와, GC부담, 낮은 성능이 발생했다는 말을 합니다.

변경 전

    public List<TransactionSummary> processTransactions(List<Transaction> transactions) {
        List<TransactionSummary> summaries = new ArrayList<>();
        for (Transaction txn : transactions) {
            if (txn.getAmount() > 0) {
                double fee = calculateFee(txn);
                summaries.add(new TransactionSummary(txn.getId(), txn.getAmount(), fee));
            }
        }
        summaries.sort(Comparator.comparing(TransactionSummary::getAmount).reversed());
        return summaries;
    }

변경 후

    public List<TransactionSummary> processTransactions(List<Transaction> transactions) {
        return transactions.stream()
                .filter(transaction -> transaction.getAmount() > 0)
                .map(transaction -> {
                    double fee = calculateFee(transaction);
                    return new TransactionSummary(transaction.getId(), transaction.getAmount(), fee);
                })
                .sorted(Comparator.comparing(TransactionSummary::getAmount).reversed())
                .collect(Collectors.toList());
    }

느려진 이유로는 filter, map, sorted 연산을 거칠 때 마다 변환된 temp List 가 생기게되고
이로인해 연산에 더 많은 메모리와, 시간이 필요하다는 주장입니다.

그런데 조금 의문인 것이, 자바 패러다임 자체가 함수형으로 작성하는 방향으로 가고있으며
대규모 트래픽을 다루는 조직에서도 Stream을 적극적으로 사용하는 추세인데.. 더 느리다면 왜 사용할까요?

중간 연산 Vs 최종 연산

Stream은 중간 연산과 최종 연산의 개념이 존재합니다.

대부분의 중간 연산에서는 Lazy Evaluation이 발생하며, 이는 연산의 절차를 기록만 하다가 최종 연산(collect/forEach)등 을 만날때에 실제로 실행됨을 의미합니다.

따라서 연산 단계마다 변환된 temp List가 생기는 것이 아니라 한 요소에 대해서 filter, map 연산을 적용하고 이를 최종 연산에 전달하게 됩니다.

따라서 중간 연산을 거칠때마다 temp list가 발생해 느려졌다는 주장은 틀렸습니다.

그럼 왜 느려졌는가?

대부분의 중간 연산에서는 Lazy Evaluation 처리를 합니다.
즉, 모든 중간 연산이 지연 평가가 발생하는 것은 아닌데요

위에서 사용된 sorted 연산은 정렬하는 중간 연산입니다.
하지만 정렬의 특성상 앞서 나온 모든 요소가 필요하며, 실제로 map 연산을 거친 원소를 최종 연산에 바로 전달하는 것이 아닌
모든 원소에 map을 적용한 결과를 메모리에 쌓은 후 sorted하게 됩니다.

For loop 에서는 전체에 대해서 변환 작업을 실행한 후 sort를 시행하기 때문에 성능이 잘 나오는 것으로
Stream 마지막에 따로 sort 연산을 한다면 비슷한 성능이 나오게 됩니다.

굉장히 자주 사용하지만, 모호한 부분들이 많은 것 같아요.

+ Recent posts