将clock_gettime移植到Windows

29

我在qnx momemntics上运行以下代码。

#define BILLION 1000000000L;

struct timespec start_time;
struct timespec stop_time; 

void start MyTestFunc() {
    //Initialize the Test Start time
     clock_gettime(CLOCK_REALTIME,&start_time)
    // ... additonal code.

    cout << "The exectuion time of func "<< calculateExecutionTime();
}


double calculateExecutionTime ()
{

    clock_gettime(CLOCK_REALTIME,&stop_time);

    double dSeconds = (stop_time.tv_sec - start_time.tv_sec);

    double dNanoSeconds = (double)( stop_time.tv_nsec - start_time.tv_nsec ) / BILLION;

    return dSeconds + dNanoSeconds;
}

现在我想将上述代码移植到Windows平台。请问有人可以提供示例代码吗?

谢谢!


1
请在https://dev59.com/MXVC5IYBdhLWcg3wfxQ8上检查一些选项。 - pmg
6个回答

42
您可以按照以下方式在Windows上实现clock_gettime()替代功能:
LARGE_INTEGER
getFILETIMEoffset()
{
    SYSTEMTIME s;
    FILETIME f;
    LARGE_INTEGER t;

    s.wYear = 1970;
    s.wMonth = 1;
    s.wDay = 1;
    s.wHour = 0;
    s.wMinute = 0;
    s.wSecond = 0;
    s.wMilliseconds = 0;
    SystemTimeToFileTime(&s, &f);
    t.QuadPart = f.dwHighDateTime;
    t.QuadPart <<= 32;
    t.QuadPart |= f.dwLowDateTime;
    return (t);
}

int
clock_gettime(int X, struct timeval *tv)
{
    LARGE_INTEGER           t;
    FILETIME            f;
    double                  microseconds;
    static LARGE_INTEGER    offset;
    static double           frequencyToMicroseconds;
    static int              initialized = 0;
    static BOOL             usePerformanceCounter = 0;

    if (!initialized) {
        LARGE_INTEGER performanceFrequency;
        initialized = 1;
        usePerformanceCounter = QueryPerformanceFrequency(&performanceFrequency);
        if (usePerformanceCounter) {
            QueryPerformanceCounter(&offset);
            frequencyToMicroseconds = (double)performanceFrequency.QuadPart / 1000000.;
        } else {
            offset = getFILETIMEoffset();
            frequencyToMicroseconds = 10.;
        }
    }
    if (usePerformanceCounter) QueryPerformanceCounter(&t);
    else {
        GetSystemTimeAsFileTime(&f);
        t.QuadPart = f.dwHighDateTime;
        t.QuadPart <<= 32;
        t.QuadPart |= f.dwLowDateTime;
    }

    t.QuadPart -= offset.QuadPart;
    microseconds = (double)t.QuadPart / frequencyToMicroseconds;
    t.QuadPart = microseconds;
    tv->tv_sec = t.QuadPart / 1000000;
    tv->tv_usec = t.QuadPart % 1000000;
    return (0);
}

1
你已经准备好这段代码了,是吗?我喜欢直接劫持 clock_gettime 函数的想法。 - xtofl
3
谢谢你提供的代码片段。我在尝试使用mingw交叉编译你的代码时,遇到了一个错误:`无法将'timespec*'转换为' timeval ',用于参数'2',以便于'int clock_gettime(int, timeval)'。请问我错过了什么? - a1337q
显然,Windows的timeval结构不超过微秒,而Linux的timeval则使用纳秒。在Windows上是否可能支持纳秒? - Salepate
4
这段代码显示与程序启动时间相关的时间,而不是真实的时钟时间。它展示了重要函数,但不应替代clock_gettime函数!!! - Asain Kujovic
2
X的值是多少? - mmain
显示剩余3条评论

20

避免PerformanceCounter混乱的方法,简单代码:

struct timespec { long tv_sec; long tv_nsec; };    //header part
int clock_gettime(int, struct timespec *spec)      //C-file part
{  __int64 wintime; GetSystemTimeAsFileTime((FILETIME*)&wintime);
   wintime      -=116444736000000000i64;  //1jan1601 to 1jan1970
   spec->tv_sec  =wintime / 10000000i64;           //seconds
   spec->tv_nsec =wintime % 10000000i64 *100;      //nano-seconds
   return 0;
}

...是一种快速、可靠和正确的移植解决方案,具有卓越的100ns精度(1ms / 10000)。

而基于QPC的解决方案,在某些硬件上可能会有更好的精度。

struct timespec { long tv_sec; long tv_nsec; };   //header part
#define exp7           10000000i64     //1E+7     //C-file part
#define exp9         1000000000i64     //1E+9
#define w2ux 116444736000000000i64     //1.jan1601 to 1.jan1970
void unix_time(struct timespec *spec)
{  __int64 wintime; GetSystemTimeAsFileTime((FILETIME*)&wintime); 
   wintime -=w2ux;  spec->tv_sec  =wintime / exp7;                 
                    spec->tv_nsec =wintime % exp7 *100;
}
int clock_gettime(int, timespec *spec)
{  static  struct timespec startspec; static double ticks2nano;
   static __int64 startticks, tps =0;    __int64 tmp, curticks;
   QueryPerformanceFrequency((LARGE_INTEGER*)&tmp); //some strange system can
   if (tps !=tmp) { tps =tmp; //init ~~ONCE         //possibly change freq ?
                    QueryPerformanceCounter((LARGE_INTEGER*)&startticks);
                    unix_time(&startspec); ticks2nano =(double)exp9 / tps; }
   QueryPerformanceCounter((LARGE_INTEGER*)&curticks); curticks -=startticks;
   spec->tv_sec  =startspec.tv_sec   +         (curticks / tps);
   spec->tv_nsec =startspec.tv_nsec  + (double)(curticks % tps) * ticks2nano;
         if (!(spec->tv_nsec < exp9)) { spec->tv_sec++; spec->tv_nsec -=exp9; }
   return 0;
}

6
FILETIME结构的文档链接中提到:"不要将指向FILETIME结构的指针转换为ULARGE_INTEGER * 或 __int64 *值,因为这可能会在64位Windows上导致对齐故障"。 - Spencer
你真是个救星。这是我Windows机器和一个面向Linux的C99库之间唯一的障碍 :) - Violet Giraffe
2
@Spencer,它是将&__int64转换为FILETIME*,而不是相反的方向,因此它始终对齐到8个字节。 - Asain Kujovic
1
@RobinF。它仅获取当前的unix_time一次...并在下一个函数调用中通过QPC计算差异,使用保存的静态变量。 - Asain Kujovic
1
具有令人印象深刻的100ns精度(1ms / 10000)的分辨率。这是分辨率,而不是精度。GetSystemTimeAsFileTime的精度非常糟糕,约为15毫秒(是的,毫秒)。 - ScumCoder
显示剩余4条评论

7
我需要单调性和实时性。
对于单调性,我只需使用性能计数器,因为以墙钟基线为基础的计数是没有意义的。
#define MS_PER_SEC      1000ULL     // MS = milliseconds
#define US_PER_MS       1000ULL     // US = microseconds
#define HNS_PER_US      10ULL       // HNS = hundred-nanoseconds (e.g., 1 hns = 100 ns)
#define NS_PER_US       1000ULL

#define HNS_PER_SEC     (MS_PER_SEC * US_PER_MS * HNS_PER_US)
#define NS_PER_HNS      (100ULL)    // NS = nanoseconds
#define NS_PER_SEC      (MS_PER_SEC * US_PER_MS * NS_PER_US)

int clock_gettime_monotonic(struct timespec *tv)
{
    static LARGE_INTEGER ticksPerSec;
    LARGE_INTEGER ticks;

    if (!ticksPerSec.QuadPart) {
        QueryPerformanceFrequency(&ticksPerSec);
        if (!ticksPerSec.QuadPart) {
            errno = ENOTSUP;
            return -1;
        }
    }

    QueryPerformanceCounter(&ticks);

    tv->tv_sec = (long)(ticks.QuadPart / ticksPerSec.QuadPart);
    tv->tv_nsec = (long)(((ticks.QuadPart % ticksPerSec.QuadPart) * NS_PER_SEC) / ticksPerSec.QuadPart);

    return 0;
}

并且墙上时钟是基于格林威治标准时间(GMT)的,不像诱人而类似的 _ftime() 函数。

int clock_gettime_realtime(struct timespec *tv)
{
    FILETIME ft;
    ULARGE_INTEGER hnsTime;

    GetSystemTimePreciseAsFileTime(&ft);

    hnsTime.LowPart = ft.dwLowDateTime;
    hnsTime.HighPart = ft.dwHighDateTime;

    // To get POSIX Epoch as baseline, subtract the number of hns intervals from Jan 1, 1601 to Jan 1, 1970.
    hnsTime.QuadPart -= (11644473600ULL * HNS_PER_SEC);

    // modulus by hns intervals per second first, then convert to ns, as not to lose resolution
    tv->tv_nsec = (long) ((hnsTime.QuadPart % HNS_PER_SEC) * NS_PER_HNS);
    tv->tv_sec = (long) (hnsTime.QuadPart / HNS_PER_SEC);

    return 0;
}

然后这个函数兼容POSIX...请参考POSIX头文件的typedef和宏。

int clock_gettime(clockid_t type, struct timespec *tp)
{
    if (type == CLOCK_MONOTONIC)
    {
        return clock_gettime_monotonic(tp);
    }
    else if (type == CLOCK_REALTIME)
    {
        return clock_gettime_realtime(tp);
    }

    errno = ENOTSUP;
    return -1;
}

目前为止,这是整个帖子中唯一正确的答案。 - ScumCoder
我被提醒并检查了我的答案。现在有GetSystemTimePreciseAsFileTime()可用于clock_gettime_realtime。理论上,clock_gettime_monotonic()应该使用long double以支持完整的64位性能计数器范围而不会失去分辨率。实际上,它使用基于足够未使用位的double来工作。 - jws
1
只是为了澄清:GetSystemTimePreciseAsFileTime 在 Windows 8 之前不可用,如果您使用 MSVC,则 long double 只是 double 的同义词。 - ScumCoder
哇,关于“long double”又有一个争议性的决定...我会摆脱“double”。 - jws

4
多年来,mingw-w64一直拥有完整功能和经过充分测试的clock_gettime()实现。要使用此功能,您需要使用带有mingw64/msys2工具链的环境,并在Windows上使用头文件#include <time.h>。如果您正在编写可在Linux和Windows之间移植的代码库,并且在<time.h>中找不到clock_gettime() 3,建议您尝试使用#include <pthread_time.h>进行编译,或者链接-lrt
另请参见问题 60020968,适用于Windows版本;以及33846055538609,适用于您的Linux版本。

1
目前我有点新手,我发现我需要在mingw中添加CFLAGS=-pthread,这让我感到困惑,因为我一直以为需要<s>LDLIBS=-lpthread</s>,但实际上并不需要。 - ThorSummoner
1
哦,抱歉,我现在意识到它在time.h中定义,所以你根本不需要去处理pthread! - Clark Thomborson
1
这个似乎相关?https://github.com/Alexpux/mingw-w64/blob/6172d2f87e426462f89785753f1be553bd10fe2f/mingw-w64-headers/crt/time.h#L295 time.h头文件声明了获取clock_gettime函数的一些额外条件。 - ThorSummoner
好的,确实在Linux构建中很难找到clock_gettime(),除非它被标记为POSIX编译。这有点偏题,因为问题是关于clock_gettime()在Windows上的移植,但既然你想编写可移植代码,为什么不想在Windows平台上使用clock_gettime()接口呢?除1e6进行除法运算很烦人,如果你需要高分辨率计时,这可能会变得不可接受慢。我刚才在StackOverflow上进一步搜索发现其他人在Linux构建中也遇到了访问clock_gettime()的问题。 - Clark Thomborson

3

我使用QueryPerformanceCounter()改进了clock_gettime()的版本。

#define BILLION                             (1E9)

static BOOL g_first_time = 1;
static LARGE_INTEGER g_counts_per_sec;

int clock_gettime(int dummy, struct timespec *ct)
{
    LARGE_INTEGER count;

    if (g_first_time)
    {
        g_first_time = 0;

        if (0 == QueryPerformanceFrequency(&g_counts_per_sec))
        {
            g_counts_per_sec.QuadPart = 0;
        }
    }

    if ((NULL == ct) || (g_counts_per_sec.QuadPart <= 0) ||
            (0 == QueryPerformanceCounter(&count)))
    {
        return -1;
    }

    ct->tv_sec = count.QuadPart / g_counts_per_sec.QuadPart;
    ct->tv_nsec = ((count.QuadPart % g_counts_per_sec.QuadPart) * BILLION) / g_counts_per_sec.QuadPart;

    return 0;
}

我认为我的版本比目前被接受的使用QueryPerformanceCounter()的答案更好,因为:

  1. 更加健壮-检查函数的返回值,还检查通过引用变量返回的值。
  2. 更加健壮-检查输入参数的有效性。
  3. 更加简洁-使用尽可能少的变量(3 vs 7)。
  4. 更加简洁-避免涉及GetSystemTimeAsFileTime()的代码路径,因为在运行Windows XP或更高版本的系统上,QueryPerformanceFrequency()QueryPerformanceCounter()保证可以工作。

这段代码无法通过MSVC2015的编译器编译。你是否忘记添加所有的头文件?如果可能的话,我想尝试一下这个代码。 - kayleeFrye_onDeck
1
我在代码片段中没有显示包含文件,但它确实需要此处提到的头文件 - https://msdn.microsoft.com/en-us/library/windows/desktop/ms644904(v=vs.85).aspx。 - work.bin
太棒了!感谢更新,work.bin!我会去看看 ^_^ - kayleeFrye_onDeck
1
很酷,添加 Windows.hstruct timespec { long tv_sec; long tv_nsec; }; 解除了我的阻塞,谢谢! - kayleeFrye_onDeck
1
我正在使用Visual C++ 2013,@kayleeFrye_onDeck的解决方案非常有帮助。 ;) - littlecodefarmer758

0

你可以使用 timespec_get 实现简单的 clock_gettime。
(timespec_get 函数从 C11 开始提供)

int clock_gettime(int, struct timespec *tv)
{
    return timespec_get(tv, TIME_UTC);
}

...但结果timespec在我的Windows7 64位机器上只有大约10毫秒的分辨率。 :(

这是我编写的clock_gettime版本。

int clock_gettime(int, struct timespec *tv)
{
    static int initialized = 0;
    static LARGE_INTEGER freq, startCount;
    static struct timespec tv_start;
    LARGE_INTEGER curCount;
    time_t sec_part;
    long nsec_part;

    if (!initialized) {
        QueryPerformanceFrequency(&freq);
        QueryPerformanceCounter(&startCount);
        timespec_get(&tv_start, TIME_UTC);
        initialized = 1;
    }

    QueryPerformanceCounter(&curCount);

    curCount.QuadPart -= startCount.QuadPart;
    sec_part = curCount.QuadPart / freq.QuadPart;
    nsec_part = (long)((curCount.QuadPart - (sec_part * freq.QuadPart))
            * 1000000000UL / freq.QuadPart);

    tv->tv_sec = tv_start.tv_sec + sec_part;
    tv->tv_nsec = tv_start.tv_nsec + nsec_part;
    if(tv->tv_nsec >= 1000000000UL) {
        tv->tv_sec += 1;
        tv->tv_nsec -= 1000000000UL;
    }
    return 0;
}

1
timespec_get() 在 VS 2013、Windows 8 中不可用。 - work.bin

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