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