Reputation: 19
I am doing some work with System.nanoTime()
.
I found online that if two events happen, A
and B
, such that A
precedes B
, then System.nanoTime()
at A
is less than that of at B
. I know that the actual value of System.nanoTime()
is always irrelevant, and we only ever subtract two System.nanoTime()
values to obtain elapsed time. However, the first observation implies that System.nanoTime()
is always increasing. Since we can always store it in a long
in Java (which is a finitely sized data type), at one point System.nanoTime()
must reset and equal 0
. When does this happen, and what happens if you try measuring the elapsed time starting from before the reset and ending after the reset?
I've coded some snippets to try and find when System.nanoTime()
= 0
, but I got nothing. It surely can't always be increasing, right?
Upvotes: 0
Views: 232
Reputation: 43401
Your question seems to imply that there are "borders" on the number line — specifically, 0 — that affect the math. That isn't the case, with one small caveat (which I'll describe at the bottom of this answer, and the JavaDocs also mention).
Let's think about two regions of the number line:
Long.MAX_VALUE
)Remember that just about the only thing you can do with nanoTime()
is compute delta = end - start
. So, let's look at what happens in these various regions.
Most of the time, the math "just works" without any trickery. For example, if we start at t=1000 and end at t=1005, then we subtract them out and get 5. Note that this also works in the negative region (-1000 - (-955) = 5), and also when the start is negative and end is positive.
That's all easy enough.
Things get a bit interesting when numbers get close to Long.MAX_VALUE
. Let's say start = Long.MAX_VALUE - 1
:
Long.MAX_VALUE
Long.MIN_VALUE
Long.MIN_VALUE + 1
Long.MIN_VALUE + 2
Long.MIN_VALUE + 3
(That wrap to MIN_VALUE
is the "reset" you mentioned in your question; note that it wraps to MIN_VALUE
, not to 0.)
(Also, I'm not counting them out to be patronizing. It's easy for me to get confused, and for small deltas like 5, it's easier for me to just count it out than write out the formulas!)
So:
delta = end - start
= (Long.MIN_VALUE + 3) - (Long.MAX_VALUE - 1)
= Long.MIN_VALUE + 3 - Long.MAX_VALUE + 1
= Long.MIN_VALUE - Long.MAX_VALUE + 4
= -2^63 - (2^63 - 1) + 4
= -2^63 - 2^63 + 1 + 4
= -2^64 + 5
This number is too small to represent in a 64-bit integer, but the way twos complement and wrapping works, it wraps to 5. Basically, the twos complement of -264 is a 1
and then 64 0
s. This gets truncated to the lowest 64 bits, which are all 0
; so -264 wraps to 0, and you're left with just the +5
. That's not exactly what's going on in the computer, but the math works out the same.
You can test this out yourself to see that it works:
long start = Long.MAX_VALUE - 1;
for (int i = 0; i < 20; i++) {
long end = start + i;
long delta = end - start;
System.out.printf("expect %d, saw %d (end=%d)%n", i, end - start, end);
}
(live example: https://ideone.com/IFYBhW)
Unfortunately, that twos complement wrapping will only get you so far. As time ticks and that "+5" grows, eventually it toggles the most significant digit of the long, and the result becomes 10000....0
, which is Long.MIN_VALUE
. In other words, after a long enough time, end - start
will actually give you a negative answer, which is obviously incorrect. (An equivalent and potentially simpler way to look at this: 263 is bigger than what a long can represent, so when the delta reaches this, it wraps around to MIN_VALUE
.)
Luckily for you, this takes 263 ns, which is a very long time: about 292.28 years. This is the origin of possibly my favorite piece of documentation in any piece of software:
Differences in successive calls that span greater than approximately 292 years (263 nanoseconds) will not accurately compute elapsed time due to numerical overflow. (source)
I can just imagine a PM telling an engineer at Sun, "you have to document all edge cases", and the engineer telling the PM "263 nanoseconds is huge, it's not worth documenting", and them going back and forth until the engineer lets out a sigh and fires up a terminal:
$ units
You have: 2**63 nanosecond
You want: year
* 292.27727
/ 0.0034214088
"hrmph!"
Upvotes: 2
Reputation: 340040
Your questions are addressed in the documentation. See the Javadoc for System.nanoTime
. To quote:
The value returned represents nanoseconds since some fixed but arbitrary origin time (perhaps in the future, so values may be negative).
The origin time is arbitrary. That means you cannot ask when the value will be zero. The documentation is telling you not to expect the origin time.
Furthermore, that quote warns you that the values may be negative or positive. No promises are made either way.
The only promise made to you is that the value will always be increasing, incrementing to a greater number. The increases continue until reaching the limit of a long
. That 64-bit value is Long.MAX_VALUE
. At that point, the values wrap around back to Long.MIN_VALUE.
So, to be safe, you shield compare your start and end times. If your start is larger than your end, then you know a wraparound occurred.
In practice, I have seen only positive numbers in the various implementations of Java I have used on various host OSes. And I have seen the origin time set to zero going back to when the machine booted or the JVM launched (I don’t recall which). I only see relatively small positive numbers. That means over a century of time before a wraparound.
If my observations hold, then for casual testing and logging, you need not worry about wraparound. But for anything critical, you do need to be prepared for wraparound because my observed behavior is not promised.
You asked:
I've coded some snippets to try and find when System.nanoTime() = 0, but I got nothing. It surely can't always be increasing, right?
Upon the value reaching Long.MAX_VALUE
, the count can go no higher. That is the limit of a long
, a 64-bit signed integer.
I presume that upon reaching that limit the nanoTime
implementation would proceed from Long.MAX_VALUE
to Long.MIN_VALUE
, and keep on counting upward. But the Javadoc does not explicitly say that. A peek at the OpenJDK source code might be illuminating.
Regarding the ranges of long
number minimum and maximum, while the range of 64-bit numbers can represent about 584 years (292 + 292), a count of elapsed nanoseconds is limited to 292 before enchanting integer overflow.
This is curious, but unimportant. None of use will be tracking elapsed time over 292 years.
And handling the wraparound when the nanoTime
value reaches Long.MAX_VALUE
and presumably continues at Long.MIN_VALUE
is a different issue altogether. (However, some smart people commenting on this page disagree. But I believe them to be misreading the text.)
The Javadoc notes this fact:
Differences in successive calls that span greater than approximately 292 years (263 nanoseconds) will not correctly compute elapsed time due to numerical overflow.
Duration durationPositive = Duration.ofNanos ( Long.MAX_VALUE );
long daysPositive = durationPositive.toDays();
double yearsPositive = daysPositive/365.25;
System.out.println ( "durationPositive = " + durationPositive );
System.out.println ( "yearsPositive = " + yearsPositive );
Duration durationNegative = Duration.ofNanos (Long.MIN_VALUE );
long daysNegative = durationNegative.toDays();
double yearsNegative = daysNegative/365.25;
System.out.println ( "durationNegative = " + durationNegative );
System.out.println ( "yearsNegative = " + yearsNegative );
durationPositive = PT2562047H47M16.854775807S
yearsPositive = 292.26830937713896
durationNegative = PT-2562047H-47M-16.854775808S
yearsNegative = -292.26830937713896
Upvotes: 2