티스토리 뷰


JMH 툴을 통해 Collection 관련 객체에 대해 성능 테스트를 진행 해보자.

JMH 툴 설치가 아직 안되신 분들은 JMH 설치 설치 및 설정 방법 글을 참고 하기 바란다.

[엔지니어링/성능과 튜닝] - [성능과 튜닝] JMH 설치 및 설정 방법


Set 클래스 중 무엇이 가장 빠를까?


Set 관련 클래스들의 성능을 비교해 보기 위해 아래와 같이 JMH 테스트 코드를 만들었다.

데이터를 담을 때 얼마나 시간 차이가 발생하는지 확인해 보자.

@State(Scope.Thread) @BenchmarkMode({Mode.AverageTime}) @OutputTimeUnit(TimeUnit.MILLISECONDS) public class SetAdd { int LOOP_CONUT = 1000; Set<String> set; String data = "abcdefghijklmnopqrstuvwxyz"; @Benchmark public void addHashSet() { set = new HashSet<String>(); for (int loop = 0; loop < LOOP_CONUT; loop++) { set.add(data + loop); } } @Benchmark public void addTreeSet() { set = new TreeSet<String>(); for (int loop = 0; loop < LOOP_CONUT; loop++) { set.add(data + loop); } } @Benchmark public void addLinkedHashSet() { set = new LinkedHashSet<String>(); for (int loop = 0; loop < LOOP_CONUT; loop++) { set.add(data + loop); } } @Benchmark public void addHashSetWithInitialSize() { set = new HashSet<String>(LOOP_CONUT); for (int loop = 0; loop < LOOP_CONUT; loop++) { set.add(data + loop); } } public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(SetAdd.class.getSimpleName()) .forks(1) .build(); new Runner(opt).run(); } }


HashSet과 LinkedHashSet의 성능이 비슷하고, TreeSet은 성능 차이가 발생한다. 또한 데이터 크기를 미리 알고 있을 때 미세하지만 성능상 유리하다.


이번에는 Set 클래스들이 데이터를 읽을 떄 얼마나 차이가 발생하는지 확인해 보자.

@State(Scope.Thread)
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class SetIterate {

    int LOOP_COUNT = 1000;
    Set<String> hashSet;
    Set<String> treeSet;
    Set<String> linkedHashSet;
    
    String data = "abcdefghijklmnopqrstuvwxyz";
    String[] keys;
    
    String result = null;
    
    @Setup(Level.Trial)
    public void setUp() {
        
        hashSet = new HashSet<String>();
        treeSet = new TreeSet<String>();
        linkedHashSet = new LinkedHashSet<>();
        
        for (int loop = 0; loop < LOOP_COUNT; loop++) {
            
            String tempData = data + loop;
            hashSet.add(tempData);
            treeSet.add(tempData);
            linkedHashSet.add(tempData);
        }
    }
    
    @Benchmark
    public void iteaterHashSet() {
        
        Iterator<String> iter = hashSet.iterator();
        while(iter.hasNext()) {
            result = iter.next();
        }
    }
    
    @Benchmark
    public void iterateTreeSet() {
        
        Iterator<String> iter = treeSet.iterator();
        while (iter.hasNext()) {
            result = iter.next();
        }
    }

    @Benchmark
    public void iterateLinkedHashSet() {
        
        Iterator<String> iter = linkedHashSet.iterator();
        while (iter.hasNext()) {
            result = iter.next();
        }
    }
    
    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(SetIterate.class.getSimpleName())
                .forks(1)
                .build();

        new Runner(opt).run();
    }
}


LinkedHashSet이 가장 빠르고, HashSet, TreeSet 순으롣 데이터를 가져오는 속도가 느려진다.

일반적으로 Set은 여러 데이터를 넣어 두고 해당 데이터가 존재하는지를 확인하는 용도로 많이 사용된다. 따라서 데이터를 램덤하게 가져와서 재 테스트를 해보자.


public class RandomKeyUtil { public static String[] generateRandomSetKeysSwap(Set<String> set) { int size = set.size(); String[] result = new String[size]; Random random = new Random(); int maxNumber = size; Iterator<String> iterator = set.iterator(); int resultPos = 0; while (iterator.hasNext()) { result[resultPos++] = iterator.next(); } for (int loop = 0; loop < size; loop++) { int randomNumber1 = random.nextInt(maxNumber); int randomNumber2 = random.nextInt(maxNumber); String temp = result[randomNumber2]; result[randomNumber2] = result[randomNumber1]; result[randomNumber1] = temp; } return result; } public static int[] generateRandomNumberKeysSwap(int loop_count) { int[] result = new int[loop_count]; Random random = new Random(); for (int i = 0; i < loop_count; i++) { int randomNumber = random.nextInt(loop_count); result[i] = randomNumber; } return result; } }

@State(Scope.Thread) @BenchmarkMode({Mode.AverageTime}) @OutputTimeUnit(TimeUnit.MILLISECONDS) public class SetContains { int LOOP_COUNT = 1000; Set<String> hashSet; Set<String> treeSet; Set<String> linkedHashSet; String data = "abcdefghijklmnopqrstuvwxyz"; String[] keys; String result = null; @Setup(Level.Trial) public void setUp() { hashSet = new HashSet<String>(); treeSet = new TreeSet<String>(); linkedHashSet = new LinkedHashSet<>(); for (int loop = 0; loop < LOOP_COUNT; loop++) { String tempData = data + loop; hashSet.add(tempData); treeSet.add(tempData); linkedHashSet.add(tempData); } if (keys == null || keys.length != LOOP_COUNT) { keys = RandomKeyUtil.generateRandomSetKeysSwap(hashSet); } } @Benchmark public void containsHashSet() { for (String key : keys) { hashSet.contains(key); } } @Benchmark public void containsTreeSet() { for (String key : keys) { treeSet.contains(key); } } @Benchmark public void containsLinkedHashSet() { for (String key : keys) { linkedHashSet.contains(key); } } public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(SetContains.class.getSimpleName()) .forks(1) .build(); new Runner(opt).run(); } }

HashSet과 LinkedHashSet의 속도는 빠르지만, TreeSet의 속도는 느리다는 것을 알 수 있다. TreeSet을 만든 이유가 무엇일까?

public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable

TreeSet은 데이터를 저장하면서 정렬한다. TreeSet 인터페이스 중에 NavigableSet이 있다. 이 인터페이스는 특정 값보다 큰 값이나, 작은 값, 가장 큰 값, 가장 작은 값 등을 추출하는 메서드를 선언해 놓았으며 JDK 1.6부터 추가된 것이다.

즉, 데이터를 순서에 따라 탐색하는 작업이 필요 할때는 TreeSet을 사용하는 것이 좋다. 그럴 필요가 없을 때는 HashSet이나 LinkedHashSet을 사용하는 것을 권장한다.


댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함