NanoTime for dual-core AMD systems.

You’ve probably heard of the issue where System.nanoTime() can return strange results on dual-core AMD systems, because the timer is based off of CPU ticks which can be different on each CPU.

Okay, so I don’t have a dual-core AMD system to test, but one person said this code works, so I thought I’d go ahead and share it.


/**
    The GameTimer is used to workaround the issue on some dual-core systems where timing 
    values between different CPUs are out of sync. In this case, the value of System.nanoTime()
    can be different depending on what CPU the code is executed on.
    
    This code attempts to guess what CPU the code is executed on, and adjusts accordingly.
    
    *** For best results, only call nanoTime() once per frame. *** 
*/
public class GameTimer {
    
    private static final GameTimer INSTANCE = new GameTimer();
    
    private static final int NUM_TIMERS = 8;
    private static final long MAX_DIFF = 1000000000L; // 1 sec
    private static final long NEVER_USED = -1; 
    
    private long[] lastTimeStamps = new long[NUM_TIMERS];
    private long[] timeSinceLastUsed = new long[NUM_TIMERS];
    
    private long virtualNanoTime = 0;
    private int timesInARowNewTimerChosen = 0;
    private long lastDiff = 0;
    
    public static GameTimer getInstance() {
        return INSTANCE;
    }
    
    private GameTimer() {
        for (int i = 0; i < NUM_TIMERS; i++) {
            timeSinceLastUsed[i] = NEVER_USED;
        }
    }
    
    public long nanoTime() {
        long diff;
        
        if (timesInARowNewTimerChosen >= NUM_TIMERS) {
            long nanoTime = System.currentTimeMillis() * 1000000;
            diff = nanoTime - lastTimeStamps[0];
        }
        else {  
            long nanoTime = System.nanoTime();
    
            // Find which timer the nanoTime value came from 
            int bestTimer = -1;
            long bestDiff = 0;
            for (int i = 0; i < NUM_TIMERS; i++) {
                if (timeSinceLastUsed[i] != NEVER_USED) {
                    long t = lastTimeStamps[i] + timeSinceLastUsed[i];
                    long timerDiff = nanoTime - t;
                    if (timerDiff > 0 && timerDiff < MAX_DIFF) {
                        if (bestTimer == -1 || timerDiff < bestDiff) {
                            bestTimer = i;
                            bestDiff = timerDiff;
                        }
                    }
                }
            }
            
            // No best timer found
            if (bestTimer == -1) {
                // Use last good diff
                diff = lastDiff;
                
                // Find a new timer 
                bestTimer = 0;
                for (int i = 0; i < NUM_TIMERS; i++) {
                    if (timeSinceLastUsed[i] == NEVER_USED) {
                        // This timer never used - use it
                        bestTimer = i;
                        break;
                    }
                    else if (timeSinceLastUsed[i] > timeSinceLastUsed[bestTimer]) {
                        // Least used timer so far, but keep looking
                        bestTimer = i;
                    }
                }
                timesInARowNewTimerChosen++;
            }
            else {
                timesInARowNewTimerChosen = 0;
                diff = nanoTime - lastTimeStamps[bestTimer] - timeSinceLastUsed[bestTimer];
                // Set lastDiff if this same timer used twice in a row
                if (timeSinceLastUsed[bestTimer] == 0) {
                    lastDiff = diff;
                }
            }
            
            lastTimeStamps[bestTimer] = nanoTime;
            timeSinceLastUsed[bestTimer] = 0;
            
            // Increment usage of all other timers
            for (int i = 0; i < NUM_TIMERS; i++) {
                if (i != bestTimer && timeSinceLastUsed[i] != NEVER_USED) {
                    timeSinceLastUsed[i] += diff;
                }
            }
            
            // Check for total failure
            if (timesInARowNewTimerChosen >= NUM_TIMERS) {
                lastTimeStamps[0] = System.currentTimeMillis() * 1000000;
            }
        }
        
        virtualNanoTime += diff;
        
        return virtualNanoTime;
    }
}

It’s kinda sad that when timesInARowNewTimerChosen reaches NUM_TIMERS only once, it will use System.currentTimeMillis() forever. There should be some kind of max-period it is used, after which timesInARowNewTimerChosen is reset to 0

My thinking is that if it gets to that point, nanoTime is completely unreliable, and currentTimeMillis is used as a fall back. I doubt it will ever get to that point.

But hey, consider the code public domain - feel free to modify it.

Or you could just install the AMD Dual-Core Optimizer from here

It smoothed alot of the games that I have and a LWJGL app too!

DP :slight_smile:

Of course, but that’s not the point. The end users need to install that as well, which is a lot tricker to achieve.

I have been thinking to change my timer and use currentTimeMillis() in my game.
Are there still computers being made with the timer issue? About what % of computers would still have this issue?