我自己和我这个时代的另一位开发人员最近从一台Core 2 Duo机器搬到了新的Core 2 Quad 9505;两者都使用JDK 1.6.0_18运行Windows XP SP3 32位。
这样做后,我们对一些时序/统计/指标聚合代码的自动单元测试立即开始失败,因为看起来似乎是从System.nanoTime()返回的荒谬值。
在我的机器上可靠地显示此行为的测试代码是:
import static org.junit.Assert.assertThat;
import org.hamcrest.Matchers;
import org.junit.Test;
public class NanoTest {
@Test
public void testNanoTime() throws InterruptedException {
final long sleepMillis = 5000;
long nanosBefore = System.nanoTime();
long millisBefore = System.currentTimeMillis();
Thread.sleep(sleepMillis);
long nanosTaken = System.nanoTime() - nanosBefore;
long millisTaken = System.currentTimeMillis() - millisBefore;
System.out.println("nanosTaken="+nanosTaken);
System.out.println("millisTaken="+millisTaken);
// Check it slept within 10% of requested time
assertThat((double)millisTaken, Matchers.closeTo(sleepMillis, sleepMillis * 0.1));
assertThat((double)nanosTaken, Matchers.closeTo(sleepMillis * 1000000, sleepMillis * 1000000 * 0.1));
}
}
典型输出:
millisTaken=5001
nanosTaken=2243785148
运行100x会产生纳米结果,占实际睡眠时间的33%到60%;通常只有40%左右。
我理解Windows中计时器准确性的弱点,并阅读了Is System.nanoTime() consistent across threads?之类的相关主题,但我的理解是System.nanoTime()的目的正是为了我们正在使用的目的: - 测量经过的时间;比currentTimeMillis()更准确。
有谁知道为什么它会返回如此疯狂的结果?这可能是硬件架构问题(唯一重要的是这台机器上的CPU /主板)? Windows HAL与我当前的硬件有问题?一个JDK问题?我应该放弃nanoTime()吗?我应该在某处记录错误,还是有关我如何进一步调查的任何建议?
更新19/07 03:15 UTC :在尝试下面的finnw测试用例之后,我做了一些谷歌搜索,遇到bugid:6440250之类的条目。它还让我想起了周五晚些时候我注意到的一些其他奇怪的行为,其中ping是负面的。所以我将 / usepmtimer 添加到我的boot.ini中,现在所有的测试都按预期运行。我的ping也是正常的。
我有点困惑为什么这仍然是一个问题;从我的阅读中我认为TSC vs PMT问题在Windows XP SP3中基本得到了解决。可能是因为我的机器最初是SP2,并且被修补到SP3而不是最初安装为SP3?我现在也想知道是否应该安装像MS KB896256那样的补丁。也许我应该在企业桌面构建团队中解决这个问题?
答案 0 :(得分:6)
通过在 C:\ boot的末尾添加 / usepmtimer ,解决了问题(对于nanoTime()在多核系统上的适用性有一些公开的怀疑!)。 ini 字符串;强制Windows使用电源管理计时器而不是TSC。这是一个悬而未决的问题,为什么我需要这样做,因为我在XP SP3上,因为我知道这是默认设置,但也许是因为我的机器被修补到SP3的方式。
答案 1 :(得分:2)
在我的系统上(Windows 7 64位,Core i7 980X):
nanosTaken=4999902563
millisTaken=5001
System.nanoTime()使用特定于操作系统的调用,因此我预计您的Windows /处理器组合中会出现错误。
答案 2 :(得分:1)
您可能想要阅读其他堆栈溢出问题的答案:Is System.nanoTime() completely useless?。
总之,看起来nanoTime依赖于可能受多个核心CPU存在影响的操作系统定时器。因此,nanoTime在OS和CPU的某些组合上可能没那么有用,在您打算在多个目标平台上运行的可移植Java代码中使用它时应该小心。网上似乎有很多关于这个主题的抱怨,但对一个有意义的替代方案没有多少共识。
答案 3 :(得分:1)
很难判断这是一个错误还是内核之间的正常计时器差异。
您可以尝试的实验是使用本机调用来强制线程在特定核心上运行。
另外,为了排除电源管理效果,请尝试在循环中旋转以替代sleep()
:
import com.sun.jna.Native;
import com.sun.jna.NativeLong;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.W32API;
public class AffinityTest {
private static void testNanoTime(boolean sameCore, boolean spin)
throws InterruptedException {
W32API.HANDLE hThread = kernel.GetCurrentThread();
final long sleepMillis = 5000;
kernel.SetThreadAffinityMask(hThread, new NativeLong(1L));
Thread.yield();
long nanosBefore = System.nanoTime();
long millisBefore = System.currentTimeMillis();
kernel.SetThreadAffinityMask(hThread, new NativeLong(sameCore? 1L: 2L));
if (spin) {
Thread.yield();
while (System.currentTimeMillis() - millisBefore < sleepMillis)
;
} else {
Thread.sleep(sleepMillis);
}
long nanosTaken = System.nanoTime() - nanosBefore;
long millisTaken = System.currentTimeMillis() - millisBefore;
System.out.println("nanosTaken="+nanosTaken);
System.out.println("millisTaken="+millisTaken);
}
public static void main(String[] args) throws InterruptedException {
System.out.println("Sleeping, different cores");
testNanoTime(false, false);
System.out.println("\nSleeping, same core");
testNanoTime(true, false);
System.out.println("\nSpinning, different cores");
testNanoTime(false, true);
System.out.println("\nSpinning, same core");
testNanoTime(true, true);
}
private static final Kernel32Ex kernel =
(Kernel32Ex) Native.loadLibrary(Kernel32Ex.class);
}
interface Kernel32Ex extends Kernel32 {
NativeLong SetThreadAffinityMask(HANDLE hThread, NativeLong dwAffinityMask);
}
如果根据核心选择得到非常不同的结果(例如,在同一核心上为5000毫秒但在不同核心上为2200毫秒),这表明问题只是核心之间的自然定时器变化。
如果你因睡眠与旋转而得到非常不同的结果,那很可能是因为电源管理会降低时钟速度。
如果四个结果的 none 接近5000毫秒,则可能是一个错误。