Let’s pause for a Microsecond
A lot of benchmarks in low latency Java applications involve having to measure a system under a certain load. This requires maintaining a steady throughput of events into the system as opposed to pumping events into a system at full throttle with no control whatsoever.
One of the tasks I often have to do is pause a producer thread for a short period inbetween events. Typically this amount of time will be single digit microseconds.
So how do you pause a Thread for this amount of time? Most Java developers think instantly of Thread.sleep()
. But that’s not going to work because Thread.sleep()
only goes down to milliseconds and that’s an order of magnitude longer than the amount of time required for our pause in microseconds.
I saw an answer on StackOverflow pointing the user to TimeUnit.MICROSECONDS.sleep()
in order to sleep for less than a millisecond. This is clearly incorrect, to quote from the JavaDoc:
Performs a
Thread.sleep
using this time unit. This is a convenience method that converts time arguments into the form required by theThread.sleep
method.
So you’re not going to be able to get better than a 1 millisecond pause , similar to Thread.sleep(1)
. (You can prove this trying the example on the code below).
The reason for this is that this method of pausing, namely putting a thread to sleep and waking it up, is never going to be fast or accurate enough to go lower than a millisecond.
Another question we should be introducing at this point is how accurate is Thread.sleep(1)
anyway? We’ll come back to this in later.
Another option when we want to pause for a microsecond is to use LockSupport.parkNanos(x)
. Using the following code to park for 1 microsecond actually takes ~10us. It’s way better than TimeUnit.sleep() / Thread.sleep() but not really fit for purpose. After 100us it does get into the same ball park with only a 50% variation.
package nanotime; import java.util.Arrays; import java.util.concurrent.TimeUnit; /** * Created by daniel on 28/10/2015. */ public class NanoTimer { public static void main(String[] args) throws InterruptedException { long[] samples = new long[100_000]; int pauseInMillis = 1; for (int i = 0; i < samples.length; i++) { long firstTime = System.nanoTime(); LockSupport.parkNanos(pauseInMicros); long timeForNano = System.nanoTime() - firstTime; samples[i] = timeForNano; } System.out.printf("Time for LockSupport.parkNanos() %.0f\n", Arrays.stream(samples).average().getAsDouble()); } }
The answer to our problems is to use System.nanoTime(). By busy waiting on a call to System.nanoTime
we will be able to pause for a single microsecond. We’ll see the code for this in a second but first let’s understand the accuracy of System.nanosecond()
. Critically, how long does it take to perform the call to System.nanoSecond()
.
Here’s some code that will do exactly this:
package nanotime; public class NanoTimer { public static void main(String[] args) throws InterruptedException { long[] samples = new long[1_000_000]; for (int i = 0; i < samples.length; i++) { long firstTime = System.nanoTime(); long timeForNano = System.nanoTime() - firstTime; samples[i] = timeForNano; } System.out.printf("Time for call to nano %.0f nanseconds", Arrays.stream(samples).average().getAsDouble()); } }
The numbers will vary from one machine to another on my MBP I get ~40 nanoseconds.
That tells us that we should be able to measure to an accuracy of around 40 nanoseconds. Therefore, measuring 1 microsecond (1000 nanoseconds) should easily be possible.
This is the busy waiting approach ‘pausing’ for a microsecond:
package nanotime; import java.util.Arrays; /** * Created by daniel on 28/10/2015. */ public class NanoTimer { public static void main(String[] args) throws InterruptedException { long[] samples = new long[100_000]; int pauseInMicros = 1; for (int i = 0; i < samples.length; i++) { long firstTime = System.nanoTime(); busyWaitMicros(pauseInMicros); long timeForNano = System.nanoTime() - firstTime; samples[i] = timeForNano; } System.out.printf("Time for micro busyWait %.0f\n", Arrays.stream(samples).average().getAsDouble()); } public static void busyWaitMicros(long micros){ long waitUntil = System.nanoTime() + (micros * 1_000); while(waitUntil > System.nanoTime()){ ; } } }
The code waits for a microsecond and then times how long it has waited. On my machine I get 1,115 nanoseconds which is within ~90% accurate.
As you wait longer the accuracy increases, 10 microseconds takes 10,267 which is ~97% accurate and 100 microseconds takes 100,497 nanoseconds which is ~99.5% accurate.
What about Thread.sleep(1)
, how accurate is that?
Here’s the code for that:
package nanotime; import java.util.Arrays; import java.util.concurrent.TimeUnit; /** * Created by daniel on 28/10/2015. */ public class NanoTimer { public static void main(String[] args) throws InterruptedException { long[] samples = new long[100_000]; int pauseInMillis = 1; for (int i = 0; i < samples.length; i++) { long firstTime = System.nanoTime(); Thread.sleep(pauseInMicros); long timeForNano = System.nanoTime() - firstTime; samples[i] = timeForNano; } System.out.printf("Time for micro sleep %.0f\n", Arrays.stream(samples).average().getAsDouble()); } }
The average time in nanoseconds for 1 millisecond sleep is 1,295,509. That only ~75% accurate. It’s probably good enough for nearly everything but if you want an exact millisecond pause you are far better off with a busy wait. Of course you need to remember that busy waiting, by definition keeps your thread busy and will costs you a CPU.
Summary Table
Pause Method | 1us | 10us | 100us | 1000us/1ms | 10,000us/10ms |
---|---|---|---|---|---|
TimeUnit.Sleep() | 1284.6 | 1293.8 | 1295.7 | 1292.7 | 11865.3 |
LockSupport.parkNanos() | 8.1 | 28.4 | 141.8 | 1294.3 | 11834.2 |
BusyWaiting | 1.1 | 10.1 | 100.2 | 1000.2 | 10000.2 |
Conclusions
- If you want to pause for less than a millisecond you need to busy wait
- System.nanoSecond() takes ~40ns
- Thread.sleep(1) is only 75% accurate
- Busy waiting on more than 10us and above is almost 100% accurate
- Busy waiting will tie up a CPU
Reference: | Let’s pause for a Microsecond from our JCG partner Daniel Shaya at the Rational Java blog. |
Thanks for the great article. Btw, just thought I would mention that I noticed that you referred to “System.nanosecond()” a couple times where it looked like you meant “System.nanoTime()”.
Indeed…. Can’t change it here but will update the source http://www.rationaljava.com