Tr : Re : what is the most precise time I can get in JDK?
Jeff Hain
jeffhain at rocketmail.com
Fri Nov 18 20:13:09 UTC 2011
New test with both attachments, and me as main destinatary (it did work when I sent it to myself _only_).
In case it still fails, I copy-paste the whole code here (3 classes) (I hope it's not too large :).
package thintime;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
/**
* This class makes up in some ways to the absence of a
* System.currentTimeNanos() method. If such a method
* would get available, most of this class would become
* pointless (unless the new method would be slower).
*
* Methods of this class are thread-safe and non-blocking.
*
* The advantage of methods of this class over
* System.curentTimeMillis(), resides not in accuracy,
* which is about the same, but in that from a call to the next,
* the returned value will change more often.
* That's why it's called "ThinTime" and not "AccurateTime".
*
* The average current time computed is biased compared
* to the time returned by System.currentTimeMillis(),
* being usually a bit ahead of it, by an amount
* depending on tolerance configuration.
*
* An offset can be defined, in milliseconds, relatively
* to the value returned by System.currentTimeMillis().
*
* Unless system time backward jumps, returned time
* is never smaller than a previously returned time
* (for a same method at least, since they do not
* all have the same precision).
* As a consequence, returned time gets stalled
* once Long.MAX_VALUE nanoseconds time is reached,
* unless actual system time does a backward jump.
*/
public class ThinTime {
/*
* Note on using a time in nanoseconds, with a custom offset from system time.
*
* One year = 3600*24*365 seconds
* = 31_536_000 seconds
* = 31_536_000_000 milliseconds
* = 31_536_000_000_000_000 nanoseconds
* and
* (2^63)/31_536_000_000_000_000 = 292,47120867753601623541349568747
* so with time in nanoseconds, starting from zero, a long allows to go up to 292 years,
* and starting from Long.MIN_VALUE, up to 584 years, which should be enough for most needs.
*
* Meanwhile, this class allows to define an offset relatively to system time, in milliseconds.
* It can therefore be used over a 584 years period of time located anywhere in the
* next 292 millions years, which should also be enough for most needs.
*/
/*
* System.currentTimeMillis() behavior illustration:
* time
* ^
* | time = actual current time
* | /
* | /
* 9| +-- time = System.currentTimeMillis()
* 8| /|
* 7| +-+ (case 1: reaches actual current time)
* 6| /| |
* 5| / +-+ (case 2: does not reach actual current time)
* 4| / |
* 3| +---+
* 2| /|
* 1|+-+
* 0+-----------------------------------> actual current time
* 0123456789
*
* As shown, we consider the actual current time to always be
* superior or equal to the value returned by System.currentTimeMillis().
* This might not be the case, but at worse it just introduces a
* small and constant bias in our measurement of time, which is no biggie
* since we just pretend to be "thin", and about as accurate (not more)
* than System.currentTimeMillis().
*
* Let minSCTMJump be the min time jump when System.currentTimeMillis()
* returned value changes.
* minSCTMJump is estimated at first with a default value, then dynamically.
*/
//--------------------------------------------------------------------------
// PUBLIC CLASSES
//--------------------------------------------------------------------------
public static class TimeRef {
/**
* Reference value returned by (System.currentTimeMillis() - systemTimeZeroMS).
*/
private final long refSCTM;
/**
* Reference value returned by System.nanoTime().
*/
private final long refSNT;
public TimeRef(
long refSCTM,
long refSNT) {
this.refSCTM = refSCTM;
this.refSNT = refSNT;
}
/**
* @return Reference (System.currentTimeMillis() - systemTimeZeroMS).
*/
public long getRefSCTM() {
return this.refSCTM;
}
/**
* @return Reference System.nanoTime().
*/
public long getRefSNT() {
return this.refSNT;
}
}
//--------------------------------------------------------------------------
// MEMBERS
//--------------------------------------------------------------------------
/**
* Max number of times sctm and snt are retrieved again,
* in case recently retrieved ones were not suited for
* time reference update.
*/
private static final int MAX_NBR_OF_TIME_REF_UPDATE_RE_ATTEMPTS = 3;
private static final String DEFAULT_FUTURE_TOLERANCE_RATIO_PROPERTY = "odk.thintime.ftr";
private static final String DEFAULT_INITIAL_MIN_SCTM_JUMP_MS_PROPERTY = "odk.thintime.imsctmj";
private static final String DEFAULT_SYSTEM_TIME_ZERO_MS_PROPERTY = "odk.thintime.stzero";
private static final double DEFAULT_FUTURE_TOLERANCE_RATIO;
static {
String tmp = System.getProperty(DEFAULT_FUTURE_TOLERANCE_RATIO_PROPERTY);
if (tmp != null) {
DEFAULT_FUTURE_TOLERANCE_RATIO = Double.parseDouble(tmp);
} else {
DEFAULT_FUTURE_TOLERANCE_RATIO = 2.0;
}
}
private static final long DEFAULT_INITIAL_MIN_SCTM_JUMP_MS;
static {
String tmp = System.getProperty(DEFAULT_INITIAL_MIN_SCTM_JUMP_MS_PROPERTY);
if (tmp != null) {
DEFAULT_INITIAL_MIN_SCTM_JUMP_MS = Long.parseLong(tmp);
} else {
DEFAULT_INITIAL_MIN_SCTM_JUMP_MS = 100L;
}
}
private static final long DEFAULT_SYSTEM_TIME_ZERO_MS;
static {
String tmp = System.getProperty(DEFAULT_SYSTEM_TIME_ZERO_MS_PROPERTY);
if (tmp != null) {
DEFAULT_SYSTEM_TIME_ZERO_MS = Long.parseLong(tmp);
} else {
DEFAULT_SYSTEM_TIME_ZERO_MS = 0L;
}
}
private static final ThinTime DEFAULT_INSTANCE = new ThinTime();
/**
* This ratio (>= 0.0) defines the maximum time delta into the future
* (the maximum time delta into the past is 0),
* from the time returned by System.currentTimeMillis(), to
* the "thin current time" (the time returned by methods of this class).
*
* This delta is computed as "ratio * minSCTMJump", where minSCTMJump
* is the min time jump when System.currentTimeMillis() value changes,
* i.e. System.currentTimeMillis()'s best precision (and hopefully accuracy).
*
* You want this ratio to be >= 1.0, or 0.0, considering
* what happens for values < 1.0:
* - If this ratio is 0.0, thin current time will always be equal
* to the time returned by System.currentTimeMillis().
* - If this ratio is in ]0.0,1.0[, as thin current time will not be
* allowed to differ from System.currentTimeMillis() for more than
* System.currentTimeMillis()'s best accuracy, there will be a lot of
* recomputations of thin current time line (time references),
* which will also jump a lot relatively to the actual current time:
* this might be CPU heavy, and leads to a less thin/continuous
* time line.
*
* For ratios >= 1.0:
* - Since System.currentTimeMillis() can have jumps larger than its
* min jumps, the bad effects described for ratios in ]0.0,1.0[ also
* occur for ratios >= 1.0 but too small regarding System.currentTimeMillis()
* accuracy variations.
* - Ratios too large regarding System.currentTimeMillis() will by definition
* allow for a "thin current time line" uselessly ahead of the actual current time,
* i.e. for a (still) thin/continuous but less accurate time.
*
* The best ratio is the smallest one that does not lead to (a lot of) jumps
* (relatively to the actual current time).
* Ideally, when System.nanoTime() does not jump, with a good ratio,
* the "thin current time line" will converge to being just-above actual
* current time line.
*/
private final double futureToleranceRatio;
private final long systemTimeZeroMS;
private final AtomicReference<TimeRef> timeRef = new AtomicReference<TimeRef>();
/**
* NB: Not having it and future tolerance in a same volatile-referenced object
* (to reduce number of accesses to a volatile object),
* for minSCTMJumpMS is only accessed when sctm changes, i.e. not so often.
*
* In milliseconds.
* Can only get smaller.
*/
private final AtomicLong minSCTMJumpMS = new AtomicLong(Long.MAX_VALUE);
/**
* In nanoseconds.
* Can only get smaller.
*/
private final AtomicLong futureToleranceNS = new AtomicLong(Long.MAX_VALUE);
/**
* Last retrieved (System.currentTimeMillis() - systemTimeZeroMS).
*/
private final AtomicLong lastGetSCTM = new AtomicLong(Long.MIN_VALUE);
/**
* Last returned current time, in nanoseconds.
*/
private final AtomicLong lastReturnedCTN = new AtomicLong(Long.MIN_VALUE);
//--------------------------------------------------------------------------
// PUBLIC METHODS
//--------------------------------------------------------------------------
/**
* Unless default parameters are redefined by properties,
* creates an instance with a future tolerance ratio of 2,
* a default initial min sctm jump of 100ms,
* and a zero offset from system time.
* You most likely would like to use the default instance,
* which is of this kind, instead of another one (the most
* an instance is used, the most accurate it is), unless
* for some reasons you don't want, for example, too many
* threads to use your instance.
*/
public ThinTime() {
this(
DEFAULT_FUTURE_TOLERANCE_RATIO,
DEFAULT_INITIAL_MIN_SCTM_JUMP_MS,
DEFAULT_SYSTEM_TIME_ZERO_MS);
}
/**
* @param futureToleranceRatio Tolerance ratio for the returned time to be ahead
* of the time returned by System.currentTimeMillis(). This value is multiplied
* with the min time jump (precision) of System.currentTimeMillis().
* Ex. : if system time precision is 10ms, and this ratio is 2, returned time
* will be allowed to be up to 20ms ahead of system time.
* This value must be 0.0, or a value >= 1.0.
* @param initialMinSCTMJumpMS Initial value (in milliseconds) for min date jump between
* two consecutive calls to System.currentTimeMillis() returning different values.
* This initial value must be higher or equal to the actual min jump,
* or we would not end up with a large enough future tolerance and
* would be updating reference very frequently; but it must not be too large,
* or first returned times could be about as inaccurate than this value.
* @param systemTimeZeroMS Offset from system time: returned time ~= system time - offset.
*/
public ThinTime(
double futureToleranceRatio,
long initialMinSCTMJumpMS,
long systemTimeZeroMS) {
if (!((futureToleranceRatio >= 1.0) || (futureToleranceRatio == 0.0))) { // takes care of NaN
throw new IllegalArgumentException("future tolerance ratio ["+futureToleranceRatio+"] must be >= 1.0, or equal to zero");
}
if (initialMinSCTMJumpMS <= 0) {
throw new IllegalArgumentException("initial min sctm jump ["+initialMinSCTMJumpMS+"] must be > 0");
}
this.futureToleranceRatio = futureToleranceRatio;
this.systemTimeZeroMS = systemTimeZeroMS;
setMinSCTMJump(initialMinSCTMJumpMS);
long sctm1 = getRawSystemCurrentTimeMillisFromTimeZero();
long snt = getSNT();
long sctm2 = getRawSystemCurrentTimeMillisFromTimeZero();
this.updateTimeRef(sctm1, snt, sctm2);
}
/**
* @return The default instance.
*/
public static ThinTime getDefaultInstance() {
return DEFAULT_INSTANCE;
}
/**
* In case anyone would like to make use of it.
* @return The current time reference.
*/
public TimeRef getTimeRef() {
return this.timeRef.get();
}
/**
* @return The future tolerance ratio.
*/
public double getFutureToleranceRatio() {
return this.futureToleranceRatio;
}
/**
* @return The System.currentTimeMillis() for which this instance, should return time 0.
*/
public long getSystemTimeZeroMS() {
return this.systemTimeZeroMS;
}
/**
* @return Min recorded time jump, in milliseconds, between two consecutive calls of
* System.currentTimeMillis(). This value is > 0 (does not count backward time jumps).
*/
public long getMinSCTMJumpMS() {
return this.minSCTMJumpMS.get();
}
/**
* @return The tolerance, in nanoseconds, for returned time to be ahead of
* (System.currentTimeMillis() - systemTimeZeroMS).
*/
public long getFutureToleranceNS() {
return this.futureToleranceNS.get();
}
/**
* @return Last retrieved (System.currentTimeMillis() - systemTimeZeroMS).
*/
public long getLastGetSCTM() {
return this.lastGetSCTM.get();
}
/**
* @return Last returned current time, in nanoseconds.
*/
public long getLastReturnedCTN() {
return this.lastReturnedCTN.get();
}
/*
* static time methods (use a default instance of ThinTime)
*/
public static double currentTimeSeconds() {
return DEFAULT_INSTANCE.currentTimeSeconds_();
}
public static long currentTimeMillis() {
return DEFAULT_INSTANCE.currentTimeMillis_();
}
public static long currentTimeMicros() {
return DEFAULT_INSTANCE.currentTimeMicros_();
}
public static long currentTimeNanos() {
return DEFAULT_INSTANCE.currentTimeNanos_();
}
/*
* instance time methods
*/
public double currentTimeSeconds_() {
return currentTimeNanos_() * 1e-9;
}
public long currentTimeMillis_() {
return currentTimeNanos_()/1000000L;
}
public long currentTimeMicros_() {
return currentTimeNanos_()/1000L;
}
public long currentTimeNanos_() {
long ctn;
if (this.futureToleranceRatio == 0.0) {
// Fast handling for this special case:
// no need to use all our machinery.
ctn = getRawSystemCurrentTimeMillisFromTimeZero() * 1000000L;
} else {
/*
* Algorithm in short (prefixing volatile variables with an underscore):
* 1) get sctm1 (and do A = {
* update _lastGetSCTM if needed,
* update _lastReturnedCTN if system time did a backward jump,
* update _minSCTMJump and _futureToleranceNS if needed})
* 2) get snt
* 3) get sctm2 (and do A)
* 4) retrieve latest _timeReference
* 5) compute ctn (using retrieved _timeReference, and snt)
* 6) if computed ctn is outside tolerance range (computed from sctm1, sctm2, and _futureToleranceNS),
* compute and set new _timeReference, and set ctn with reference sctm (*1000000 for ctn is in nanoseconds).
* 7) if ctn is inferior to _lastReturnedCTN, set it with _lastReturnedCTN value,
* else update _lastReturnedCTN value with ctn.
*
* Here is the update policy (i.e. using CAS and stuffs or not) for the volatile variables:
* - _timeReference:
* NOT a problem if set concurrently in anarchy by multiple threads,
* since it always goes towards a "good" reference.
* ===> Not using CAS for it.
* - _minSCTMJump and _futureToleranceNS:
* NOT a problem if set concurrently in anarchy by multiple threads,
* as long as they are set together (they are linked), since they can
* only get smaller, and finally don't change anymore.
* Though, it's nice to have them get small ASAP, and doesn't hurt to take care
* of it since they are only set a finite (and small) number of time.
* Also if using "setMinAndGet" to set each of them, no need for synchronization
* or lock to set them together.
* ===> Using CAS and stuffs for these.
* - _lastGetSCTM and _lastReturnedCTN:
* IMPORTANT not to have them set concurrently in anarchy by multiple threads,
* since we want to keep track of System.currentTimeMillis() backward jumps,
* and don't want to return a time inferior to previously returned time
* (unless system time backward jumps).
* ===> Using CAS and stuffs for these.
*/
/*
* Since System.nanoTime() can have jumps, we make sure
* we won't return an absurd value, by using a security
* window computed from times returned by System.currentTimeMillis().
*/
long sctm1 = getSystemCurrentTimeMillisFromTimeZero();
/*
* If our thread has a rest here, for a duration d,
* we will be tolerant to System.nanoTime() jumps in
* past of -d, when testing (ctn < sctn1).
* This is no biggie, since the computed current time will
* remain superior to the time this method was called at.
*/
long snt = getSNT();
/*
* If our thread has a rest here, for a duration d,
* we will be tolerant to System.nanoTime() jumps in
* future of toleranceToSCTN + d, when testing (ctn > sctn2 + futureTolerance).
* This is no biggie, since the computed current time will
* remain inferior to the time this method returns + toleranceToSCTN.
*/
long sctm2 = getSystemCurrentTimeMillisFromTimeZero();
long sctn1 = sctm1 * 1000000L;
long sctn2 = sctm2 * 1000000L;
TimeRef timeRef = this.timeRef.get();
ctn = timeRef.refSCTM * 1000000L + (snt - timeRef.refSNT);
/*
* If system time jumped backward between calls to System.currentTimeMillis(),
* no biggie, we will just most likely update references according to new
* system time.
*/
if ((ctn < sctn1) || (ctn > sctn2 + this.futureToleranceNS.get())) {
// ctn too low or too high: need to update reference
// and recompute ctn.
/*
* ctn too low:
*
* Three non-exclusive possibilities:
* - Our line was below the line "time=actual current time".
* System.nanoTime() might also have jumped some into the
* future, but not enough to make our line reach the line
* "time=actual current time",
* - System.nanoTime() did jump in the past (or went past
* Long.MAX_VALUE),
* - futureToleranceRatio is small.
*/
/*
* ctn too high:
*
* We authorize our computed current time to be a bit ahead
* system current time millis, since it changes more often,
* hence the tolerance, but too much is too much.
*
* Two non-exclusive possibilities:
* - System.nanoTime() did jump in the future,
* - futureToleranceRatio is small.
*/
timeRef = updateTimeRef(sctm1,snt,sctm2);
ctn = timeRef.refSCTM * 1000000L;
}
// Making sure we don't return a ctn inferior to a previously returned one,
// (unless system time went backward, which is handled elsewhere),
// and updating lastReturnedCTN if needed.
ctn = setMaxAndGet(this.lastReturnedCTN, ctn);
}
return ctn;
}
//--------------------------------------------------------------------------
// PROTECTED METHODS
//--------------------------------------------------------------------------
/*
* Overridable for use of custom time sources (for tests or else).
*/
protected long getSCTM() {
return System.currentTimeMillis();
}
protected long getSNT() {
return System.nanoTime();
}
//--------------------------------------------------------------------------
// PRIVATE METHODS
//--------------------------------------------------------------------------
/**
* This method updates minSCTMJump and related values as needed,
* and keeps track of system time backward jumps.
*/
private long getSystemCurrentTimeMillisFromTimeZero() {
long sctm;
long previousSCTM;
// Most of the time, this loop should only
// do one round, and it could do two if done near
// the time the value returned by System.currentTimeMillis()
// changes.
do {
previousSCTM = this.lastGetSCTM.get();
sctm = getRawSystemCurrentTimeMillisFromTimeZero();
// CAS fails when last get SCTM was changed
// by another thread since we read it last.
} while (!this.lastGetSCTM.compareAndSet(previousSCTM, sctm));
// Here, we are sure sctm has been retrieved while
// lastGetSCTM value was previousSCTM.
if (sctm != previousSCTM) {
// "rare" case: sctm changed.
if (sctm < previousSCTM) {
// System time went backward: need to reset lastReturnedCTN,
// or our clock will be stalled until system time reaches
// lastGetSCTM again.
this.lastReturnedCTN.set(Long.MIN_VALUE);
} else {
long dtMS = sctm - previousSCTM;
if (dtMS < 0) {
// Jump was so large we had overflow:
// obviously, not a candidate value
// to update min possible jump!
} else {
// If time jump was smaller than smallest
// registered, we update it.
if (dtMS < this.minSCTMJumpMS.get()) {
setMinSCTMJump(dtMS);
}
}
}
}
return sctm;
}
/**
* Method to be used with caution, since it does not update
* anything of our multiple time related variables...
*/
private long getRawSystemCurrentTimeMillisFromTimeZero() {
return getSCTM() - this.systemTimeZeroMS;
}
/**
* {sctm1, snt, sctm2} : recent values of System.currentTimeMillis(),
* System.nanoTime(), and System.currentTimeMillis(), retrieved in
* that order.
* @return The new time reference.
*/
private TimeRef updateTimeRef(
long sctm1,
long snt,
long sctm2) {
/*
* Using counter to prevent "infinite" loop,
* in case System.currentTimeMillis() is really thin,
* and our thread really lazy...
* At worse, currentTimeNanos() will be less "thin"
* and closer to System.currentTimeMillis().
*/
int counter = MAX_NBR_OF_TIME_REF_UPDATE_RE_ATTEMPTS;
while ((sctm1 != sctm2) && (counter-- != 0)) {
sctm1 = sctm2;
snt = getSNT();
sctm2 = getSystemCurrentTimeMillisFromTimeZero();
}
/*
* In case sctm1 != sctm2, and even if sctm1 > sctm2 (backward
* system time jump), we can use either value for SCTM reference:
* our "thin current time line", defined by our references,
* might be well below (with sctm1) or well above (with sctm2)
* actual current time line, but in both cases, SCTM reference
* will be a system time computed while currentTimeNanos() was
* being called, so a valid time to return, and for next calls,
* as for each call, time window validity will be checked again.
*/
TimeRef newTimeRef = new TimeRef(sctm1,snt);
this.timeRef.set(newTimeRef);
return newTimeRef;
}
private void setMinSCTMJump(long newValue) {
setMinAndGet(this.minSCTMJumpMS, newValue);
long newFutureToleranceNS = (long)Math.ceil(newValue * (this.futureToleranceRatio * 1000000.0));
setMinAndGet(this.futureToleranceNS, newFutureToleranceNS);
}
/**
* Atomically sets the specified atomic long with min(atomic long value, specified value),
* and returns the new atomic long value (which might have not changed).
*/
private static long setMinAndGet(AtomicLong atomic, long value) {
long tmpLastReturned;
do {
tmpLastReturned = atomic.get();
if (tmpLastReturned <= value) {
return tmpLastReturned;
}
// Here, value < tmpLastReturned,
// so we will try to set it as new value.
} while (!atomic.compareAndSet(tmpLastReturned, value));
return value;
}
/**
* Atomically sets the specified atomic long with max(atomic long value, specified value),
* and returns the new atomic long value (which might have not changed).
*/
private static long setMaxAndGet(AtomicLong atomic, long value) {
long tmpLastReturned;
do {
tmpLastReturned = atomic.get();
if (tmpLastReturned >= value) {
return tmpLastReturned;
}
// Here, value > tmpLastReturned,
// so we will try to set it as new value.
} while (!atomic.compareAndSet(tmpLastReturned, value));
return value;
}
}
package thintime;
public class ThinTimePerf {
//--------------------------------------------------------------------------
// PUBLIC METHODS
//--------------------------------------------------------------------------
public static void main(String[] args) {
System.out.println("--- "+ThinTimePerf.class.getSimpleName()+" ---");
test_granularity();
System.out.println("");
System.out.println("--- done ---");
}
//--------------------------------------------------------------------------
// PRIVATE METHODS
//--------------------------------------------------------------------------
private static double sRounded(long ns) {
return Math.round(ns/1e6)/1e3;
}
private static void test_granularity() {
final int nbrOfCalls = 10 * 1000 * 1000;
System.out.println("");
System.out.println("--- testing granularity : loops of "+nbrOfCalls+" calls ---");
long previousTime = 0;
long time;
long a;
long b;
int nbrOfChanges;
/*
* System
*/
nbrOfChanges = 0;
a = System.nanoTime();
for (int i=0;i<nbrOfCalls;i++) {
time = System.currentTimeMillis();
if (time != previousTime) {
nbrOfChanges++;
}
previousTime = time;
}
b = System.nanoTime();
System.out.println("System.currentTimeMillis() : took "+sRounded(b-a)+" s");
System.out.println(" nbrOfChanges = "+nbrOfChanges);
nbrOfChanges = 0;
a = System.nanoTime();
for (int i=0;i<nbrOfCalls;i++) {
time = System.nanoTime();
if (time != previousTime) {
nbrOfChanges++;
}
previousTime = time;
}
b = System.nanoTime();
System.out.println("System.nanoTime() : took "+sRounded(b-a)+" s");
System.out.println(" nbrOfChanges = "+nbrOfChanges);
/*
* ThinTime
*/
nbrOfChanges = 0;
a = System.nanoTime();
for (int i=0;i<nbrOfCalls;i++) {
time = ThinTime.currentTimeMillis();
if (time != previousTime) {
nbrOfChanges++;
}
previousTime = time;
}
b = System.nanoTime();
System.out.println("ThinTime.currentTimeMillis() : took "+sRounded(b-a)+" s");
System.out.println(" nbrOfChanges = "+nbrOfChanges);
nbrOfChanges = 0;
a = System.nanoTime();
for (int i=0;i<nbrOfCalls;i++) {
time = ThinTime.currentTimeMicros();
if (time != previousTime) {
nbrOfChanges++;
}
previousTime = time;
}
b = System.nanoTime();
System.out.println("ThinTime.currentTimeMicros() : took "+sRounded(b-a)+" s");
System.out.println(" nbrOfChanges = "+nbrOfChanges);
nbrOfChanges = 0;
a = System.nanoTime();
for (int i=0;i<nbrOfCalls;i++) {
time = ThinTime.currentTimeNanos();
if (time != previousTime) {
nbrOfChanges++;
}
previousTime = time;
}
b = System.nanoTime();
System.out.println("ThinTime.currentTimeNanos() : took "+sRounded(b-a)+" s");
System.out.println(" nbrOfChanges = "+nbrOfChanges);
}
}
package thintime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import junit.framework.TestCase;
public class ThinTimeTest extends TestCase {
//--------------------------------------------------------------------------
// PRIVATE CLASSES
//--------------------------------------------------------------------------
/**
* Allows to test static behavior.
*/
private class MyThinTime extends ThinTime {
private long sctm;
private long snt;
public MyThinTime(
double futureToleranceRatio,
long initialMinSCTMJumpMS,
long systemTimeZeroMS) {
super(
futureToleranceRatio,
initialMinSCTMJumpMS,
systemTimeZeroMS);
}
protected long getSCTM() {
return sctm;
}
protected long getSNT() {
return snt;
}
}
private static class MyCallerRunnable implements Runnable {
private final long nbrOfCalls;
public MyCallerRunnable(long nbrOfCalls) {
this.nbrOfCalls = nbrOfCalls;
}
@Override
public void run() {
long previousRefNS = System.currentTimeMillis() * (1000L * 1000L);
long previousCTN = ThinTime.currentTimeNanos();
for (int i=0;i<this.nbrOfCalls;i++) {
final long forwardToleranceNS = 50L * 1000L * 1000L;
long ref1NS = previousRefNS;
long ctn = ThinTime.currentTimeNanos();
long ref2NS = System.currentTimeMillis() * (1000L * 1000L);
long deltaRefNS = ref2NS - ref1NS;
if (deltaRefNS < 0) {
System.err.println("time backward jump : "+deltaRefNS+" ns");
assertTrue(false);
} else {
if (Math.abs(deltaRefNS) > (1000L * 1000L * 1000L)) {
System.err.println("spent more than 1 second (maybe too many threads) : "+deltaRefNS+" ns");
assertTrue(false);
} else {
if (ctn < previousCTN) {
// If actual time backward jumps a bit, but we don't
// notice, ctn might also backward jump and we might
// notice it, so it's not necessarily abnormal.
System.err.println("ctn backward jump : "+(ctn - previousCTN)+" ns");
assertTrue(false);
}
if (ctn < ref1NS) {
System.err.println("ctn ["+ctn+"] < ref1NS ["+ref1NS+"]");
assertTrue(false);
} else if (ctn > ref2NS + forwardToleranceNS) {
long surplus = (ctn - (ref2NS + forwardToleranceNS));
System.err.println("ctn ["+ctn+"] > ref2NS ["+ref2NS+"] + forwardToleranceNS ["+forwardToleranceNS+"] by "+surplus+" ns");
assertTrue(false);
}
}
}
previousRefNS = ref2NS;
previousCTN = ctn;
}
}
}
//--------------------------------------------------------------------------
// MEMBERS
//--------------------------------------------------------------------------
// We consider the average granularity of ThinTime (for currentTimeMicros
// and currentTimeNanos) must be at least 10 microseconds.
private static final long MIN_THIN_TIME_GRANULARITY_NS = 10L * 1000L;
private static final long S_TO_MS = 1000L;
private static final long MS_TO_NS = 1000L * 1000L;
private static final long S_TO_NS = 1000L * 1000L * 1000L;
//--------------------------------------------------------------------------
// PUBLIC METHODS
//--------------------------------------------------------------------------
public void test_currentTimeMillis() {
final long toleranceMS = 1000L;
assertTrue(Math.abs(ThinTime.currentTimeMillis() - System.currentTimeMillis()) < toleranceMS);
}
public void test_currentTimeMicros_granularity() {
final long toleranceMS = 1000L;
assertTrue(Math.abs(ThinTime.currentTimeMicros()/1000L - System.currentTimeMillis()) < toleranceMS);
final int nbrOfRounds = 100000;
long previousTime = 0;
long time;
long dateA;
long dateB;
int nbrOfChanges;
nbrOfChanges = 0;
dateA = System.nanoTime();
for (int i=0;i<nbrOfRounds;i++) {
time = ThinTime.currentTimeMicros();
if (time != previousTime) {
nbrOfChanges++;
}
previousTime = time;
}
dateB = System.nanoTime();
assertTrue(nbrOfChanges >= (long)((dateB - dateA)/(double)MIN_THIN_TIME_GRANULARITY_NS));
}
public void test_currentTimeNanos_granularity() {
final long toleranceMS = 1000L;
assertTrue(Math.abs(ThinTime.currentTimeNanos()/1000000L - System.currentTimeMillis()) < toleranceMS);
final int nbrOfRounds = 100000;
long previousTime = 0;
long time;
long dateA;
long dateB;
int nbrOfChanges;
nbrOfChanges = 0;
dateA = System.nanoTime();
for (int i=0;i<nbrOfRounds;i++) {
time = ThinTime.currentTimeNanos();
if (time != previousTime) {
nbrOfChanges++;
}
previousTime = time;
}
dateB = System.nanoTime();
assertTrue(nbrOfChanges >= (long)((dateB - dateA)/(double)MIN_THIN_TIME_GRANULARITY_NS));
}
public void test_sequentialBehavior_minSCTMJump() {
final double futureToleranceRatio = 2.0;
final long initialMinSCTMJumpMS = 1000L;
final long systemTimeZeroMS = 123;
MyThinTime tt = new MyThinTime(
futureToleranceRatio,
initialMinSCTMJumpMS,
systemTimeZeroMS);
assertEquals(initialMinSCTMJumpMS, tt.getMinSCTMJumpMS());
// doesn't get lower on first call,
// which initializes previous SCTM value
tt.sctm += 100L;
tt.currentTimeNanos_();
assertEquals(initialMinSCTMJumpMS, tt.getMinSCTMJumpMS());
// gets lower
tt.sctm += 100L;
tt.currentTimeNanos_();
assertEquals(100L, tt.getMinSCTMJumpMS());
// gets lower again
tt.sctm += 10L;
tt.currentTimeNanos_();
assertEquals(10L, tt.getMinSCTMJumpMS());
// doesn't grow
tt.sctm += 100L;
tt.currentTimeNanos_();
assertEquals(10L, tt.getMinSCTMJumpMS());
}
public void test_sequentialBehavior_regular() {
final double futureToleranceRatio = 2.0;
final long initialMinSCTMJumpMS = 1000L;
final long systemTimeZeroMS = 123;
MyThinTime tt = new MyThinTime(
futureToleranceRatio,
initialMinSCTMJumpMS,
systemTimeZeroMS);
long expectedNS;
long expectedRefSCTM;
long expectedRefSNT;
long minSCTMJumpMS;
// initial time
expectedNS = (tt.sctm - systemTimeZeroMS) * MS_TO_NS;
expectedRefSCTM = -systemTimeZeroMS;
expectedRefSNT = 0;
minSCTMJumpMS = initialMinSCTMJumpMS;
// computing ctn
assertEquals(expectedNS, tt.currentTimeNanos_());
// call didn't change time ref
assertEquals(expectedRefSCTM, tt.getTimeRef().getRefSCTM());
assertEquals(expectedRefSNT, tt.getTimeRef().getRefSNT());
// call did't change min SCTM jump
assertEquals(minSCTMJumpMS, tt.getMinSCTMJumpMS());
// sctm and snt both getting ahead 10 seconds
tt.sctm += 10 * S_TO_MS;
tt.snt += 10 * S_TO_NS;
expectedNS += 10 * S_TO_NS;
// computing ctn
assertEquals(expectedNS, tt.currentTimeNanos_());
// call didn't change time ref
assertEquals(expectedRefSCTM, tt.getTimeRef().getRefSCTM());
assertEquals(expectedRefSNT, tt.getTimeRef().getRefSNT());
// call did't change min SCTM jump (smaller than our jump)
assertEquals(minSCTMJumpMS, tt.getMinSCTMJumpMS());
}
public void test_sequentialBehavior_backwardJumps() {
final double futureToleranceRatio = 2.0;
final long initialMinSCTMJumpMS = 1000L;
final long systemTimeZeroMS = 123;
MyThinTime tt = new MyThinTime(
futureToleranceRatio,
initialMinSCTMJumpMS,
systemTimeZeroMS);
long expectedNS = tt.currentTimeNanos_();
long expectedRefSCTM = tt.getTimeRef().getRefSCTM();
long expectedRefSNT = tt.getTimeRef().getRefSNT();
long minSCTMJumpMS = tt.getMinSCTMJumpMS();
// snt jumped 1ns forward: cnt changes accordingly,
// and time ref is not recomputed
tt.snt += 1;
expectedNS += 1;
// computing ctn
assertEquals(expectedNS, tt.currentTimeNanos_());
// call didn't change time ref
assertEquals(expectedRefSCTM, tt.getTimeRef().getRefSCTM());
assertEquals(expectedRefSNT, tt.getTimeRef().getRefSNT());
// call did't change min SCTM jump
assertEquals(minSCTMJumpMS, tt.getMinSCTMJumpMS());
// snt jumped back 1ns: not returning a time < to previously returned,
// but since computed ctn is not < sctm, time ref is not recomputed
tt.snt -= 1;
// computing ctn
assertEquals(expectedNS, tt.currentTimeNanos_());
// call didn't change time ref
assertEquals(expectedRefSCTM, tt.getTimeRef().getRefSCTM());
assertEquals(expectedRefSNT, tt.getTimeRef().getRefSNT());
// call did't change min SCTM jump
assertEquals(minSCTMJumpMS, tt.getMinSCTMJumpMS());
// snt jumped backward 1ns again: not returning a time < to previously returned,
// and since computed ctn is < sctm, time ref is recomputed
tt.snt -= 1;
// computing ctn
assertEquals(expectedNS, tt.currentTimeNanos_());
// call changed time ref
expectedRefSCTM = tt.sctm - systemTimeZeroMS;
expectedRefSNT = tt.snt;
assertEquals(expectedRefSCTM, tt.getTimeRef().getRefSCTM());
assertEquals(expectedRefSNT, tt.getTimeRef().getRefSNT());
// call did't change min SCTM jump
assertEquals(minSCTMJumpMS, tt.getMinSCTMJumpMS());
// sctm jumped backward, of exactly "future tolerance ratio * min SCTM jump":
// ctn just goes backward 1ns (due to previous backward jump of stn),
// and time ref is unchanged
tt.sctm -= (long)(futureToleranceRatio * minSCTMJumpMS);
expectedNS -= 1;
// computing ctn
assertEquals(expectedNS, tt.currentTimeNanos_());
// call didn't change time ref
assertEquals(expectedRefSCTM, tt.getTimeRef().getRefSCTM());
assertEquals(expectedRefSNT, tt.getTimeRef().getRefSNT());
// call did't change min SCTM jump
assertEquals(minSCTMJumpMS, tt.getMinSCTMJumpMS());
// sctm jumped backward 1ms, i.e. now computed ctn is past future tolerance.
// Backward time jump is detected: time ref is recomputed.
tt.sctm -= 1;
expectedNS = (tt.sctm - systemTimeZeroMS) * MS_TO_NS;
// computing ctn
assertEquals(expectedNS, tt.currentTimeNanos_());
// call did change time ref
expectedRefSCTM = tt.sctm - systemTimeZeroMS;
expectedRefSNT = tt.snt;
assertEquals(expectedRefSCTM, tt.getTimeRef().getRefSCTM());
assertEquals(expectedRefSNT, tt.getTimeRef().getRefSNT());
// call did't change min SCTM jump (1ms is smaller, but not considering backward jumps)
assertEquals(minSCTMJumpMS, tt.getMinSCTMJumpMS());
}
public void test_sequentialBehavior_forwardJumps() {
final double futureToleranceRatio = 2.0;
final long initialMinSCTMJumpMS = 1000L;
final long systemTimeZeroMS = 123;
MyThinTime tt = new MyThinTime(
futureToleranceRatio,
initialMinSCTMJumpMS,
systemTimeZeroMS);
long deltaNS;
long expectedNS = tt.currentTimeNanos_();
long expectedRefSCTM = tt.getTimeRef().getRefSCTM();
long expectedRefSNT = tt.getTimeRef().getRefSNT();
long minSCTMJumpMS = tt.getMinSCTMJumpMS();
// moving snt at future tolerance
deltaNS = (long)(futureToleranceRatio * minSCTMJumpMS * MS_TO_NS);
tt.snt += deltaNS;
expectedNS += deltaNS;
// computing ctn
assertEquals(expectedNS, tt.currentTimeNanos_());
// call didn't change time ref
assertEquals(expectedRefSCTM, tt.getTimeRef().getRefSCTM());
assertEquals(expectedRefSNT, tt.getTimeRef().getRefSNT());
// call did't change min SCTM jump
assertEquals(minSCTMJumpMS, tt.getMinSCTMJumpMS());
// moving snt 1ns past future tolerance:
// won't go past it, and will recompute time ref,
// but won't return "sctm - offset" as current time,
// since this would make returned time jump backward:
// instead, returns the same as previously returned
tt.snt += 1;
assertEquals(expectedNS, tt.currentTimeNanos_());
// call did change time ref
expectedRefSCTM = tt.sctm - systemTimeZeroMS;
expectedRefSNT = tt.snt;
assertEquals(expectedRefSCTM, tt.getTimeRef().getRefSCTM());
assertEquals(expectedRefSNT, tt.getTimeRef().getRefSNT());
// call did't change min SCTM jump
assertEquals(minSCTMJumpMS, tt.getMinSCTMJumpMS());
// moving sctm 10s ahead, to get away of previous returned time,
// and moving snt 1ns past future tolerance:
// time ref will be recomputed
tt.sctm += 10 * S_TO_MS;
tt.snt += 10 * S_TO_NS + (long)(futureToleranceRatio * minSCTMJumpMS * MS_TO_NS) + 1;
expectedNS = (tt.sctm - systemTimeZeroMS) * MS_TO_NS;
assertEquals(expectedNS, tt.currentTimeNanos_());
// call did change time ref
expectedRefSCTM = tt.sctm - systemTimeZeroMS;
expectedRefSNT = tt.snt;
assertEquals(expectedRefSCTM, tt.getTimeRef().getRefSCTM());
assertEquals(expectedRefSNT, tt.getTimeRef().getRefSNT());
// call did't change min SCTM jump (which is smaller)
assertEquals(minSCTMJumpMS, tt.getMinSCTMJumpMS());
}
/*
*
*/
public static void test_currentTimeNanos_behavior() {
final ExecutorService executor = Executors.newCachedThreadPool();
final long nbrOfCalls = 1000L * 1000L;
final int nbrOfThreads = 1 + Runtime.getRuntime().availableProcessors();
for (int i=0;i<nbrOfThreads;i++) {
executor.execute(new MyCallerRunnable(nbrOfCalls));
}
executor.shutdown();
try {
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
More information about the core-libs-dev
mailing list