Timing Code Execution in Java
Published 12 months ago on June 29, 2023

Sometimes you might find yourself wanting or needing to know just how fast (or slow) a particular piece of code is. You might be looking to make as many micro-optimisations as possible, have the most efficient code, or you are just curious about whether method A vs method B is faster.

The majority of, if not all, programming languages provide some form of time utilities and we can use those utilities to measure how long a particular piece of code takes to execute. Of course, this can vary greatly from machine to machine and even from one execution to the next. Because of these variations, the exact numbers provided by measuring code aren’t necessarily useful, but looking at the differences between the execution of different functions that do the same thing, in a different way, is.

For example, in Java, the System.currentTimeMillis() function is useful for measuring the time in milliseconds it takes to execute some code, whereas in C, we could use clock(). The difference between these two is that with Java, we only get the time it took for the timed code to execute, but in C, we are able to calculate the CPU usage time too.

We’ll use Java’s System.nanoTime() in this example because, as you’ll see, the execution time for one of these methods is significantly faster than the other.


The Formula

To time a piece of code, we simply record the time before executing the code, and then record it again after. The difference between those two timestamps is the duration the code took to execute:

startTime = System.currentTimeMillis(); // ... some code to time endTime = System.currentTimeMillis(); duration = endTime - startTime;


Fibonacci

Here’s a method for calculating the nth Fibonacci number, written in Java.
static int fibN(int n) { if (n <= 1) { return n; } else { return fibN(n - 1) + fibN(n - 2); } }

This method is great for demonstrating recursion but it's terribly inefficient for the task at hand. A more efficient method of computing the nth Fibonacci sequence would be to literally count from the first to the nth sequence number. To demonstrate this, I'll be timing the code execution of both the method above, fibN(), and the method below, fibN2().

static int fibN2(int n) { int current = 1; int previous = 0; int next = 1; int temp = 0; for (int i = 1; i < n; i++) { temp = current; next = previous + current; current = next; previous = temp; } return next; }

Each method will be called from within a loop 40 times, the nanoTime() being noted before the loop starts and when the loop ends in order to calculate execution time. Remember, end time minus start time equals duration. So, here's the full class for this process:
class Fib { static long startTime = 0; static long endTime = 0; static long duration = 0; static long duration2 = 0; static final int n = 40; public static void main(String[] args) { System.out.println(""); startTime = System.nanoTime(); int a = 0, b =0; for (int i = 1; i <= n; i++) a = fibN(i); endTime = System.nanoTime(); duration = (endTime - startTime); double ms = ((double)duration/1000000.d); System.out.println("execution took " + duration + " nanoseconds (" + ms + "ms), recursive"); startTime = System.nanoTime(); for (int i = 1; i <= n; i++) b = fibN2(i); endTime = System.nanoTime(); duration2 = (endTime - startTime); ms = ((double) duration2/1000000.d); System.out.println("execution took " + duration2 + " nanoseconds (" + ms + "ms), non-recursive"); System.out.println("Fibonacci number " + n + ": " + a); System.out.println("Fibonacci number " + n + ": " + b); } static int fibN(int n) { if (n <= 1) { return n; } else { return fibN(n - 1) + fibN(n - 2); } } static int fibN2(int n) { int current = 1; int previous = 0; int next = 1; int temp = 0; for (int i = 1; i < n; i++) { temp = current; next = previous + current; current = next; previous = temp; } return next; } }

You'll notice that there is no data output within the timed code, this is because printing information out to the user can drastically increase the time it takes for our code to execute, its a little bit computationally expensive.

The output of executing the above class is:

execution took 723062375 nanoseconds (723.062375ms), recursive execution took 9875 nanoseconds (0.009875ms), non-recursive Fibonacci number 40: 102334155 Fibonacci number 40: 102334155

As you can see, the non-recursive function - fibN2() - is considerably faster than fibN(). Again, we don't care about the exact numbers we get, just the difference. So from this we can see that simply adding all the numbers between 1 and n is about 73000 times quicker (723062375/9875 = 73220~) than calculating fibN recursively.

A final note, nanoseconds are pretty small, a million times smaller than a millisecond. We can change System.nanoTime() to System.currentTimeMillis() instead to time the processes in milliseconds. This will give a more friendly result, but expect 0ms for the output of fibN2(), because it's so much quicker. Because of this, I opted to use nanoseconds and then convert the result to milliseconds and show that as additional output information.