最近项目升级到VS2017,无法通过调系统时间测试业务问题

1 问题分析

调试发现工程中很多定时器和时间相关业务都是通过 clock函数实现,而 clock微软在VS2015及之后版本做了重新实现:

  • VS2015前,clock随系统时间变化
  • VS2015及之后,clock不随系统时间变化

从Microsoft官方文档:Microsoft C/C++ change history 2003 - 2015,截取的描述:

<time.h>

  • clock

    In previous versions, the clock function was implemented using the Windows API GetSystemTimeAsFileTime. With this implementation, the clock function was sensitive to the system time, and was thus not necessarily monotonic. The clock function has been reimplemented in terms of QueryPerformanceCounter and is now monotonic.

大致是说 clock以前使用 GetSystemTimeAsFileTime实现会对系统时间敏感,后续更改为使用 QueryPerformanceCounter实现

这时去看了下官方的clock实现(题主只有VS2008和VS2017)

VS2008 clock实现:

代码在:代码在:<VS2008安装目录>/VC/crt/src/clock.c

VS2017 clock实现:

代码在:<sdk目录>/ucrt/time/clock.cpp

通过对比,可以看出和官方文档描述的一样,clockQueryPerformanceCounter重新实现了

2 解决方案

自定义与 clock相同功能函数,并替换代码中的 clock调用,有两种实现方案,目前采取方案一

2.1 方案一

使用静态变量特性初始化进程启动时间,每次获取 clock时用当前时间减启动时间

#include <windows.h>
#include <time.h>

/*
 * 获取系统时间
 */
static unsigned __int64 GetCurSystemTime()
{
    FILETIME ct;
    ::GetSystemTimeAsFileTime(&ct);
    return (unsigned __int64)ct.dwLowDateTime + (((unsigned __int64)ct.dwHighDateTime) << 32);
}

// 进程启动时间
static unsigned __int64 g_ui64ProcessStartTime = GetCurSystemTime();

/*
 * clock函数实现
 */
clock_t GetClock()
{
    FILETIME ct;
    ::GetSystemTimeAsFileTime(&ct);
    unsigned __int64 ui64CurTime = (unsigned __int64)ct.dwLowDateTime + (((unsigned __int64)ct.dwHighDateTime) << 32);
    ui64CurTime -= g_ui64ProcessStartTime;
    return (clock_t)(ui64CurTime / 10000);
}

效率:

VS2017测试,CPU i7-4790:

  • GetClock:10w次/1ms
  • 系统提供的clock:10w次/2~3ms

可以看出比系统的 clock更快(其实也正常,看C++ crt代码就知道哪种更快)

2.2 方案二

用VS2008中的源码的实现方法,把初始化进程启动时间放到 crt初始化时获取,其它和方案一一样

#include <windows.h>
#include <time.h>

// 自定义一个段
#pragma section(".CRT$XCTMY",long,read)
typedef int(__cdecl *_PIFV)(void);

static unsigned __int64 g_ui64ProcessStartTime; // 进程启动时间

/*
 * 初始化进程启动时间
 */
int __cdecl InitProcessStartTime()
{
    FILETIME ct;
    ::GetSystemTimeAsFileTime(&ct);
    g_ui64ProcessStartTime = (unsigned __int64)ct.dwLowDateTime + (((unsigned __int64)ct.dwHighDateTime) << 32);

    return 0;
}

// 将`__InitProcessStartTime`函数定义在自定义段中
__declspec(allocate(".CRT$XCTMY")) _PIFV __InitProcessStartTime = InitProcessStartTime;

/*
 * clock函数实现
 */
clock_t GetClock()
{
    FILETIME ct;
    ::GetSystemTimeAsFileTime(&ct);
    unsigned __int64 ui64CurTime = (unsigned __int64)ct.dwLowDateTime + (((unsigned __int64)ct.dwHighDateTime) << 32);
    ui64CurTime -= g_ui64ProcessStartTime;
    return (clock_t)(ui64CurTime / 10000);
}

3 其它的思考

之前在想能否使用 WindowstimeGetTime去实现

timeGetTime:返回Windows启动到当前的时间(毫秒),每49.71天就会再绕回0

https://docs.microsoft.com/en-us/previous-versions/ms713418(v=vs.85)

但有个问题,Windows开启超过49.71天后会溢出,限制比较大

标签: c, c++, vs, virsual studio, windows, vc++

添加新评论