为什么System.nanoTime()和System.currentTimeMillis()如此迅速地分开?

时间:2011-04-30 02:57:12

标签: java windows datetime time

出于诊断目的,我希望能够在长时间运行的服务器应用程序中检测系统时钟的变化。由于System.currentTimeMillis()基于挂钟时间,System.nanoTime()基于挂钟时间独立(*)的系统计时器,我想我可以使用这些值之间的差异来检测系统时间变了。

我写了一个快速测试应用程序,看看这些值之间的差异是多么稳定,令我惊讶的是,这些值在我每秒几毫秒的水平上立即发散。有几次我看到了更快的分歧。这是在带有Java 6的Win7 64位桌面上。我没有在Linux(或Solaris或MacOS)下尝试过这个测试程序来查看它的执行情况。对于这个应用程序的一些运行,分歧是积极的,对于某些运行它是负面的。它似乎取决于桌面正在做什么,但很难说。

public class TimeTest {
  private static final int ONE_MILLION  = 1000000;
  private static final int HALF_MILLION =  499999;

  public static void main(String[] args) {
    long start = System.nanoTime();
    long base = System.currentTimeMillis() - (start / ONE_MILLION);

    while (true) {
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        // Don't care if we're interrupted
      }
      long now = System.nanoTime();
      long drift = System.currentTimeMillis() - (now / ONE_MILLION) - base;
      long interval = (now - start + HALF_MILLION) / ONE_MILLION;
      System.out.println("Clock drift " + drift + " ms after " + interval
                         + " ms = " + (drift * 1000 / interval) + " ms/s");
    }
  }
}

Thread.sleep()时间的不准确以及中断,应与定时器漂移完全无关。

这两个Java“系统”调用都用作测量 - 一个用于测量挂钟时间的差异,另一个用于测量绝对间隔,因此当实时时钟未被更改时,这些值应该以非常接近相同的速度变化,对吧?这是Java中的错误或弱点还是失败?操作系统或硬件中是否存在阻止Java更准确的内容?

我完全期望这些独立测量之间存在一些漂移和抖动(**),但我预计每天漂移不到一分钟。每秒1毫秒的漂移,如果是单调的,几乎是90秒!我观察到的最坏情况可能是十倍。每次我运行这个程序时,我都会看到第一次测量的偏差。到目前为止,我还没有运行该程序超过30分钟。

由于抖动,我希望在打印的值中看到一些小的随机性,但在程序的几乎所有运行中,我都看到差异的稳定增加,通常每秒增加3毫秒,增加几倍不止于此。

任何版本的Windows都有类似于Linux的机制,可以调整系统时钟速度,以便慢慢使时钟与外部时钟源同步吗?这样的事情会影响定时器,还是只影响挂钟定时器?

(*)据我所知,在某些体系结构中,System.nanoTime()必然会使用与System.currentTimeMillis()相同的机制。我也相信,假设任何现代Windows服务器都不是这样的硬件架构是公平的。这是一个不好的假设吗?

(**)当然,System.currentTimeMillis()通常会有比System.nanoTime()大得多的抖动,因为在大多数系统上它的粒度不是1毫秒。

5 个答案:

答案 0 :(得分:11)

您可能会感兴趣this Sun/Oracle blog post about JVM timers

以下是该文章中有关Windows下JVM计时器的几个段落:

  

System.currentTimeMillis()是使用GetSystemTimeAsFileTime方法实现的,该方法基本上只读取Windows维护的低分辨率时间值。根据报告的信息,读取这个全局变量自然非常快 - 大约6个周期。无论定时器中断如何编程,这个时间值都以恒定速率更新 - 取决于平台,这将是10ms或15ms(此值似乎与默认中断周期相关)。

     

System.nanoTime()是使用QueryPerformanceCounter / QueryPerformanceFrequency API实现的(如果可用,否则返回currentTimeMillis*10^6)。 QueryPerformanceCounter(QPC)以不同的方式实现,具体取决于它运行的硬件。通常,它将使用可编程间隔定时器(PIT)或ACPI电源管理定时器(PMT)或CPU级时间戳计数器(TSC)。访问PIT / PMT需要执行慢速I / O端口指令,因此QPC的执行时间大约为微秒。相反,读取TSC大约为100个时钟周期(从芯片读取TSC并将其转换为基于工作频率的时间值)。您可以通过检查QueryPerformanceFrequency是否返回3,579,545(即3.57MHz)的签名值来判断您的系统是否使用ACPI PMT。如果您看到大约1.19Mhz的值,那么您的系统正在使用旧的8245 PIT芯片。否则,您应该看到一个大约相当于CPU频率的值(模数可能有效的任何速度限制或电源管理。)

答案 1 :(得分:7)

我不确定这实际上有多大帮助。但这是Windows / Intel / AMD / Java世界中一个积极变化的领域。在几个(至少10年)内,对准确和精确的时间测量的需求已经很明显。英特尔和AMD都通过改变TSC的工作方式做出了回应。两家公司现在都有一种名为 Invariant-TSC 和/或 Constant-TSC 的东西。

结帐rdtsc accuracy across CPU cores。引自osgx(指英特尔手册)。

“16.11.1不变的TSC

较新处理器中的时间戳计数器可能支持增强,称为不变TSC。处理器对不变TSC的支持由PUID.80000007H:EDX [8]表示。

不变的TSC将在所有ACPI P-,C-中以恒定速率运行。和T状态。这是向前发展的建筑行为。在具有不变TSC支持的处理器上,OS可以将TSC用于挂钟计时器服务(而不是ACPI或HPET计时器)。 TSC读取效率更高,不会产生与环转换或访问平台资源相关的开销。“

另见http://www.citihub.com/requesting-timestamp-in-applications/。引自作者

对于AMD:

如果CPUID 8000_0007.edx [8] = 1,则确保TSC速率在所有P状态,C状态和停止授权转换(例如STPCLK限制)之间保持不变;因此,TSC适合用作时间源。

对于英特尔:

处理器对不变TSC的支持由CPUID.80000007H:EDX [8]指示。不变的TSC将在所有ACPI P-,C-中以恒定速率运行。和T状态。这是向前发展的建筑行为。在具有不变TSC支持的处理器上,OS可以将TSC用于挂钟计时器服务(而不是ACPI或HPET计时器)。 TSC读取效率更高,不会产生与环转换或访问平台资源相关的开销。“

现在非常重要的一点是,最新的JVM似乎正在利用新的可靠TSC机制。没有多少在线显示这一点。但是,请查看http://code.google.com/p/disruptor/wiki/PerformanceResults

“为了测量延迟,我们采用三级流水线并生成低于饱和度的事件。这是通过在注入事件之前等待1微秒然后注入下一个并重复5000万次来实现的。要达到这个精度水平的时间需要使用来自CPU的时间戳计数器。我们选择具有不变TSC的CPU,因为较旧的处理器由于节能和睡眠状态而受到频率变化的影响.Intel Nehalem和后来的处理器使用可由最新Oracle访问的不变TSC在Ubuntu 11.04上运行的JVM。此测试没有使用CPU绑定“

请注意,“Disruptor”的作者与处理Azul和其他JVM的人有密切联系。

另请参阅“幕后的Java飞行记录”。本演示文稿提到了新的不变TSC指令。

答案 2 :(得分:2)

“返回最精确的可用系统计时器的当前值,以纳秒为单位。

“此方法只能用于测量经过的时间,与系统或挂钟时间的任何其他概念无关。返回的值表示纳秒,因为某些固定但是任意时间(可能在未来,所以值可能这种方法提供纳秒精度,但不一定是纳秒精度。不能保证值的变化频率。连续调用的差异超过大约292年(2 ** 63纳秒)将无法准确计算经过的时间由于数字溢出。“

请注意,它表示“精确”,而不是“准确”。

这不是“Java中的错误”或任何“bug”。这是一个定义。 JVM开发人员四处寻找系统中最快的时钟/定时器并使用它。如果那与系统时钟锁定步骤那么好,但如果不是,那就是cookie崩溃的方式。比如说,计算机系统将具有准确的系统时钟,但内部具有更高速率的定时器,这与CPU时钟速率或某些此类速率相关,这完全合情合理。由于时钟频率通常会发生变化以最大限度地降低功耗,因此内部定时器的增量速率会发生变化。

答案 3 :(得分:2)

System.currentTimeMillis()System.nanoTime()不一定由提供 相同的硬件。 System.currentTimeMillis()支持GetSystemTimeAsFileTime() 拥有100ns的分辨率元素。它的来源是系统计时器。 System.nanoTime()由系统的高性能计数器支持。有各种各样的硬件 提供这个柜台。因此,其分辨率因底层硬件而异。

在任何情况下都不能假设这两个来源同相。测量这两个值 相互对抗将披露不同的运行速度。如果System.currentTimeMillis()的更新被视为真正的时间进度,System.nanoTime()的输出有时可能会更慢,有时更快,也会变化。

必须进行仔细校准才能锁定这两个时间源。

可以找到这两个时间源之间关系的更详细描述 在Windows Timestamp Project

答案 4 :(得分:2)

  

任何版本的Windows都有类似于Linux的机制,可以调整系统时钟速度,以便慢慢使时钟与外部时钟源同步吗?这样的事情会影响定时器,还是只影响挂钟定时器?

Windows Timestamp Project符合您的要求。据我所知,它只影响挂钟计时器。