시간 측정을 위한 Java API


일반적으로 프로그램의 성능을 평가하는 기준은 무엇일까요? 아마도 특정 입력에 대해 결과를 출력하는데 까지 걸리는 시간을 측정하는 것이 가장 직관적인 방법일 것입니다. 예를 들면, N개 입력 정수를 정렬하는데 A 프로그램은 10초, B 프로그램은 20초가 걸렸다면 상대적으로 A 프로그램이 빠르다고 판단할 수 있습니다. 단, 두 프로그램이 동일한 환경에서 실행되었다는 가정하에 설득력을 갖습니다. 이론적으로는 알고리즘의 복잡도를 분석하여 Ω, Θ, Ο과 같은 표기법으로 성능을 기술합니다.

Java 플렛폼에서 사용할 수 있는 시간측정 도구(API)는 System.currentTimeMillisSystem.nanoTime 메서드가 있습니다. 본 포스트에서는 두 메서드의 차이와 용도를 알아보도록 하겠습니다.

1. "시간"의 용도?
시스템에서 "시간"은 크게 두 가지 용도로 사용됩니다. 첫 번째는 말 그대로 시각(시/분/초)을 표현하기 위한 용도이고, 두 번째는 스케줄링이나 이벤트 처리를 위한 타이머 용도입니다. 오라클의 Davidholmes는 각각을 "passive clock"과 "active clock"으로 표현하기도 했습니다. 이 두 가지 시간을 각각 반환하는 메서드가 바로 currentTimeMillis와 nanoTime입니다.

2. currentTimeMillis 메서드
우선 간단한 코드로 currentTimeMillis 메서드의 결과를 살펴보겠습니다.
public class CurrentTimeMillisTest {
    public static void main(String[] args) {
        long currentTime = System.currentTimeMillis();
        System.out.println("current time value = " + currentTime);
        System.exit(0);
    }
}
current time value = 1335895964247
currentTimeMillis 메서드는 UTC 시간 즉, 1970년 1월 1일 자정부터 현재까지 카운트된 시간을 ms(milliseconds) 단위로 표시합니다. 따라서 출력 결과는 1970년 1월 1일 UTC 시작 시점으로부터 현재까지 총 1335895964247(ms) 만큼의 시간이 흘렀음을 말해주는 것입니다. 이 값을 각각 시/분/초로 계산해 보면 현재 시각(GMT+0)을 얻을 수 있습니다.

그럼 currentTimeMillis 메서드로 특정 코드의 수행 시간을 계산해 봅시다.
public class CurrentTimeMillisTest {
    public static void doHardWork(int n, int m) {
        int result = 1;
        for(int i=0; i<n; i++)
            for(int j=0; j<m; j++)
                for(int k=0; k<n+m; k++)
                    for(int l=0; l<n*m; l++)
                        result *= 2;
    }   
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        doHardWork(100, 100);
        long endTime = System.currentTimeMillis();
        
        long elapsedTime = endTime - startTime;
        System.out.println("Total elapsed time = " + elapsedTime);
        
        System.exit(0);
    }
}
Total elapsed time = 11844
위 코드에서 startTime과 endTime 사이에 어느 정도 계산시간을 요하는 doHardWork 메서드를 실행하고 결과를 확인해 보니 대략 11.844초가 나왔습니다. currentTimeMillis 메서드로 특정 코드의 수행 시간을 측정하는데 아무런 문제가 없어보입니다. 그렇다면 nanoTime 메서드는 왜 제공되는 것일까요?

3. nanoTime 메서드
API 레퍼런스에는 nanoTime 메서드를 다음과 같이 설명하고 있습니다.
Returns the current value of the running Java Virtual Machine's high-resolution time source, in nanoseconds.
This method can only be used to measure elapsed time and is not related to any other notion of system or wall-clock time. The value returned represents nanoseconds since some fixed but arbitrary origin time (perhaps in the future, so values may be negative). The same origin is used by all invocations of this method in an instance of a Java virtual machine; other virtual machine instances are likely to use a different origin. ...생략

레퍼런스에 따르면 nanoTime 메서드는 현재 Java 가상머신의 high-resolution 시간값을 ns(nano sec.) 단위로 반환한다고 되어 있고, 이 메서드는 오직 경과된 시간을 측정하는데 사용해야하고, 시스템이나 시각(시/분/초)과는 아무런 연관성이 없음을 말해주고 있습니다. 그럼 간단한 테스트 코드로 nanoTime 메서드의 반환값과 특정 코드의 수행 시간을 계산해 봅시다.
public class NanoTimeTest {
    public static int doHardWork(int n, int m) {
        int result = 1;
        for(int i=0; i<n; i++)
            for(int j=0; j<m; j++)
                for(int k=0; k<n+m; k++)
                    for(int l=0; l<n*m; l++)
                        result *= 2;
        return result;
    }
    public static void main(String[] args) {
        long startTime = System.nanoTime();
        System.out.println("start nano-time = " + startTime);
        
        doHardWork(100, 100);
        
        long endTime = System.nanoTime();
        System.out.println("end nano-time = " + endTime);
        
        long elapsedTime = endTime - startTime;
        System.out.println("total elapsed time = " + elapsedTime);
        
        System.exit(0);
    }
}
start nano-time = 583086181421940
end nano-time = 583098045092043
total elapsed time = 11863670103

출력 결과를 확인해 보면 대략 11.86초가 경과되었음을 알 수 있습니다.

4. 비교
레퍼런스와 기타 참고문서를 토대로 두 메서드를 비교해 정리하면,

  • currentTimeMillis()
    • 날짜와 관련한 시각 계산을 위해 사용(ms 단위)
    • 시스템 시간을 참조(외부데이터)
    • 일반적인 업데이트 간격은 인터럽트의 타이머(10microsec)에 따름
    • GetSystemTimeAsFileTime 메서드로 구현되어 있으며, 운영체제가 관리하는 시간값을 가져옴
  • nanoTime()
    • 기준 시점에서 경과 시간을 측정하는데 사용(ns 단위)
    • 시스템 시간과 무관
    • QueryPerformanceCounter/QueryPerformanceFrequency API로 구현되어 있음. 

5. 결론
코드의 수행시간 계산은 nanoTime 메서드를, 시각(시/분/초) 계산은 currentTimeMillis 메서드를 사용하자!!

6. 시간 측정을 위한 StopWatch 클래스
StopWatch는 Apache의 java.lang 확장 컴포넌트인 Common Lang 패키지에 포함된 것으로 시간측정을 위한 클래스입니다. [Eclipse의 경우:  Apache 사이트에서 최신 릴리즈를 다운받은 후 외부 라이브러리 형태로 jar파일(common-langX-xx.jar)을 buildPath에 추가하여 사용]
import org.apache.commons.lang3.time.StopWatch;

public class ApacheStopWatchTest {
    public static void main(String[] args) throws InterruptedException {

        StopWatch sw = new StopWatch();

        sw.start();
        doHartWork(100,100);
        sw.stop();
 
        System.out.println(sw.getTime());
        System.exit(0);
    }
}

[참고문헌]
1. Java 1.7 API Documents.
2. "Inside the Hotspot VM: Clocks, Timers and Scheduling Events - Part I - Windows",  davidholmes, 2006.



0 댓글