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

25

如何以毫秒分辨率获取Windows系统时间?

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


相关:获取高分辨率时间戳(MSDN) - Matt Johnson-Pint
答案不就是 GetSystemTime 吗? - user1196549
10个回答

21

1
我不需要计时器,我需要带有毫秒精度的系统时间。 - axilmar
3
你读了这篇文章吗?它就是关于这个的!这个人做了你所要求的并展示了如何实现,包括代码。 - Kevin Gale
我点了赞...但是我无法让那个网站上的代码工作。它经常会让我的电脑死机。有时候它能够正常运行,而且在繁忙的CPU上非常准确。我仍在查看代码,看看为什么它不能工作。我的设置是Win7 Pro 64位,使用Visual Studio 2005和C++ Builder XE。如果有人感兴趣,我将代码移植到了C++ Builder。这可能会成为一个很好的开源项目 ;) - Eric
这将是一个很好的开源项目,因为这是一个普遍存在的问题。大多数人不需要代码所追求的极端精度,但如果它被打包并且易于使用,我相信即使对他们而言有些过度了,人们也会使用它。 - Kevin Gale
1
从链接的文章中可以看出,“代码并不打算在任何可用系统上直接使用,因为可能会出现问题……”值得注意的是,文章甚至没有提到一个严重的问题:性能计数器经常在多个核心/ CPU之间无法正确同步(微软将其归咎于硬件抽象层(HAL)),因此在一个线程上进行的校准可能不会导致另一个线程上的正确结果。在我对不同系统的测量中,TSC值的错误同步可能达到几秒钟。 - Tony Delroy
2
链接对我来说已经失效了。http://web.archive.org/web/20121018065403/http://msdn.microsoft.com/en-us/magazine/cc163996.aspx - JDong

13

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

  • 它返回一个结构,能够以100纳秒分辨率存储时间。
  • 它保留在协调世界时(UTC)。

FILETIME结构记录了自1600年1月1日以来的100纳秒间隔数;这意味着其分辨率受到100纳秒的限制。

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

enter image description here

一个自1600年1月1日以来的100纳秒滴答声的64位数字有点不方便。 Windows提供了一个方便的帮助函数FileTimeToSystemTime,可以将这个64位整数解码成有用的部分:

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

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

现在我们有一种方法可以从FILETIME转换为SYSTEMTIME

enter image description here

我们可以编写函数以将当前系统时间作为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提供了一种将FILETIME(UTC时间)转换为本地时间的方法:FileTimeToLocalFileTime

输入图像描述

你可以编写一个返回本地时间的FILETIME的函数:

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

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

   return ftNowLocal;
}

这里输入图像描述

假设你想把本地的FILETIME解码成SYSTEMTIME。那不是问题,你可以再次使用FileTimeToSystemTime

这里输入图像描述

幸运的是,Windows已经提供了一个函数可以返回这个值:

这里输入图像描述

精度

还有另一种考虑。在Windows 8之前,时钟的分辨率大约为15ms。在Windows 8中,他们将时钟改进到100ns(与FILETIME的分辨率相匹配)。

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

这意味着我们应该始终优先选择新值:

这里输入图像描述

你要求时间

你要求时间;但你有一些选择。

时区:

  • UTC (系统本地)
  • 本地时区

格式:

  • FILETIME (系统本地, 100ns 分辨率)
  • SYTEMTIME (已解码, 1ms 分辨率)

总结

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

非常好的解释。谢谢。 - undefined

12

这是对上述评论的详细阐述,解释了一些原因。

首先,GetSystemTime* 调用是提供系统时间的唯一 Win32 API。由于大多数应用程序不需要维护更高分辨率所需的开销,该时间具有相当粗糙的精度。时间(可能)存储为毫秒的 64 位计数。调用 timeGetTime 获取低 32 位。调用 GetSystemTime 等请求 Windows 返回此毫秒时间,经过转换为天等并包括系统启动时间。

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

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

针对需要更高时间分辨率的情况,系统时间并不会保持如同大本钟秒针一般的精确度。可以使用 QueryPerformanceCounter (QPC) 或者CPU的时钟周期数 (rdtsc) 来提供系统时间间隔的分辨率。在Kevin引用的MSDN杂志文章中采用了这种方式。尽管这些方法可能存在漂移 (例如由于频率缩放等原因),因此需要将它们与系统时间同步。


我喜欢你的回答。如果Windows很难以1毫秒的周期运行,那么Linux怎么能做到呢? - yan bellavance
1
我不知道这两个操作系统在1毫秒周期下的表现如何,但它们都可以做到。在任何软件设计中,都必须进行权衡。代码应该设计为1毫秒周期还是16毫秒(具体为15.625毫秒)?即使如此,某些设计点也会执行得更好。 - Brian

8

GetTickCount 不足以满足您的需求。

请查看 QueryPerformanceFrequency / QueryPerformanceCounter。唯一需要注意的是 CPU 频率变化,因此请做好研究。


QPC不是我想要的。我想要毫秒精度的系统时间,而不是计时器。 - axilmar
1
省电(即使是空闲状态下的省电)和 Turbo Boost 对 QPF 造成了很大的影响。它曾经非常有效,但对于现代 CPU 来说已经不再适用了。 - Ben Voigt
1
这里唯一需要注意的是CPU缩放并不是唯一的问题。这些函数无法处理硬件抽象层未在核心/ CPU之间同步TSC值的系统:这可能会导致几秒钟的差异。当一个样本在一个核心上采集,然后与在另一个核心上采集的样本进行比较时,它会产生影响。有一些解决多核使用的方法,但我没有看到任何打包在公共可用库中的方法。您可以将线程绑定到单个核心,但有时这比计时有用更具破坏性。 - Tony Delroy
QPF在一些旧的多核设计和损坏的BIOS上无法正常工作。总体而言,QPF表现非常出色。从Vista开始,主板提供了特殊的高精度计数器用于QPC。 - Eelke

4

自Windows 8起,Microsoft引入了新的API命令GetSystemTimePreciseAsFileTime

不幸的是,如果您创建的软件还必须在旧操作系统上运行,则无法使用该命令。

我的当前解决方案如下,但请注意:确定的时间不是精确的,它只接近于实际时间。结果应始终小于或等于实际时间,但具有固定误差(除非计算机进入待机状态)。结果具有毫秒分辨率。对于我的目的来说,这已经足够精确。

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);
    }
}

2

GetSystemTimeAsFileTime 是Win32中用于绝对时间的精度最高的函数。如Joel Clark所建议的,QPF/QPC将提供更好的相对时间。


2
难道没有比GetSystemTimeAsFileTime更好的选择吗?get-system-time函数的精度只有10到15毫秒。 - axilmar
这是准确性,而不是精度。调用 timeBeginPeriod(1); 将把准确性设置为1毫秒。 - Ben Voigt
3
好的,那就是准确度的问题了(因为在我的语言中只有一个词代表这两个概念,所以我经常混淆它们)。有没有一种方法可以在 Windows 中以 1 毫秒的准确度获取系统时间? 我正在使用 timeBeginPeriod(1),但是返回的时间仍然只有10-15毫秒的准确度。 - axilmar

2

既然我们都来这里寻找快速代码片段而不是无聊的解释,那我就写一个:

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!)

不知道为什么漂移性能计数器的答案提高了,大家不要出现滑动错误。


0

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++开发人员,请看这里:如何使用{{link1:QueryPerformanceCounter函数在Visual C++中计时代码}}。

1
从支持文章中:“•在某些情况下,API调用可能会失败。检查返回值,然后调整应用程序代码以确保您收到有效的结果。” - 是啊。这些函数有许多经过充分记录的问题,包括由于节能模式而导致CPU速度变化以及跨核心不同步的TSC寄存器等问题。 - Tony Delroy

0

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


0
在C11及以上(或C++17及以上)中,您可以使用{{link1:timespec_get()}}以便携的方式获取更高精度的时间。
#include <stdio.h>
#include <time.h>
 
int main(void)
{
    struct timespec ts;
    timespec_get(&ts, TIME_UTC);
    char buff[100];
    strftime(buff, sizeof buff, "%D %T", gmtime(&ts.tv_sec));
    printf("Current time: %s.%09ld UTC\n", buff, ts.tv_nsec);
}

如果您正在使用 C++,那么自从 C++11 以来,您可以在新的 <chrono> 头文件中使用 std::chrono::high_resolution_clockstd::chrono::system_clock(挂钟)或 std::chrono::steady_clock(单调时钟),不再需要使用特定于 Windows 的 API。
auto start1 = std::chrono::high_resolution_clock::now();
auto start2 = std::chrono::system_clock::now();
auto start3 = std::chrono::steady_clock::now();
// do some work
auto end1 = std::chrono::high_resolution_clock::now();
auto end2 = std::chrono::system_clock::now();
auto end3 = std::chrono::steady_clock::now();

std::chrono::duration<long long, std::milli> diff1 = end1 - start1;
std::chrono::duration<double, std::milli>    diff2 = end2 - start2;
auto diff3 = std::chrono::duration_cast<std::chrono::milliseconds>(end3 - start3);

std::cout << diff.count() << ' ' << diff2.count() << ' ' << diff3.count() << '\n';

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接