在Mac OS X中,替代clock_gettime的方法是什么?

72

在我通过MacPorts安装必要的库后,在Mac OS X上编译我的程序时,我遇到了这个错误:

In function 'nanotime':
error: 'CLOCK_REALTIME' undeclared (first use in this function)
error: (Each undeclared identifier is reported only once
error: for each function it appears in.)

看起来在 Mac OS X 中没有实现 clock_gettime。是否有其他方式可以获取纳秒级别的epoch时间?不幸的是,gettimeofday 只能获得微秒级别的时间。


1
我的文档说:“所有实现都支持系统范围内的实时时钟,该时钟由CLOCK_REALTIME标识。”你是否包含了#include <time.h> - pmg
4
那并没有帮助。在 Mac OS X 中没有实现 clock_gettime - Delan Azabani
6
我知道。这在我的链接命令行中。我根本没有达到链接阶段。Mac OS X没有 clock_gettime,而Linux有。 - Delan Azabani
2
我为此编写了一个快速包装器:https://gist.github.com/alfwatt/3588c5aa1f7a1ef7a3bb - alfwatt
11
请注意,macOS Sierra 10.12(2016年9月,XCode 8)及更高版本直接支持clock_gettime()函数——正如James Wald在这个答案中所指出的。 - Jonathan Leffler
显示剩余4条评论
13个回答

131

在查看了很多答案、博客和头文件后,我找到了一种可移植的获取当前时间的方法:

#include <time.h>
#include <sys/time.h>

#ifdef __MACH__
#include <mach/clock.h>
#include <mach/mach.h>
#endif



struct timespec ts;

#ifdef __MACH__ // OS X does not have clock_gettime, use clock_get_time
clock_serv_t cclock;
mach_timespec_t mts;
host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
clock_get_time(cclock, &mts);
mach_port_deallocate(mach_task_self(), cclock);
ts.tv_sec = mts.tv_sec;
ts.tv_nsec = mts.tv_nsec;

#else
clock_gettime(CLOCK_REALTIME, &ts);
#endif

或者查看这个代码片段:https://gist.github.com/1087739

希望这可以为某些人节省时间。干杯!


4
主机获取时钟服务(host_get_clock_service)昂贵吗?将其缓存到进程中是否值得?它是否可重复使用?是否线程安全?谢谢 - :) - peterk
1
请包含以下内容:#ifdef __MACH__ #include <mach/clock.h> #include <mach/mach.h> #endif - Nikolay Vyahhi
3
@NikolayVyahhi 是的!我在摘要中有它们。不过既然你没找到,最好将它们添加到答案中。 - jbenet
4
有没有人测试过上述的__MACH__代码运行时间?使用一个已经过充分测试的独立微秒级计时器,我的印象是以上代码的两次调用需要花费约25微秒的时间。 - P Marecki
1
有人知道 @peterk 的问题的答案吗?在我的测试中,似乎缓存可以将性能提高一倍,但我需要为每个线程缓存它,还是可以创建一个并在所有线程之间共享它? - robbie_c
快速记录,我已经测试了mach_absolute_time()在2015年的MacBook Air上运行需要68纳秒。 - William Entriken

35

以上的解决方案都没有回答问题。它们要么不能给出绝对的Unix时间,要么它们的准确性为1微秒。最受欢迎的解决方案是由jbenet提出的,但它很慢(约6000ns),即使它的返回结果表明如此,它也不计算纳秒。下面是针对jbenet和Dmitri B提出的两个解决方案以及我的想法的测试。您可以在不更改代码的情况下运行该代码。

第三个解决方案确实计算纳秒,并且能够快速地给出绝对Unix时间(约90ns)。因此,如果有人发现它有用,请在这里告诉我们:-)。我会坚持使用 Dmitri B的那个(在代码中是解决方案#1),因为它更适合我的需求。

我需要商业质量的替代clock_gettime()来进行pthread_…timed..调用,并发现这个讨论非常有帮助。谢谢大家。

/*
 Ratings of alternatives to clock_gettime() to use with pthread timed waits:
    Solution 1 "gettimeofday":
        Complexity      : simple
        Portability     : POSIX 1
        timespec        : easy to convert from timeval to timespec
        granularity     : 1000 ns,
        call            : 120 ns,
        Rating          : the best.

    Solution 2 "host_get_clock_service, clock_get_time":
        Complexity      : simple (error handling?)
        Portability     : Mac specific (is it always available?)
        timespec        : yes (struct timespec return)
        granularity     : 1000 ns (don't be fooled by timespec format)
        call time       : 6000 ns
        Rating          : the worst.

    Solution 3 "mach_absolute_time + gettimeofday once":
        Complexity      : simple..average (requires initialisation)
        Portability     : Mac specific. Always available
        timespec        : system clock can be converted to timespec without float-math
        granularity     : 1 ns.
        call time       : 90 ns unoptimised.
        Rating          : not bad, but do we really need nanoseconds timeout?

 References:
 - OS X is UNIX System 3 [U03] certified
    http://www.opengroup.org/homepage-items/c987.html

 - UNIX System 3 <--> POSIX 1 <--> IEEE Std 1003.1-1988
    http://en.wikipedia.org/wiki/POSIX
    http://www.unix.org/version3/

 - gettimeofday() is mandatory on U03,
   clock_..() functions are optional on U03,
   clock_..() are part of POSIX Realtime extensions
    http://www.unix.org/version3/inttables.pdf

 - clock_gettime() is not available on MacMini OS X
    (Xcode > Preferences > Downloads > Command Line Tools = Installed)

 - OS X recommends to use gettimeofday to calculate values for timespec
    https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/pthread_cond_timedwait.3.html

 - timeval holds microseconds, timespec - nanoseconds
    http://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html

 - microtime() is used by kernel to implement gettimeofday()
    http://ftp.tw.freebsd.org/pub/branches/7.0-stable/src/sys/kern/kern_time.c

 - mach_absolute_time() is really fast
    http://www.opensource.apple.com/source/Libc/Libc-320.1.3/i386/mach/mach_absolute_time.c

 - Only 9 deciaml digits have meaning when int nanoseconds converted to double seconds
    Tutorial: Performance and Time post uses .12 precision for nanoseconds
    http://www.macresearch.org/tutorial_performance_and_time

 Example:
    Three ways to prepare absolute time 1500 milliseconds in the future to use with pthread timed functions.

 Output, N = 3, stock MacMini, OSX 10.7.5, 2.3GHz i5, 2GB 1333MHz DDR3:
    inittime.tv_sec = 1390659993
    inittime.tv_nsec = 361539000
    initclock = 76672695144136
    get_abs_future_time_0() : 1390659994.861599000
    get_abs_future_time_0() : 1390659994.861599000
    get_abs_future_time_0() : 1390659994.861599000
    get_abs_future_time_1() : 1390659994.861618000
    get_abs_future_time_1() : 1390659994.861634000
    get_abs_future_time_1() : 1390659994.861642000
    get_abs_future_time_2() : 1390659994.861643671
    get_abs_future_time_2() : 1390659994.861643877
    get_abs_future_time_2() : 1390659994.861643972
 */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>       /* gettimeofday */
#include <mach/mach_time.h> /* mach_absolute_time */
#include <mach/mach.h>      /* host_get_clock_service, mach_... */
#include <mach/clock.h>     /* clock_get_time */

#define BILLION 1000000000L
#define MILLION 1000000L

#define NORMALISE_TIMESPEC( ts, uint_milli )            \
    do {                                                \
        ts.tv_sec += uint_milli / 1000u;                \
        ts.tv_nsec += (uint_milli % 1000u) * MILLION;   \
        ts.tv_sec += ts.tv_nsec / BILLION;              \
        ts.tv_nsec = ts.tv_nsec % BILLION;              \
    } while (0)

static mach_timebase_info_data_t timebase = { 0, 0 }; /* numer = 0, denom = 0 */
static struct timespec           inittime = { 0, 0 }; /* nanoseconds since 1-Jan-1970 to init() */
static uint64_t                  initclock;           /* ticks since boot to init() */

void init()
{
    struct timeval  micro;      /* microseconds since 1 Jan 1970 */

    if (mach_timebase_info(&timebase) != 0)
        abort();                            /* very unlikely error */

    if (gettimeofday(&micro, NULL) != 0)
        abort();                            /* very unlikely error */

    initclock = mach_absolute_time();

    inittime.tv_sec = micro.tv_sec;
    inittime.tv_nsec = micro.tv_usec * 1000;
    printf("\tinittime.tv_sec = %ld\n", inittime.tv_sec);
    printf("\tinittime.tv_nsec = %ld\n", inittime.tv_nsec);
    printf("\tinitclock = %ld\n", (long)initclock);
}

/*
 * Get absolute future time for pthread timed calls
 *  Solution 1: microseconds granularity
 */
struct timespec get_abs_future_time_coarse(unsigned milli)
{
    struct timespec future;         /* ns since 1 Jan 1970 to 1500 ms in the future */
    struct timeval  micro = {0, 0}; /* 1 Jan 1970 */

    (void) gettimeofday(&micro, NULL);
    future.tv_sec = micro.tv_sec;
    future.tv_nsec = micro.tv_usec * 1000;
    NORMALISE_TIMESPEC( future, milli );
    return future;
}

/*
 * Solution 2: via clock service
 */
struct timespec get_abs_future_time_served(unsigned milli)
{
    struct timespec     future;
    clock_serv_t        cclock;
    mach_timespec_t     mts;

    host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
    clock_get_time(cclock, &mts);
    mach_port_deallocate(mach_task_self(), cclock);
    future.tv_sec = mts.tv_sec;
    future.tv_nsec = mts.tv_nsec;
    NORMALISE_TIMESPEC( future, milli );
    return future;
}

/*
 * Solution 3: nanosecond granularity
 */
struct timespec get_abs_future_time_fine(unsigned milli)
{
    struct timespec future;     /* ns since 1 Jan 1970 to 1500 ms in future */
    uint64_t        clock;      /* ticks since init */
    uint64_t        nano;       /* nanoseconds since init */

    clock = mach_absolute_time() - initclock;
    nano = clock * (uint64_t)timebase.numer / (uint64_t)timebase.denom;
    future = inittime;
    future.tv_sec += nano / BILLION;
    future.tv_nsec += nano % BILLION;
    NORMALISE_TIMESPEC( future, milli );
    return future;
}

#define N 3

int main()
{
    int                 i, j;
    struct timespec     time[3][N];
    struct timespec   (*get_abs_future_time[])(unsigned milli) =
    {
        &get_abs_future_time_coarse,
        &get_abs_future_time_served,
        &get_abs_future_time_fine
    };

    init();
    for (j = 0; j < 3; j++)
        for (i = 0; i < N; i++)
            time[j][i] = get_abs_future_time[j](1500);  /* now() + 1500 ms */

    for (j = 0; j < 3; j++)
        for (i = 0; i < N; i++)
            printf("get_abs_future_time_%d() : %10ld.%09ld\n",
                   j, time[j][i].tv_sec, time[j][i].tv_nsec);

    return 0;
}

1
仅供参考,我的系统上调用mach_absolute_time()并乘以info.numer / info.denom大约需要33纳秒每次调用:基准测试:https://gist.github.com/aktau/9f52f812200d8d69a5d1 libuv问题:https://github.com/joyent/libuv/pull/1325 - Aktau

32

实际上,在 macOS Sierra 10.12 之前,它似乎没有被实现。您可能需要查看此博客条目。主要思想在以下代码片段中:

#include <mach/mach_time.h>
#define ORWL_NANO (+1.0E-9)
#define ORWL_GIGA UINT64_C(1000000000)

static double orwl_timebase = 0.0;
static uint64_t orwl_timestart = 0;

struct timespec orwl_gettime(void) {
  // be more careful in a multithreaded environement
  if (!orwl_timestart) {
    mach_timebase_info_data_t tb = { 0 };
    mach_timebase_info(&tb);
    orwl_timebase = tb.numer;
    orwl_timebase /= tb.denom;
    orwl_timestart = mach_absolute_time();
  }
  struct timespec t;
  double diff = (mach_absolute_time() - orwl_timestart) * orwl_timebase;
  t.tv_sec = diff * ORWL_NANO;
  t.tv_nsec = diff - (t.tv_sec * ORWL_GIGA);
  return t;
}

4
我不希望得到单调的时间,而是想要自纪元以来的真实时间,以纳秒为单位。 - Delan Azabani
4
@Delan,我不明白你为什么想要这个,这对于计数年份级别的东西来说是无用的精度。通常情况下,你需要纳秒级的时间来计时一个函数等等。然后像那个博客里所做的那样,在执行前后获取时间就足够了。但是你可以通过在程序开始时取gettimeofdaymach_absolute_time ,然后将它们相加来模拟此过程。 - Jens Gustedt
10
永远不要混淆单调性和实时性。实时性可能会因NTP守护程序校正系统时钟而跳跃。它们确实是两个完全不同的事情。 - mic_e
2
顺便提一下,代码通过将乘法和除法封装在一个变量中,然后再进行乘法运算,不必要地降低了净计算的精度。对于整数运算,最好先进行所有乘法,然后再进行任何除法。 - JohnK
3
请使用以下链接: http://web.archive.org/web/20100517095152/http://www.wand.net.nz/~smr26/wordpress/2009/01/19/monotonic-time-in-mac-os-x/comment-page-1/ - Brian Cannard
显示剩余6条评论

29
#if defined(__MACH__) && !defined(CLOCK_REALTIME)
#include <sys/time.h>
#define CLOCK_REALTIME 0
// clock_gettime is not implemented on older versions of OS X (< 10.12).
// If implemented, CLOCK_REALTIME will have already been defined.
int clock_gettime(int /*clk_id*/, struct timespec* t) {
    struct timeval now;
    int rv = gettimeofday(&now, NULL);
    if (rv) return rv;
    t->tv_sec  = now.tv_sec;
    t->tv_nsec = now.tv_usec * 1000;
    return 0;
}
#endif

8
如果您使用CLOCK_REALTIME或CLOCK_MONOTONIC,您还应定义以下内容: #define CLOCK_REALTIME 0 #define CLOCK_MONOTONIC 0 - nat chouf
5
因为a) 精度低1000倍(尽管这并不太糟糕,因为clock_gettime实际返回的时间粒度很少(几乎从不)为1纳秒),以及b)gettimeofday返回的时间是不同且非单调的。它可能会突然跳变(例如在夏令时发生时),或者在时间服务器同步后甚至会略微以稍慢/稍快的速度运行!)其中时间服务可能希望避免对时间进行小的突然更改。 - the swine
4
很遗憾,一天中的时间并不是单调的时钟。(单调时钟是为了避免所有与日历时钟有关的问题而存在的,包括管理员或NTP更改时间、闰秒、时区等。) - Dave Pacheco

22

你需要的一切都在技术问答QA1398:Mach绝对时间单位中有详细描述,基本上你需要的函数是mach_absolute_time

下面是该页面稍早版本的示例代码,它使用Mach调用完成所有操作(当前版本使用CoreServices中的AbsoluteToNanoseconds)。在当前的OS X系统(即在x86_64架构的Snow Leopard上),绝对时间值实际上是以纳秒为单位的,因此根本不需要进行任何转换。所以,如果你想编写可移植的代码,可以进行转换,但如果只是为自己快速地完成某些简单操作,则无需费心。

顺便说一句,mach_absolute_time非常快。

uint64_t GetPIDTimeInNanoseconds(void)
{
    uint64_t        start;
    uint64_t        end;
    uint64_t        elapsed;
    uint64_t        elapsedNano;
    static mach_timebase_info_data_t    sTimebaseInfo;

    // Start the clock.

    start = mach_absolute_time();

    // Call getpid. This will produce inaccurate results because 
    // we're only making a single system call. For more accurate 
    // results you should call getpid multiple times and average 
    // the results.

    (void) getpid();

    // Stop the clock.

    end = mach_absolute_time();

    // Calculate the duration.

    elapsed = end - start;

    // Convert to nanoseconds.

    // If this is the first time we've run, get the timebase.
    // We can use denom == 0 to indicate that sTimebaseInfo is 
    // uninitialised because it makes no sense to have a zero 
    // denominator is a fraction.

    if ( sTimebaseInfo.denom == 0 ) {
        (void) mach_timebase_info(&sTimebaseInfo);
    }

    // Do the maths. We hope that the multiplication doesn't 
    // overflow; the price you pay for working in fixed point.

    elapsedNano = elapsed * sTimebaseInfo.numer / sTimebaseInfo.denom;

    printf("multiplier %u / %u\n", sTimebaseInfo.numer, sTimebaseInfo.denom);
    return elapsedNano;
}

15

请注意,macOS Sierra 10.12 现在支持 clock_gettime() 函数:

#include <stdio.h>
#include <time.h>

int main() {
    struct timespec res;
    struct timespec time;

    clock_getres(CLOCK_REALTIME, &res);
    clock_gettime(CLOCK_REALTIME, &time);

    printf("CLOCK_REALTIME: res.tv_sec=%lu res.tv_nsec=%lu\n", res.tv_sec, res.tv_nsec);
    printf("CLOCK_REALTIME: time.tv_sec=%lu time.tv_nsec=%lu\n", time.tv_sec, time.tv_nsec);
}

它确实提供了纳秒级别的时间;然而,它的分辨率是1000,因此实际上只能达到微秒级别:

CLOCK_REALTIME: res.tv_sec=0 res.tv_nsec=1000
CLOCK_REALTIME: time.tv_sec=1475279260 time.tv_nsec=525627000

要使用此功能,您需要XCode 8或更高版本。编译以使用此功能的代码将无法在Mac OS X(10.11或更早版本)上运行。


1
我使用的是macOS 10.12,但无法编译上述代码;我收到了“使用未声明的标识符'CLOCK_REALTIME'”的错误提示。 - Necktwi
1
奇怪,应该使用 #include <time.h> 可用。尝试运行 "xcode-select --install" 命令更新命令行开发工具。 - James Wald
我的Xcode版本是v7。对我来说下载4GB的Xcode8很痛苦;我对xcode-select --install使用的数据感到非常担心! - Necktwi
@JamesWald:我在问题下面放了一个评论,指向你的答案。对评论进行点赞可以提高其可见性(多个点赞会更好)。 - Jonathan Leffler
2
警告:如果使用XCode 8(或更高版本)进行构建,但目标版本为旧于10.12的OSX,则会在编译时找到 clock_gettime 符号,但在10.11或以下运行二进制文件时将出现 "dyld:Symbol not found: _clock_gettime",因为编译器生成了一个弱符号。 - Anon

9
感谢您的帖子。
我认为您可以添加以下内容。
#ifdef __MACH__
#include <mach/mach_time.h>
#define CLOCK_REALTIME 0
#define CLOCK_MONOTONIC 0
int clock_gettime(int clk_id, struct timespec *t){
    mach_timebase_info_data_t timebase;
    mach_timebase_info(&timebase);
    uint64_t time;
    time = mach_absolute_time();
    double nseconds = ((double)time * (double)timebase.numer)/((double)timebase.denom);
    double seconds = ((double)time * (double)timebase.numer)/((double)timebase.denom * 1e9);
    t->tv_sec = seconds;
    t->tv_nsec = nseconds;
    return 0;
}
#else
#include <time.h>
#endif

请告诉我您获取的延迟和粒度数据。


3
最好缓存mach_timebase_info调用(可以使用静态变量来使代码更整洁)。mach_timebase_info()是一个系统调用,在我的机器上需要大约180ns的时间。相比之下,mach_absolute_time()只需要大约22ns的时间,它基本上仅对rdtsc进行采样。 - Aktau
1
有趣的是,从10.12开始它不再是直接系统调用,而且苹果已经在libc层实现了缓存 https://opensource.apple.com/source/xnu/xnu-3789.41.3/libsyscall/wrappers/mach_timebase_info.c.auto.html 。但旧操作系统不是这种情况。 - 1110101001

4

到目前为止,Maristic给出了最好的答案。让我简化并加一个备注。 #includeInit():

#include <mach/mach_time.h>

double conversion_factor;

void Init() {
  mach_timebase_info_data_t timebase;
  mach_timebase_info(&timebase);
  conversion_factor = (double)timebase.numer / (double)timebase.denom;
}

使用方法:

  uint64_t t1, t2;

  Init();

  t1 = mach_absolute_time();
  /* profiled code here */
  t2 = mach_absolute_time();

  double duration_ns = (double)(t2 - t1) * conversion_factor;  

这样的计时器延迟为65ns +/- 2ns(2GHz CPU)。如果需要单次执行的“时间演化”,则使用此选项。否则,将您的代码循环10000次并使用gettimeofday()进行分析,该方法具有可移植性(POSIX),延迟为100ns +/- 0.5ns(只有1us精度)。


1
你有没有想过将数据转换为double类型对精度的影响?据我所知,double类型只有53位的精度,而mach_absolute_time函数返回的是64位范围内的任意值。只是好奇。 - Aktau

3

我尝试了带有clock_get_time版本,并缓存了host_get_clock_service调用。比gettimeofday慢得多,每次调用需要几微秒。更糟糕的是,返回的值具有1000个步骤,即仍然是微秒粒度。

我建议使用gettimeofday,并将tv_usec乘以1000。


1
gettimeofday 返回的时间可能是非单调的。它可能会跳动(例如在夏令时更改时),或者甚至在时间服务器同步后以稍微慢/快的速度运行,其中时间服务可能希望避免对时间进行小的突然更改。 - the swine

2
基于开源的mach_absolute_time.c,我们可以看到extern mach_port_t clock_port;这一行告诉我们已经初始化了一个用于单调时间的mach端口。可以直接访问这个时钟端口,而不必调用mach_absolute_time然后再将其转换为struct timespec。绕过对mach_absolute_time的调用应该能提高性能。
我创建了一个小的 Github 仓库 (PosixMachTiming),其中的代码基于外部的 clock_port 和一个类似的线程PosixMachTiming 模拟了 CLOCK_REALTIMECLOCK_MONOTONICclock_gettime 函数,并为绝对单调时间模拟了 clock_nanosleep 函数。请尝试一下并查看性能如何比较。也许您想创建比较测试或模拟其他 POSIX 时钟/函数?

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