jdk8 HashMap Iterating 성능 비교

이종완·2022년 3월 4일
0

개발이야기

목록 보기
2/12
post-thumbnail

개요

Map은 키를 통한 빠른 데이터 접근이 용이하여 자바를 포함한 다양한 언어에서 자주 사용하는 자료구조다

자바에서 O(1)의 접근성을 가지는 Map 구현체 HashMap을 많이 이용하는데,

가끔은 저장된 값을 전부 순회해야 하는 경우가 생기기도 한다?
(애초에 그런 경우가 발생하지 않는게 가장 좋을 것이다...)

업무를 진행하는 과정에서, HashMap iterating이 필요한 일이 생기게 되었는데 성능 비교를 한번쯤은 해보고 싶었다

참조

참조글

위 참조글의 따봉 5개를 받은 답변에서 keySet for, keySet foreach, entrySet for, entrySet foreach, foreach의 전체 값 순회에 대한 밴치마크 테스트 결과를 제시하고 있다

결과적으로는 jdk8에서 entryset의 foreach 람다 처리가 가장 빠르다고 주장하고 있다.

환경

내가 성능 비교를 위해 사용한 환경은 다음과 같다

코드

테스트를 위해 준비한 샘플 코드는 다음과 같다

import java.util.Map;
import java.util.HashMap;

public class MapIterComp {
    
    private static int size = 10000, round = 5;
    private static long sum, start, end;

    private static void print(String name, long value) {

        System.out.print(String.format("%-20s: ", name));
        System.out.print(String.format("%5d", (end - start)));
        System.out.println(" ms");
    }

    private static void compare() {

        final Map<Integer, Integer> map = new HashMap<>();

        for (int i = 0; i < size; i++) map.put(i, i);

        System.out.println("[size]: " + size);
        System.out.println("[round]: " + round);
        
        // 1. key set for loop
        sum = 0;
        for (int i = 0; i < round; i++) {

            start = System.currentTimeMillis();
            for (Integer key: map.keySet()) sum += map.get(key);
            end = System.currentTimeMillis();
            sum += (end - start);
        }
        print("keySet() for", sum/round);

        // 2. key set for each
        sum = 0;
        for (int i = 0; i < round; i++) {

            start = System.currentTimeMillis();
            map.keySet().forEach(k -> sum += map.get(k));
            end = System.currentTimeMillis();
            sum += (end - start);
        }
        print("keySet() forEach", sum/round);

        // 3. entry set for loop
        sum = 0;
        for (int i = 0; i < round; i++) {

            start = System.currentTimeMillis();
            for (Map.Entry<Integer, Integer> e: map.entrySet()) sum += e.getValue();
            end = System.currentTimeMillis();
            sum += (end - start);
        }
        print("entrySet() for", sum/round);

        // 4. entry set for each
        sum = 0;
        for (int i = 0; i < round; i++) {

            start = System.currentTimeMillis();
            map.entrySet().forEach(e -> sum += e.getValue());
            end = System.currentTimeMillis();
            sum += (end - start);
        }
        print("entrySet() forEach", sum/round);

        // 5. values for loop
        sum = 0;
        for (int i = 0; i < round; i++) {

            start = System.currentTimeMillis();
            for (Integer value: map.values()) sum += value;
            end = System.currentTimeMillis();
            sum += (end - start);
        }
        print("values() for", sum/round);
        
        // 6. values for each
        sum = 0;
        for (int i = 0; i < round; i++) {

            start = System.currentTimeMillis();
            map.values().forEach(v -> sum += v);
            end = System.currentTimeMillis();
            sum += (end - start);
        }
        print("values() for each", sum/round);
        
        // 7. for each
        sum = 0;
        for (int i = 0; i < round; i++) {

            start = System.currentTimeMillis();
            map.forEach((k, v) -> sum += v);
            end = System.currentTimeMillis();
            sum += (end - start);
        }
        print("for each", sum/round);
    }

    public static void main(String[] args) {
        
        if (args.length == 2) {

            size = Integer.valueOf(args[0]);
            round = Integer.valueOf(args[1]);
        }

        if (args.length == 1) size = Integer.valueOf(args[0]);

        compare();
    }
}

테스트 결과

1만 루프, 각 10회차 평균

10만 루프, 각 10회차 평균

50만 루프, 각 10회차 평균

100만 루프, 각 10회차 평균

500만 루프, 각 10회차 평균

1000만 루프, 각 10회차 평균

5000만 루프, 각 10회차 평균

테스트 결론

entrySet().forEach 또는 values().forEach 가 좋은 듯하다

기존의 정통적인 for 루프 처리보다 jdk8에 추가된 forEach 방식이 훨신 성능이 우세한 것을 알 수 있었다
특히, 키와 밸류를 한번에 들고다녀야하기 때문에 더 무거울 것이라고 생각했던 entrySet이 생각보다 거의 가장 빠른 처리 속도를 보여준 것은 놀라웠다

해당 테스트코드는 정수에 대한 덧셈만 수행하는 간단한 액션만 들어있어서 시간차이가 미비하게 느껴지지만, 좀 더 복잡한 로직을 수행한다고 가정한다면 각 순회 코드들의 성능 차이는 더욱 커질 것이라고 생각한다

profile
안녕하세요...

0개의 댓글