본문 바로가기

Programing/JVM(Java, Kotlin)

[JVM] currentTimeMillis vs nanoTime

Why

회의 시간에 Ummm님이 currentTimeMillis 보다는 성능상 nanoTime을 쓰는 것이 좋다고 하셔서 찾아보게 되었다.

예전에 이상민님의 자바 성능 개발자가 반드시 알아야 할 자바 성능 튜닝 이야기에 비슷한 내용이 있었던 것 같은데 너무 오래 전에 읽어서 기억이 나지 않는다.


선 검색

검색해보면 Baeldung.com 에는 Measure Elapsed Time in Java 라는 제목으로 글이 있다.

System.currentTimeMillis의 경우는 wall-clock time이라서 시스템의 시간을 바꾼다거나 외부적인 요인에 의해 시간 점프가 발생할 수 있다고 한다.

nanoTime의 경우 system or wall-clock time에 대한 언급이 따로 없다고 한다.


용어

Wall clock: 현실 세계와 시간 단위가 똑간은 시계. 시스템의 시간을 동기화 할 때 시간의 값이 튀는 일이 발생할 수 있다.

Monotonic clock: 시간 값이 단조 증가하는 시계. 시스템의 시간이 1초 뒤로 돌아가더라고 상관없이 증기를 한다.


Robert Love's book LINUX System Programming 2nd Edition, specifically addresses your question at the beginning of Chapter 11, pg 363:

The important aspect of a monotonic time source is NOT the current value, but the guarantee that the time source is strictly linearly increasing, and thus useful for calculating the difference in time between two samplings

JDK and JVM

JDK에서는 다음과 같이 native 메서드로 export가 되어 있다.

public static native long currentTimeMillis();
public static native long nanoTime();

jvm.cpp에는 다음과 같이 래핑이 되어 있다.

JVM_LEAF(jlong, JVM_CurrentTimeMillis(JNIEnv *env, jclass ignored))
JVMWrapper("JVM_CurrentTimeMillis");
return os::javaTimeMillis();
JVM_END

JVM_LEAF(jlong, JVM_NanoTime(JNIEnv *env, jclass ignored))
JVMWrapper("JVM_NanoTime");
return os::javaTimeNanos();
JVM_END


OS에 따라 구현하고 있는 시스템 콜이 다르기 때문이다.


os.hpp

class os: AllStatic {
friend class VMStructs;


public:
static jlong javaTimeMillis();
static jlong javaTimeNanos();


구현은 플랫폼 마다 다르게 되어 있다.

윈도우 (os_windows.cpp)

javaTimeMillis의 경우 윈도우 API GetSystemTimeAsFileTime 함수를 호출한다.
javaTimeNanos의 경우 고해상도 카운터 함수를 호출한다. (고해상도 카운터를 쓸 수 없는 시스템인 경우에는 그냥 javaTimeMillis에 100,000을 곱한다.

jlong os::javaTimeMillis() {
if (UseFakeTimers) {
return fake_time++;
} else {
FILETIME wt;
GetSystemTimeAsFileTime(&wt);
return windows_to_java_time(wt);
}
}

jlong os::javaTimeNanos() {
if (!has_performance_count) {
return javaTimeMillis() * NANOSECS_PER_MILLISEC; // the best we can do.
} else {
LARGE_INTEGER current_count;
QueryPerformanceCounter(&current_count);
double current = as_long(current_count);
double freq = performance_frequency;
jlong time = (jlong)((current/freq) * NANOSECS_PER_SEC);
return time;
}
}


globals.hpp

develop(bool, UseFakeTimers, false, \
"Tell whether the VM should use system time or a fake timer")


globalDefinitions.hpp

const jint NANOSECS_PER_MILLISEC = 1000000;

LINUX (os_linux.cpp)

리눅스에서는 gettimeofday 시스템 콜을 호출한다.
javaTimeNanos의 경우 monotonic clock 이 지정되어 있으면 그것을 쓰고, 없는 경우에는 javaTimeMillis 처럼 gettimeofday 시스템 콜을 호출한다.

jlong os::javaTimeMillis() {
timeval time;
int status = gettimeofday(&time, NULL);
assert(status != -1, "linux error");
return jlong(time.tv_sec) * 1000 + jlong(time.tv_usec / 1000);
}


jlong os::javaTimeNanos() {
if (Linux::supports_monotonic_clock()) {
struct timespec tp;
int status = Linux::clock_gettime(CLOCK_MONOTONIC, &tp);
assert(status == 0, "gettime error");
jlong result = jlong(tp.tv_sec) * (1000 * 1000 * 1000) + jlong(tp.tv_nsec);
return result;
} else {
timeval time;
int status = gettimeofday(&time, NULL);
assert(status != -1, "linux error");
jlong usecs = jlong(time.tv_sec) * (1000 * 1000) + jlong(time.tv_usec);
return 1000 * usecs;
}
}

os_linux.hpp

class Linux {
friend class os;
friend class TestReserveMemorySpecial;

public:
static inline bool supports_monotonic_clock() {
return _clock_gettime != NULL;
}

BSD (os_bsd.cpp)

jlong os::javaTimeMillis() {
timeval time;
int status = gettimeofday(&time, NULL);
assert(status != -1, "bsd error");
return jlong(time.tv_sec) * 1000 + jlong(time.tv_usec / 1000);
}


jlong os::javaTimeNanos() {
if (Bsd::supports_monotonic_clock()) {
struct timespec tp;
int status = Bsd::clock_gettime(CLOCK_MONOTONIC, &tp);
assert(status == 0, "gettime error");
jlong result = jlong(tp.tv_sec) * (1000 * 1000 * 1000) + jlong(tp.tv_nsec);
return result;
} else {
timeval time;
int status = gettimeofday(&time, NULL);
assert(status != -1, "bsd error");
jlong usecs = jlong(time.tv_sec) * (1000 * 1000) + jlong(time.tv_usec);
return 1000 * usecs;
}
}

Solaris (os_solaris.cpp)

jlong os::javaTimeMillis() {
timeval t;
if (gettimeofday( &t, NULL) == -1)
fatal(err_msg("os::javaTimeMillis: gettimeofday (%s)", strerror(errno)));
return jlong(t.tv_sec) * 1000 + jlong(t.tv_usec) / 1000;
}

jlong os::javaTimeNanos() {
return (jlong)getTimeNanos();
}



'Programing > JVM(Java, Kotlin)' 카테고리의 다른 글

[Java] Class 생성 실험  (0) 2019.04.01
[Java] switch와 String 그리고 바이트코드  (0) 2019.03.22
[Java] break label 문  (0) 2019.03.09
[Java] Generic in depth  (0) 2019.01.31
Spock Framework 프로젝트에 추가하기  (0) 2018.10.02