@Benchmark라는 애노테이션을 메서드에 선언하면 JMH에서 측정 대상 코드라고 인식한다.
하나의 클래스에 여러 개의 메서드가 존재할 수 있다.
애노테이션을 선언한 메서드가 끝나지 않으면 측정도 끝나지 않는다.
예외가 발생할 경우 해당 메서드의 측정을 종료하고, 다음 측정 메서드로 이동한다.
위의 수행 결과는 다음과 같다.
Set 클래스 중 무엇이 가장 빠를까?
먼저 HashSet, TreeSet, LinkedHashSet add를 비교해보면 다음과 같다.
HashSet과 LinkedHashSet의 성능이 비슷하고, TreeSet은 성능 차이가 발생한다 TreeSet은 레드블랙 트리에 데이터를 담는다. 값에 따라서 순서가 정해진다. 데이터를 담으면서 동시에 정렬을 하기 때문에 HashSet보다 성능상 느리다.
이번에는 Set 클래스들이 데이터를 읽을 때 얼마나 많은 차이가 발생하는지 확인해보자.
읽기에서는 크게 차이나지 않는다. Set을 Iterator 돌려서 사용하기도 하지만, 일반적으로 Set은 여러 데이터를 넣어 두고 해당 데이터가 존재하는지를 확인하는 용도로 많이 사용된다. 따라서 데이터를 Iterator로 가져오는 것이 아니라, 랜덤하게 가져와야만 한다.
HashSet과 LinkedHashSet의 속도는 빠르지만, TreeSet의 속도는 느리다는 것을 알 수 있다. 그러면 왜 결과가 항상 느리게 나오는 TreeSet 클래스를 만들었을까?
TreeSet은 데이터를 저장하면서 정렬한다. 구현한 인터페이스 중에 NavigableSet이 있다. 이 인터페이스는 특정 값보다 큰 값이나 작은 값, 가장 큰 값, 가장 작은 값 등을 추출하는 메서드를 선언해 놓았으며 JDK 1.6부터 추가된 것이다. 즉, 데이터를 순서에 따라 탐색하는 작업이 필요할때는 TreeSet을 사용하는 것이 좋다는 의미다. 하지만 그럴 필요가 없을 때는 HashSet이나 LinkedHashSet을 사용하는 것을 권장한다.
List 관련 클래스 중 무엇이 빠를까?
데이터를 넣는 속도부터 비교해보자.
어떤 클래스든 큰 차이가 없다는 것을 알 수 있다.
이번에는 데이터를 꺼내는 속도를 확인해보자.
ArrayList의 속도가 가장 빠르고, Vector와 LinkedList는 속도가 매우 느리다. LinkedList가 터무니없이 느리게 나온 이유는 LinkedList가 Queue 인터페이스를 상속받기 때문이다. 이를 수정하기 위해서는 순차적으로 결과를 받아오는 peek() 메서드를 사용해야 한다.
LinkedList 클래스를 사용할 때는 get() 메서드가 아닌 peek()이나 poll() 메서드를 사용해야 한다. 그런데 왜 ArrayList와 Vector의 성능 차이가 이렇게 클까? ArrayList는 여러 스레드에서 접근할 경우 문제가 발생할 수 있지만, Vector는 여러 스레드에서 접근할 경우를 방지하기 위해서 get() 메서드에 synchronized가 선언되어 있다. 따라서 성능 저하가 발생할 수 밖에 없다.
마지막으로 데이터를 삭제하는 속도를 비교해보자.
결과를 보면 첫 번째 값을 삭제하는 메서드와 마지막 값을 삭제하는 메서드의 속도 차이는 크다. 그리고 LinkedList는 별 차이가 없다. 그 이유가뭘까? ArrayList와 Vector는 실제로 그 안에 배열을 사용하기 때문에 0번째 인덱스 값을 삭제하면 나머지를 다 옮겨야 하기 때문이다.
Map 관련 클래스 중에서 무엇이 빠를까?
트리 형태로 처리하는 TreeMap 클래스가 가장 느린 것을 알 수 있다. 그리고 동기화처리를 하 Hashtable도 HashMap과 속도차이를 보인다.
지금까지 Set, List, Map 관련 클래스에 어떤 것들이 있고, 각각의 성능이 얼마나 되는지 정확하게 측정해 보았다. 하지만 일반적인 웹을 개발할때는 Collection 성능 차이를 비교하는 것은 큰 의미가 없다. 각 클래스에는 사용 목적이 있기 때문에 목적에 부합하는 클래스를 선택해서 사용하는 것이 바람직하다.
@State(Scope.Thread)
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.MICROSECONDS)
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<>();
treeSet = new TreeSet<>();
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())
.warmupIterations(3)
.measurementIterations(5)
.forks(1)
.build();
new Runner(opt).run();
}
}
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;
}
}