【问题标题】:How can I get the Windows system time with millisecond resolution?如何获得毫秒分辨率的 Windows 系统时间?
【发布时间】:2011-04-13 08:26:10
【问题描述】:

如何获得毫秒分辨率的 Windows 系统时间?

如果以上都做不到,那如何获取操作系统的启动时间呢?我想将此值与 timeGetTime() 一起使用,以计算具有毫秒分辨率的系统时间。

提前谢谢你。

【问题讨论】:

标签: windows time system resolution


【解决方案1】:

试试这篇来自 MSDN 杂志的文章。其实挺复杂的。

Implement a Continuously Updating, High-Resolution Time Provider for Windows

【讨论】:

  • 我不想要计时器,我想要毫秒精度的系统时间。
  • 你读过这篇文章吗?那是什么!这家伙完全按照你的要求做了,并展示了它是如何完成的,包括代码。
  • 我投了赞成票……但我无法让该网站上的代码正常工作。它大部分时间都很难锁定我的电脑。有时它有效,当它有效时,它在一个非常繁忙的 CPU 上是完全准确的。仍在翻找代码,看看为什么它不起作用。我的设置是带有 Visual Studio 2005 和 C++ Builder XE 的 Win7 Pro 64bit。如果有人感兴趣,我将代码移植到 C++ Builder。可能会成为一个好的开源项目;)
  • 来自链接的文章“该代码不打算在任何可用系统上按原样使用,因为可能会因...而出现问题”。值得注意的是,这篇文章甚至没有提到一个严重的问题:性能计数器通常无法在内核/cpu 之间正确同步(微软指责硬件抽象层 (HAL)),因此在一个线程上进行的校准可能无法正确地得到结果另外一个。在我对不同系统的测量中,TSC 值的不同步可能会持续数秒。
【解决方案2】:

这是对上述 cmets 的详细说明,以解释其中的一些原因。

首先,GetSystemTime* 调用是唯一提供系统时间的 Win32 API。这个时间的粒度相当粗略,因为大多数应用程序不需要维持更高分辨率所需的开销。时间(可能)在内部存储为 64 位毫秒计数。调用 timeGetTime 获取低 32 位。调用 GetSystemTime 等请求 Windows 返回此毫秒时间,转换为天等后,包括系统启动时间。

机器中有两个时间源:CPU 时钟和板载时钟(例如,实时时钟 (RTC)、可编程间隔定时器 (PIT) 和高精度事件定时器 (HPET))。第一个具有约 0.5ns (2GHz) 的分辨率,第二个通常可编程为 1ms 的周期(尽管较新的芯片 (HPET) 具有更高的分辨率)。 Windows 使用这些周期性滴答来执行某些操作,包括更新系统时间。

应用程序可以通过timerBeginPeriod更改此期间;但是,这会影响整个系统。操作系统将以请求的频率检查/更新常规事件。在 CPU 负载/频率较低的情况下,存在用于节能的空闲期。在高频下,没有时间将处理器置于低功耗状态。有关详细信息,请参阅Timer Resolution。最后,每个tick都有一些开销,增加频率会消耗更多的CPU周期。

对于更高分辨率的时间,系统时间没有保持到这个精度,不超过大本钟有一个秒针。使用 QueryPerformanceCounter(QPC) 或 CPU 的节拍 (rdtsc) 可以提供系统时间节拍之间的分辨率。凯文引用的 MSDN 杂志文章中使用了这种方法。尽管这些方法可能存在漂移(例如,由于频率缩放)等,因此需要与系统时间同步。

【讨论】:

  • 我喜欢你的回答。如果 windows 运行 1ms 这么难,那么 Linux 怎么做呢?
  • 我不知道这两个操作系统在 1ms 周期内的表现如何,但两者都可以做到。在任何软件设计中,都必须进行权衡。代码应该设计为 1 毫秒还是 16 毫秒(特别是 15.625 毫秒)?即使这样,一些设计点也会执行得更好。
【解决方案3】:

在 Windows 中,所有时间的基础是一个名为 GetSystemTimeAsFiletime 的函数。

  • 它返回一个结构,该结构能够以 100ns 的分辨率保持时间。
  • 以 UTC 保存

FILETIME 结构记录自 1600 年 1 月 1 日以来 100ns 间隔的数量;意味着它的分辨率限制在 100ns。

这构成了我们的第一个函数:

自 1600 年 1 月 1 日以来 100ns 滴答的 64 位数字有点笨拙。 Windows 提供了一个方便的辅助函数FileTimeToSystemTime,可以将这个 64 位整数解码为有用的部分:

record SYSTEMTIME {
   wYear: Word;
   wMonth: Word;
   wDayOfWeek: Word;
   wDay: Word;
   wHour: Word;
   wMinute: Word;
   wSecond: Word;
   wMilliseconds: Word;
}

请注意,SYSTEMTIME 具有 1ms 的内置分辨率限制

现在我们可以从FILETIMESYSTEMTIME

我们可以编写函数来获取当前系统时间作为SYSTEIMTIME 结构:

SYSTEMTIME GetSystemTime()
{
    //Get the current system time utc in it's native 100ns FILETIME structure
    FILETIME ftNow;
    GetSytemTimeAsFileTime(ref ft);

    //Decode the 100ns intervals into a 1ms resolution SYSTEMTIME for us
    SYSTEMTIME stNow;
    FileTimeToSystemTime(ref stNow);

    return stNow;
}

除了Windows已经为你写了这样的函数:GetSystemTime

本地,而不是 UTC

现在,如果您不想要 UTC 中的当前时间怎么办。如果你想在你的当地时间呢? Windows 提供了将 UTC 格式的 FILETIME 转换为本地时间的功能:FileTimeToLocalFileTime

您可以编写一个函数,在本地时间返回FILETIME

FILETIME GetLocalTimeAsFileTime()
{
   FILETIME ftNow;
   GetSystemTimeAsFileTime(ref ftNow);

   //convert to local
   FILETIME ftNowLocal
   FileTimeToLocalFileTime(ftNow, ref ftNowLocal);

   return ftNowLocal;
}

假设您想将 local FILETIME 解码为 SYSTEMTIME。没问题,你可以再次使用FileTimeToSystemTime

幸运的是,Windows 已经为您提供了一个返回值的函数:

精确

还有另一个考虑因素。在 Windows 8 之前,时钟的分辨率约为 15 毫秒。在 Windows 8 中,他们将时钟提高到 100ns(与FILETIME 的分辨率相匹配)。

  • GetSystemTimeAsFileTime (旧版,15 毫秒分辨率)
  • GetSystemTimeAsPreciseFileTime (Windows 8,100ns 分辨率)

这意味着我们应该总是更喜欢新值:

你问了时间

你问了时间;但你有一些选择。

时区:

  • UTC (系统原生)
  • 当地时区

格式:

  • FILETIME (系统原生,100ns 分辨率)
  • SYTEMTIME (解码,1ms 分辨率)

总结

  • 100ns分辨率:FILETIME
    • UTC:GetSytemTimeAsPreciseFileTime (或GetSystemTimeAsFileTime
    • 本地:(自己滚动)
  • 1ms分辨率:SYSTEMTIME
    • UTC:GetSystemTime
    • 本地:GetLocalTime

【讨论】:

    【解决方案4】:

    GetTickCount 不会为你完成它。

    查看QueryPerformanceFrequency / QueryPerformanceCounter。不过,这里唯一的问题是 CPU 扩展,因此请进行研究。

    【讨论】:

    • QPC 不是我想要的。我想要毫秒精度的系统时间,而不是计时器。
    • 省电(甚至只是空闲省电)和 Turbo Boost 与 QPF 一起玩得很开心。它曾经非常有效,但在现代 CPU 上已经不行了。
    • “这里唯一的问题是 CPU 扩展” - 不正确。这些函数无法处理硬件抽象层没有跨内核/cpu 同步 TSC 值的系统:这可能是几秒钟的增量。当在一个核心上采集一个样本,然后与在另一个核心上采集的样本进行比较时,它会咬人。有多核使用的解决方法,但我没有看到打包在公开可用的库中。您可以将线程绑定到单个核心,但有时这比时间有用更具破坏性。
    • QPF 仅在一些旧的多核设计和损坏的 BIOS 上无法正常工作。一般来说,QPF 工作得很好。从vista开始,主板提供的专用高精度计数器用于QPC
    【解决方案5】:

    从 Windows 8 开始,微软引入了新的 API 命令 GetSystemTimePreciseAsFileTime:

    https://msdn.microsoft.com/en-us/library/windows/desktop/hh706895%28v=vs.85%29.aspx

    很遗憾,如果您创建的软件还必须在旧操作系统上运行,您将无法使用它。

    我目前的解决方案如下,但请注意:确定的时间并不准确,它只是接近实时。结果应该总是小于或等于实时,但有一个固定的错误(除非计算机进入待机状态)。结果具有毫秒分辨率。就我的目的而言,它已经足够准确了。

    void GetHighResolutionSystemTime(SYSTEMTIME* pst)
    {
        static LARGE_INTEGER    uFrequency = { 0 };
        static LARGE_INTEGER    uInitialCount;
        static LARGE_INTEGER    uInitialTime;
        static bool             bNoHighResolution = false;
    
        if(!bNoHighResolution && uFrequency.QuadPart == 0)
        {
            // Initialize performance counter to system time mapping
            bNoHighResolution = !QueryPerformanceFrequency(&uFrequency);
            if(!bNoHighResolution)
            {
                FILETIME ftOld, ftInitial;
    
                GetSystemTimeAsFileTime(&ftOld);
                do
                {
                    GetSystemTimeAsFileTime(&ftInitial);
                    QueryPerformanceCounter(&uInitialCount);
                } while(ftOld.dwHighDateTime == ftInitial.dwHighDateTime && ftOld.dwLowDateTime == ftInitial.dwLowDateTime);
                uInitialTime.LowPart  = ftInitial.dwLowDateTime;
                uInitialTime.HighPart = ftInitial.dwHighDateTime;
            }
        }
    
        if(bNoHighResolution)
        {
            GetSystemTime(pst);
        }
        else
        {
            LARGE_INTEGER   uNow, uSystemTime;
    
            {
                FILETIME    ftTemp;
                GetSystemTimeAsFileTime(&ftTemp);
                uSystemTime.LowPart  = ftTemp.dwLowDateTime;
                uSystemTime.HighPart = ftTemp.dwHighDateTime;
            }
            QueryPerformanceCounter(&uNow);
    
            LARGE_INTEGER   uCurrentTime;
            uCurrentTime.QuadPart = uInitialTime.QuadPart + (uNow.QuadPart - uInitialCount.QuadPart) * 10000000 / uFrequency.QuadPart;
    
            if(uCurrentTime.QuadPart < uSystemTime.QuadPart || abs(uSystemTime.QuadPart - uCurrentTime.QuadPart) > 1000000)
            {
                // The performance counter has been frozen (e. g. after standby on laptops)
                // -> Use current system time and determine the high performance time the next time we need it
                uFrequency.QuadPart = 0;
                uCurrentTime = uSystemTime;
            }
    
            FILETIME ftCurrent;
            ftCurrent.dwLowDateTime  = uCurrentTime.LowPart;
            ftCurrent.dwHighDateTime = uCurrentTime.HighPart;
            FileTimeToSystemTime(&ftCurrent, pst);
        }
    }
    

    【讨论】:

      【解决方案6】:

      GetSystemTimeAsFileTime 为绝对时间提供了所有 Win32 函数的最佳精度。 Joel Clark 建议的 QPF/QPC 将提供更好的相对时间。

      【讨论】:

      • 没有比 GetSystemTimeAsFileTime 更好的东西了吗? get-system-time 函数的精度为 10 到 15 毫秒。
      • 那是准确度,不是精确度。调用timeBeginPeriod(1); 会将精度设置为 1 毫秒。
      • 好的,那就是准确性(我总是将这两者混合,因为在我的语言中只有一个词代表两者)。有没有办法以 1 毫秒的精度在 Windows 中获取系统时间?我正在使用 timeBeginPeriod(1),但仍以 10-15 毫秒的精度返回时间。
      【解决方案7】:

      既然大家都来这里是为了快速sn-ps而不是无聊的解释,我会写一个:

      FILETIME t;
      GetSystemTimeAsFileTime(&t); // unusable as is
      
      ULARGE_INTEGER i;
      i.LowPart = t.dwLowDateTime;
      i.HighPart = t.dwHighDateTime;
      
      int64_t ticks_since_1601 = i.QuadPart; // now usable
      int64_t us_since_1601   = (i.QuadPart * 1e-1);
      int64_t ms_since_1601   = (i.QuadPart * 1e-4);
      int64_t sec_since_1601  = (i.QuadPart * 1e-7);
      
      // unix epoch
      int64_t unix_us  = (i.QuadPart * 1e-1) - 11644473600LL * 1000000;
      int64_t unix_ms  = (i.QuadPart * 1e-4) - 11644473600LL * 1000;
      double  unix_sec = (i.QuadPart * 1e-7) - 11644473600LL;
      
      // i.QuadPart is # of 100ns ticks since 1601-01-01T00:00:00Z
      // difference to Unix Epoch is 11644473600 seconds (attention to units!)
      

      不知道基于性能计数器的答案是如何漂移的,伙计们,不要犯滑点错误。

      【讨论】:

        【解决方案8】:

        QueryPerformanceCounter() 是为细粒度的计时器分辨率而构建的。

        这是系统必须提供的最高分辨率计时器,您可以在应用程序代码中使用它来识别性能瓶颈

        这是 C# 开发人员的简单实现:

            [DllImport("kernel32.dll")]
            extern static short QueryPerformanceCounter(ref long x);
            [DllImport("kernel32.dll")]
            extern static short QueryPerformanceFrequency(ref long x);
            private long m_endTime;
            private long m_startTime;
            private long m_frequency;
        
            public Form1()
            {
                InitializeComponent();
            }
            public void Begin()
            {
                QueryPerformanceCounter(ref m_startTime);
            }
            public void End()
            {
                QueryPerformanceCounter(ref m_endTime);
            }
        
            private void button1_Click(object sender, EventArgs e)
            {
                QueryPerformanceFrequency(ref m_frequency);
                Begin();
                for (long i = 0; i < 1000; i++) ;
                End();
                MessageBox.Show((m_endTime - m_startTime).ToString());
            }
        

        如果您是 C/C++ 开发人员,请看这里: http://support.microsoft.com/kb/815668

        【讨论】:

        • 来自支持文章“•API调用在某些情况下可能会失败。检查返回值,然后调整您的应用程序代码以确保您收到有效的结果。” - 是的。这些函数有许多有据可查的问题,包括 CPU 速度因省电模式而变化的问题,以及跨内核的不同步 TSC 寄存器。
        【解决方案9】:

        嗯,这个很老了,但是在 Windows C 库 _ftime 中还有另一个有用的函数,它返回一个结构体,其本地时间为 time_t,毫秒、时区和夏令时标志。

        【讨论】:

          【解决方案10】:

          在我对"Microsecond resolution timestamps on Windows" 问题的回答中,我已经写了一些关于如何以适合大多数目的的方式快速轻松地实现此功能的信息。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-11-12
            • 2020-02-06
            • 2011-01-25
            • 2014-07-21
            相关资源
            最近更新 更多