【问题标题】:Log with timestamps that have millisecond accuracy & resolution in Windows C++在 Windows C++ 中记录具有毫秒精度和分辨率的时间戳
【发布时间】:2010-12-31 12:51:17
【问题描述】:

我知道,对于计时的准确性,timeGetTime、timeBeginPeriod、QueryPerformanceCounter 等函数非常棒,提供了良好的分辨率和准确性,但仅基于自启动后的时间,与时钟时间没有直接链接。

但是,我不想为这样的事件计时。我希望能够生成一个准确的时间戳(本地时间),以便我可以在日志文件中显示它,例如 31-12-2010 12:38:35.345,每个条目。 (我需要毫秒精度)

标准的 Windows 时间函数(如 GetLocalTime)虽然提供毫秒值,但没有毫秒分辨率,具体取决于运行的操作系统。我使用的是 XP,所以我不能指望比 15 毫秒的分辨率更好。

我需要的是一种两全其美的方法,而不会产生大量开销来获得所需的输出。过大的方法/计算意味着记录器会在其运行期间开始占用太多时间。

最好/最简单的方法是什么?

【问题讨论】:

    标签: c++ windows timestamp resolution


    【解决方案1】:

    首先,一些功能:

    // ==========================================================================
    #define NOMINMAX
    #define _AFXDLL
    #include "afxwin.h"               // TRACE
    #include "windows.h"              // ULARGE_INTEGER
    #include "mmSystem.h"             // timeGetTime
    #pragma comment(lib, "Winmm.lib") // timeGetTime
    
    // ==========================================================================
    // convert FILETIME to ULONGLONG
    // (casting won't work on 64-bit platforms, due to alignment of FILETIME members)
    inline void ToULL(const FILETIME& ft, ULONGLONG& uft)
    {
        ULARGE_INTEGER uli;
        uli.LowPart = ft.dwLowDateTime ;
        uli.HighPart= ft.dwHighDateTime;
        uft= uli.QuadPart;
    }
    
    // --------------------------------------------------------------------------
    // convert ULONGLONG to FILETIME
    // (casting won't work on 64-bit platforms, due to alignment of FILETIME members)
    inline void ToFILETIME(const ULONGLONG& uft, FILETIME& ft)
    {
        ULARGE_INTEGER uli;
        uli.QuadPart= uft;
        ft.dwLowDateTime = uli.LowPart ;
        ft.dwHighDateTime= uli.HighPart;
    }
    
    // --------------------------------------------------------------------------
    // ULONGLONG version for GetSystemTimeAsFileTime
    inline void GetSystemTimeAsULL(ULONGLONG& uft)
    {
        FILETIME ft;
        ::GetSystemTimeAsFileTime(&ft);
        ToULL(ft, uft);
    }
    
    // --------------------------------------------------------------------------
    // convert ULONGLONG to time-components
    bool ULLToSystemTime(const ULONGLONG nTime        ,  // [i]
                         WORD&           nYear        ,  // [o] 1601 - 30827
                         WORD&           nMonth       ,  // [o] 1 -    12
                         WORD&           nDay         ,  // [o] 1 -    31
                         WORD&           nHour        ,  // [o] 0 -    23
                         WORD&           nMinute      ,  // [o] 0 -    59
                         WORD&           nSecond      ,  // [o] 0 -    59
                         WORD&           nMilliseconds ) // [o] 0 -   999
    {
        SYSTEMTIME sysTime;
        FILETIME   ft     ;
        ToFILETIME(nTime, ft);
    
        // the wDayOfWeek member of the SYSTEMTIME structure is ignored
        if (0 == ::FileTimeToSystemTime(&ft, &sysTime))
            return false;
    
        nYear        = sysTime.wYear        ;
        nMonth       = sysTime.wMonth       ;
        nDay         = sysTime.wDay         ;
        nHour        = sysTime.wHour        ;
        nMinute      = sysTime.wMinute      ;
        nSecond      = sysTime.wSecond      ;
        nMilliseconds= sysTime.wMilliseconds;
        return true;
    }
    
    // --------------------------------------------------------------------------
    void TraceTime(const ULONGLONG nTime) // [i]
    {
        WORD nYear,nMonth,nDay,nHour,nMinute,nSecond,nMilliseconds;
        ULLToSystemTime(nTime, nYear,nMonth,nDay,nHour,nMinute,nSecond,nMilliseconds);
        TRACE("Time: %02u-%02u-%04u %02u:%02u:%02u.%03u\n", nDay,nMonth,nYear,nHour,nMinute,nSecond,nMilliseconds);
    }
    

    现在,如何使用:

    ULONGLONG u0,u1;
    ::GetSystemTimeAsULL(u0);
    
    // wait for tick (each 14.4mS)
    do
    {
        ::GetSystemTimeAsULL(u1);
    }
    while (u0==u1);
    
    DWORD d1= ::timeGetTime();
    
    // d1 and u1 are now synchronized
    
    // ... do some work
    
    // get current time:
    ULONGLONG u2= u1+(::timeGetTime() - d1)*10000; // mSec --> HectoNanoSec
    
    TraceTime(u2);
    

    请注意,您应该在 2-3 分钟内重新同步 d1 和 u1 一次,以保持准确性。 实际上,您可以通过测量时钟之间的漂移来找到最佳的重新同步间隔。

    【讨论】:

    • 有趣的是关于 64 位转换的观点,因为我不知道这一点。意味着我当前的解决方案是 32 位锁定的,因为我正在使用 (__int64*)&ft
    • 虽然这不是我最终得到的结果,但它肯定最接近我使用的逻辑(尽管在 32 位锁定版本中)。不过这个答案可能更好!
    【解决方案2】:

    您可以尝试GetSystemAsFileTime,它以 100 纳秒为单位表示时间。由 Windows 决定填充它的实际分辨率。

    另一种方法是查询本地时间并使用QueryPerformanceCounter 在应用程序启动时锁定偏移与时间的比率并将其应用于后续计数器读数。

    【讨论】:

    • 但是,我的难点在于处理来自timeGetTime的毫秒值,以及GetLocalTime的SYSTEMTIME结构。我可以在启动时得到两者,在日志时间计算 timeGetTime 的差异,但是如何在干净和快速的过程中将该偏移量应用到 GetLocalTime 结构是我的问题。扩展它以使用 QueryPerformanceCounter 会添加另一个变量(QueryPerformanceFrequency),从而进一步减慢它。我需要一种“快速”算法,而不是会占用系统的算法。
    • @Psychic 担心千兆赫处理器上的基本数学性能有点愚蠢,当您输出到一个速度慢很多因素的磁盘时。
    • 输出主要是屏幕。文件是可选的,数据库输出也是如此。每秒也会有很多条目,因此需要毫秒精度,因此在这种情况下非常需要优化方法。我们可能有千兆赫的计算机,但日志系统并不能全部完成。我有相当多的其他模块占用了更多的资源,这些资源不能被低效的记录器减慢。
    • 请注意,返回的 FILETIME 通常只精确到 1 ms 而不是 100ns 间隔。但鉴于您想要毫秒精度,这不是问题。在较旧的 Windows 版本 (2000) 上,您可能需要调用 NtSetTimerResolution 以获得所需的准确性。
    • 另请注意,GetSystemAsFileTime 是在 Windows 系统上获取 UTC 时间戳的最快方法(只需要几个时钟周期)!
    【解决方案3】:

    我曾经为此编写过代码,但最终放弃了它并接受了操作系统时间分辨率。该代码在紧密循环中调用了 GetLocalTime 和 QueryPerformanceCounter 大约 50 次。当我检测到 GetLocalTime 的结果被一个分辨率刻度改变时,我假设相应 QueryPerformanceCounter 的结果足够接近该刻度的开头。这样我得到一个精确的时间偏移。从那时起,我调用 QueryPerformanceCounter 并使用时间偏移量进行数学运算,以获得精确的本地时间。

    最后我推断,这一切都不值得这么麻烦,而且我真的不需要那么精确。

    【讨论】:

      【解决方案4】:

      我要做的是在启动时获取系统时间和 queryperfcounter。你知道有一个相当准确的起点。

      然后在您的日志系统中调用 QueryPerformanceCounter(),减去 start-QPC 值,除以 QPF 得到秒数(以双精度存储),将此值与启动时的系统时间相结合并打印。

      【讨论】:

        【解决方案5】:
        typedef struct _TIME_FIELDS {
        
        USHORT Year; USHORT Month; USHORT Day; USHORT Hour; USHORT Minute; USHORT Second; USHORT Milliseconds; USHORT Weekday;
        
        } TIME_FIELDS;
        
        typedef signed __int64      INT64;
        typedef unsigned __int64    UINT64;
        
        
        INT64 get_current_time ()
        {
            ULONG secs;
            LARGE_INTEGER tm;
        
            KeQuerySystemTime (&tm);
        
            RtlTimeToSecondsSince1970(&tm, &secs);
        
            /* NOTE: tm is in 100s of nano seconds */
            return (secs * 1000 + (tm.QuadPart/10000)%1000);
        }
        
        LARGE_INTEGER get_system_time ()
        {
            LARGE_INTEGER tm;
            INT64 time = get_current_time ();
        
            RtlSecondsSince1970ToTime (time, &tm);
        
            /* NOTE: tm is in 100s of nano seconds */
            tm.QuadPart += (QTIME_MSECS(time)%1000)*10000;
            return tm;
        
        }
        
        int log_current_time ()
        {
            static char* month_names[] =
            {
            "Jan",
            "Feb",
            "Mar",
            "Apr",
            "May",
            "Jun",
            "Jul",
            "Aug",
            "Sep",
            "Oct",
            "Nov",
            "Dec"
            };
        
            LARGE_INTEGER tm, lcl_time;
            TIME_FIELDS fields;
        
            tm = get_system_time ();
        
           ExSystemTimeToLocalTime (&tm, &lcl_time);
        
           RtlTimeToTimeFields(&tm, &fields);
        
            printf ("%s %02d %04d:%02d:%02d:%02d:%02d", month_names[fields.Month - 1],
                fields.Day, fields.Year, fields.Hour, fields.Minute, fields.Second,
                fileds.Milliseconds);
        }
        

        请注意,这段代码使用了两个未记录的东西,TIME_FILEDS 结构和 RtlTimeToTimeFields 函数。我在我的代码中使用了类似的实现,它适用于所有当前的 WIN NT 风格。但是,使用它不能保证在下一个 WIN NT 版本中可以移植

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-03-20
          • 2020-02-06
          • 2011-01-25
          • 1970-01-01
          • 1970-01-01
          • 2020-03-18
          • 1970-01-01
          相关资源
          最近更新 更多