Java : Use System.nanoTime to measure elapsed time

What are you using to measure elapsed time? Is it System.currentTimeMillis? This article recommends using System.nanoTime to measure elapsed time, and explains why.

Note:


Introduction

To examine the performance of your program, you may want to measure processing time at key points. For example, the following main method, how would you measure the processing time of func1 and func2 methods?

void main() {
    func1();
    func2();
}

One way is to measure the elapsed time before and after calling the func1 and func2 methods. You may think of System.currentTimeMillis as the API to use for that.

System.currentTimeMillis returns the current time in milliseconds.

void main() {
    final var time1 = System.currentTimeMillis();
    func1();
    final var time2 = System.currentTimeMillis();
    func2();
    final var time3 = System.currentTimeMillis();

    System.out.println("func1 time : " + (time2 - time1));
    System.out.println("func2 time : " + (time3 - time2));
}

Result is :

func1 time : 24
func2 time : 201

The func1 method had an elapsed time of 24 milliseconds and the func2 method had an elapsed time of 201 milliseconds. You were able to measure elapsed time with System.currentTimeMillis.

However, there is a better API for measuring elapsed time than currentTimeMillis. That is System.nanoTime.

Difference between currentTimeMillis and nanoTime

The System class has two methods related to time. These are the currentTimeMillis and nanoTime methods. It's hard to tell from the name, but the two methods have very different uses.

Let's take a quick look at the difference between the two methods.


public static long currentTimeMillis()
Returns the current time in milliseconds.

System.currentTimeMillis returns the current time in milliseconds.

final var time = System.currentTimeMillis();
System.out.println(time); // 1679209828914

// Display as date and time.
final var instant = Instant.ofEpochMilli(time);
System.out.println(instant); // 2023-03-19T07:37:44.283Z

There are other alternative APIs for getting the current time, such as Instant.now and LocalDateTime.now.


public static long 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.

System.nanoTime can only be used to measure elapsed time. It's also stated in the API specification.

The values returned by the nanoTime method are not related to system or clock time. Therefore, the values can't be converted to a datetime.

final var startTime = System.nanoTime();
System.out.println(startTime); // 18847675664600

TimeUnit.SECONDS.sleep(1);

final var endTime = System.nanoTime();
System.out.println(endTime); // 18848688620500

final var elapsedTime = endTime - startTime;
System.out.println(elapsedTime); // 1012955900

// Convert to seconds.
System.out.println(elapsedTime / 1000000000.0 + " sec."); // 1.0129559 sec.

In the example above, the nanoTime method is used to measure elapsed time before and after 1 second sleep. The nanoTime method returns value in nanoseconds. Finally the value is divided by 1000000000 to convert to seconds.

Advantages of nanoTime

High-resolution

public static long nanoTime()
...
This method provides nanosecond precision, but not necessarily nanosecond resolution (that is, how frequently the value changes)

System.nanoTime returns the current value in nanoseconds whereas System.currentTimeMillis returns the current time in milliseconds. The nanoTime method is 1000000 times more precise than the currentTimeMillis method. (nanoseconds → microseconds → milliseconds)

However, as documented, not all environments can be measured with nanosecond precision (resolution). In my environment (Windows 10), the last two digits were always 0. So the resolution seems to be 100 nanoseconds.

final var startTime = System.nanoTime();
System.out.println(startTime); // 18847675664600

TimeUnit.SECONDS.sleep(1);

final var endTime = System.nanoTime();
System.out.println(endTime); // 18848688620500

final var elapsedTime = endTime - startTime;
System.out.println(elapsedTime); // 1012955900

// Convert to seconds.
System.out.println(elapsedTime / 1000000000.0 + " sec."); // 1.0129559 sec.

Elapsed time can't be negative

public static long nanoTime()
...
This method can only be used to measure elapsed time and is not related to any other notion of system or wall-clock time.

System.currentTimeMillis returns the current time. It depends on the machine's clock.

System.nanoTime does not depend on the machine's clock. Changing the machine's time has no effect.

Let's check it with a code example. The code example uses currentTimeMillis and nanoTime to measure elapsed time before and after sleeping for 10 seconds.

final var startTime1 = System.currentTimeMillis();
final var startTime2 = System.nanoTime();

TimeUnit.SECONDS.sleep(10);

final var endTime1 = System.currentTimeMillis();
final var endTime2 = System.nanoTime();

final var elapsedTime1 = endTime1 - startTime1;
final var elapsedTime2 = endTime2 - startTime2;

// Convert to seconds.
System.out.println(elapsedTime1 / 1000.0 + " sec.");
System.out.println(elapsedTime2 / 1000000000.0 + " sec.");

First, the code example runs without changing the machine's clock.

10.004 sec.
10.0029617 sec.

The elapsed time of currentTimeMillis and nanoTime are both about 10 seconds.

Next, during the 10 seconds sleep, the machine's clock is adjusted back 30 seconds. To change the clock run following command in Windows PowerShell:

> Set-Date -Adjust -00:00:30

Result is :

-19.251 sec.
10.0021223 sec.

The currentTimeMillis method measured about -20 seconds. The elapsed time is negative. It is not good.

The nanoTime method measured about 10 seconds, which is as expected. Changing the machine's clock or not doesn't change the result.

This time, I manually changed the machine's clock. However, machines that use NTP (Network Time Protocol) to automatically synchronize their clocks may have their clocks adjust back without manual intervention.

If you use the currentTimeMillis method, you should also take into account the negative elapsed time. With the nanoTime method you don't have to worry about that.

Be careful of numerical overflow

public static long nanoTime()
...
if (System.nanoTime() >= startTime + timeoutNanos) ...
because of the possibility of numerical overflow.

As mentioned in the API document, be careful of numerical overflow when you use nanoTime values in calculations. Basically, you subtract the start time from the end time. If you use addition, you may exceed the maximum value of long.

Conclusion

It would be better to use System.nanoTime instead of System.currentTimeMillis to measure elapsed time. Because nanoTime is high-resolution and the elapsed time can't be negative.


Related posts

To top of page