메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

Trove를 사용하여 Collection 객체의 성능 개선하기

한빛미디어

|

2002-10-22

|

by HANBIT

11,230

저자: 다이온 알메르(Dion Almaer), 역 웹로거 김대곤

자바의 Collection API를 사용해 보았는가? 이 질문에 "아니오(No)"라고 말할 수 있는 사람은 거의 없을 것이다. Collection API는 자바가 개발자들에게 제공하는 유용한 API 중 하나이다. HashMap, ArrayList, LinkedList는 프로그램 이곳 저곳에서 때때로 사용되기 때문에, 이러한 객체들의 성능을 개선하는 것은 정말 큰 도움이 된다.

에릭(Eric D. Friedman)은 Trove라는 향상된 성능을 제고하는 Collection API를 개발하였다. Trove는 HashMap, HashSet, LinkedList와 같은 다양한 Collection 클래스를 제공하며, 마치 자바의 표준 Collection처럼 사용할 수 있다. 자바의 기본(primitive) 데이터 타입을 저장해야 하는 경우, 더욱 향상된 성능을 얻기 위한 방법도 제공한다.

본 기사는 다음과 같은 내용을 포함하고 있다.
  • Trove 클래스 사용하기
  • Trove와 JDK Collection를 자유롭게 선택할 수 있게 하는 Factory 클래스 만들기
  • 자바 기본 데이터 타입을 위한 Trove의 collection 클래스
Trove 클래스 사용하기

Trove를 사용하기 위한 절차는 이상할 정도로 간단하다. 코드를 다운로드 받아서 CLASSPATHlib/trove.jar파일만 추가하면 된다. Trove 클래스를 사용하기 위한 절차는 다음과 같다.
  1. 첫째, Trove 클래스를 임포트 한다.
    import gnu.trove.*;
    
  2. 둘째, collection 클래스를 생성하는 부분을 변경한다.
    // Map map = new HashMap(); // standard hashmap
    Map map = new THashMap();   // trove version of hashmap
    
  3. 셋째, Trove 클래스들은 T로 시작한다. 클래스 이름의 중복을 피하기 위한 방법이다. 그러므로 패키지 명 전체를 명시해야 할 이유가 없다. 따라서 java.util.HaspMap 또는 gnu.trove.HaspMap으로 사용할 필요가 없다.
Trove의 수행속도은 얼마나 빠른가?

ThashMap 클래스는 java.util.HaspMap 클래스보다 상당히 빠른 편이다. 이것이 어떻게 가능할까? 표준 HaspMap 클래스는 각 원소들이 Map.Entry를 가지고 있다. Trove는 주소법(Open Addressing)를 사용하며, 체인을 통하여 HashMap이 가진 문제을 해결한다. Trove의 Map에서 Map.entrySet를 사용하는 것은 좋은 방법이 아니다. 이것을 사용할 경우 Trove는 우리가 피하고자 했던 Map.Entry 객체를 생성할 것이다. 가능하면 이러한 작업은 수행하지 않는 것이 좋다. Trove를 구입하게 되면, 표준 자바 Collection API에서는 제공하지 않는 Trove만의 API를 사용할 수 있다. 즉, forEachEntry, forEachKey, forEachValue, transformValues와 같은 Trove만의 고유한 API를 사용함으로서 성능을 획기적으로 개선할 수 있다.

해시(Hash) 테이블은 크기를 가지고 있고, 데이터가 테이블의 크기를 초과할 때, 테이블의 크기가 늘어나야 한다. 해시 테이블의 크기가 소수(Prime Number)일 때, Hash 테이블의 성능이 좋은 것으로 나타나고 있다. Trove는 JDK와는 달리 Hash 테이블의 크기를 항상 소수로 가지고 있다. 다음은 필자의 시스템에서 Hash 테이블에 값을 입력하는 벤치마크 테스트를 수행한 결과이다.
Comparing 100000 Map.put() operations
--------------------------------------------------------------
JDK   average (msec): 321
Trove average (msec): 93

Difference: Trove is 3.45 times faster!
Trove는 얼마나 작은가?

Map.Entry 객체를 포함하지 않음으로써 생기는 부차적인 효과는 생성된 데이터의 구조가 적은 용량을 차지한다는 것이다. 이러한 점은 Set 클래스 구현에서 두드러지게 나타나는데, Trove의 Set은 JDK처럼 Maps에 사용하지 않는다.
Comparing size of Set implementation: 1,000 Integer"s
--------------------------------------------------------------

JDK   size: 46216 bytes
Trove size: 28856 bytes
Trove의 Collection은 JDK 사용시 필요한 메모리의 62%만을 요구한다.


Learning Java, Second Edition

참고 도서

Learning Java, Second Edition
Pat Niemeyer, Jonathan Knudsen




Trove와 JDK Collection를 자유롭게 선택할 수 있게 하는 Factory 클래스

위에서 살펴본 것처럼, 간단한 작업을 통해 프로그램에서 Trove 클래스를 사용할 수 있도록 변경할 수 있지만 프로그램에서 Trove나 JDK의 코드를 수정하지 않고 자유롭게 사용할 수 있도록 작은 스위치를 달아주는 것이 더욱 좋을 것이다. 그럼 이제부터 이러한 기능을 할 수 있는 CollectionFactory 클래스를 만들어 보도록 하자.

Trove를 사용할 것인지, JDK를 사용할 것인지는 자바 System 속성을 참조하여 결정되도록 한다. 사용자가 System 속성 중 usetrove의 속성값의 true로 설정하면, Trove 객체가 사용되고, usetrove 속성이 없거나 속성의 값이 true가 아니면 JDK 객체가 생성될 것이다. 사용자는 usetrove 속성값을 다음과 같이 두 가지 방법으로 설정할 수 있다.
  • 자바 가상 버신(JVM)에 속성 값을 전달한다.
    % java -Dusetrove=true MyProgram.java
    
  • 아래와 같이 속성값을 설정한 trove.properties 파일을 생성하여 CLASSPATH에 추가한다.
    usetrove=true
    
Collection Factory 소스

Factory 클래스의 핵심은 설정파일에서, 또는 JVM에 직접 전달된 값에서 System 설정을 가져오는 static {}블록 부분이다. 이 작업은 단 한 번 수행되며, usetrove 속성이 true로 설정되었을 경우 클래스 static 변수인 useTrovetrue값을 할당한다.
public class CollectionFactory {
    static boolean useTrove = false;

    /**
     *  Try to find the usetrove property on command line, or  
     *  in a trove.properties in the CLASSPATH
     *  If usetrove is set to true, then set the useTrove 
     *  boolean, else leave it as false
     */
    static {
      try {
        System.getProperties().load( 
      Thread.currentThread().
      getContextClassLoader().
      getResourceAsStream("trove.properties") );

        useTrove = "true".equalsIgnoreCase( 
         ((String) System.getProperty("usetrove") ).trim() ) 
         ? true : false;
          } catch (Exception e) {
            // do nothing.  keep the default: false
          }
    }
이제 useTrove 값을 이용하여 해당되는 타입의 Collection를 생성하여 반환한다.
        /**
         *  Return a hashmap based on the properties
         */
        public static Map getHashMap() {
            if ( useTrove ) return new THashMap();
            else            return new HashMap();
        }

        /**
         *  Return a hashset based on the properties
         */
        public static Set getHashSet() {
            if ( useTrove ) return new THashSet();
            else            return new HashSet();
        }

        /**
         *  Return a linkedlist based on the properties
         */
        public static List getLinkedList() {
            if ( useTrove ) return new TLinkedList();
            else            return new LinkedList();
        }
이제 Trove를 사용할 것인지 JDK클래스를 사용할 것인지 명확하게 구별할 수 있는 방법이 생겼으니, 실제 Collection 클래스가 필요할 때에는 다음과 같이 사용한다.
Map map = CollectionFactory.getHashMap();
 map.put("name", "dion");
 System.out.println("name = " + map.get("name"));
 System.out.println("Class is: " + map.getClass());
위의 코드는 프로그램에서 사용한다면, 각 경우 성공적으로 수행되는지 테스트하면서 두 가지 방법으로 실행시킬 수 있다.
dion@frag [~]> java CollectionFactory
name = dion
Class is: class java.util.HashMap

--------------------------------------------------------------

dion@frag [~]> java -Dusetrove=true CollectionFactory
name = dion
Class is: class gnu.trove.THashMap
Trove 기본 데이터 타입 Collections

지금까지 Trove와 JDK Collection 클래스들이 호환 가능하다는 것을 살펴보았다. 성능이 주요한 쟁점으로 대두된다면, JDK Collection 클래스 대신 Trove의 클래스를 사용하여 성능을 향상시킬 수 있다.

Collection 객체에 기본(primitive) 데이터 타입의 자료를 저장하고자 할 경우에 대해 살펴보자. 기본 데이터 타입이 아닌 각각의 Wrapper 클래스(int->Integer, float->Float)를 사용해야 한다. 만약, 저장되는 키(key) 또는 값(vaule)이 항상 정수인 경우에는 Trove의 특정 Collection 클래스를 사용할 수 있다.
TObjectIntHashMap intmap = new TObjectIntHashMap();
intmap.put("NumInStock", 2);
int numInStock = intmap.get("NumInStock");
JDK map과 Trove의 ObjectInt map의 성능을 비교하면 다음과 같다.
Comparing TObjectIntHashMap to HashMap:
--------------------------------------------------------------
JDK   average (msec): 502
Trove average (msec): 183

Difference: Trove is 2.74 times faster!
HashMap에서 키(key)와 값(value)을 모두 정수(integer)로 사용할 경우(key = int, value = int), JDK와 Trove의 성능은 더욱 차이가 난다.
Comparing TIntIntHashMap to HashMap:
--------------------------------------------------------------
JDK   average (msec): 502
Trove average (msec): 67

Difference: Trove is 7.5 times faster!
Trove의 기본 데이터 타입을 위한 클래스를 사용할 경우, Collection에 저장되는 자료에 특정 제한을 가할 수 있는 부대효과가 있다. 일반 HashMap의 경우, java.lang.Object를 사용하기 때문에, 사용자는 자신이 원하는 객체는 무엇이나 HashMap에 입력할 수 있다. 그러나 TintHashMap를 사용할 경우, 부적당한 자료를 입력하면 컴파일시 경고 메시지를 사용자에게 보낸다.

Note: 향후 자바 버전에서는 특정한 데이터 타입에 한정된 Collection 클래스를 생성할 수 있도록, C++ 탬플릿과 같은 Generics를 제공할 것이다. 이 때에는 Collection에서 객체를 가져와 캐스팅하는 불필요한 작업은 해줄 필요가 없다. 데이터를 저장할 때 값이 체크되기 때문이다.

결론

GNU Trove는 Collection 클래스의 성능을 간단하게 개선시킬 수 있는 해결책이다. 또한 본 기사를 통해 일반 Collection 클래스처럼 Trove의 클래스를 사용할 수 있다는 것도 알게 되었을 것이다. 향상된 성능을 위해서는 기본(primitive) 데이터 타입을 위한 Trove 클래스를 사용할 수 있다.
다이온 알메르(Dion Almaer)는 EJB/J2EE와 B2B 교육 분야의 선도 기업이며 TheServerSide.com의 J2EE의 커뮤니티를 후원하고 있는 미들웨어 회사(www.middleware-company.com)의 책임 기술자이다.
TAG :
댓글 입력
자료실

최근 본 상품0