linux上的各种时间函数

rdtsc

这是一个汇编指令,可以获取CPU的时钟周期,即滴答tick次数,现在随便一个CPU的频率就是几GHz了,所以算算就知道, 一个CPU时钟周期比纳秒还短,这是非常精确获取时间的方法。当然这获取的只是时钟周期,要转换成时间, 还需要获取运行时的CPU频率,通过cat /proc/cpuinfo获取cpu频率,然后时钟tick数除以频率换算得到时间。对该汇编指令的函数封装如下。

#if defined(__i386__)
static __inline__ unsigned long long rdtsc(void)
{
    unsigned long long int x;
    __asm__ volatile ("rdtsc" : "=A" (x));
    return x;
}
#elif defined(__x86_64__)
static __inline__ unsigned long long rdtsc(void)
{
    unsigned hi, lo; 
    __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
    return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}
#endif

gettimeofday

函数原型如下:

int gettimeofday(struct timeval *tv, struct timezone *tz);

获取时间戳,精度达到微秒,时间存储结构体如下,为16字节大小,分别为两个有符号64位整形。

struct timeval {
    time_t      tv_sec;     /* seconds */
    suseconds_t tv_usec;    /* microseconds */
};

gettimeofday通常用在统计函数处理时间上。举个例子:

/**
 * 时间统计
 */
class Task
{
public:
    Task(const char* title)
    {   
        strncpy(t, title, 16);
        gettimeofday(&tv,NULL);
    }   

    void set_task(const char* title)
    {   
        struct timeval ttv;
        gettimeofday(&ttv, NULL);
        fprintf(stdout, "%s: %lu ms\n", t,
                (ttv.tv_sec-tv.tv_sec)*1000+(ttv.tv_usec-tv.tv_usec)/1000);
        strncpy(t, title, 16);
        tv = ttv;
    }   

    ~Task()
    {   
        struct timeval ttv;
        gettimeofday(&ttv, NULL);
        fprintf(stdout, "%s: %lu ms\n", t,
                (ttv.tv_sec-tv.tv_sec)*1000+(ttv.tv_usec-tv.tv_usec)/1000);
    }   
private:
    struct timeval tv; 
    char t[16];
};

clock_gettime

POSIX.1-2008标准不推荐使用gettimeofday,而推荐使用clock_gettime,函数原型如下:

int clock_gettime(clockid_t clk_id, struct timespec *tp);

获取时间戳,精度达到纳秒,时间存储结构体如下:

struct timespec {
        time_t   tv_sec;        /* seconds */
        long     tv_nsec;       /* nanoseconds */
};

参数clk_id有很多种取值,常见的有:CLOCK_REALTIME(系统时间,受系统时间调整影响,所以不保证是递增),CLOCK_MONOTONIC(非系统时间,严格递增,适合算时间差),CLOCK_REALTIME_COARSE和CLOCK_MONOTONIC_COARSE速度更快,大约能快4倍,但时间没那么精确。各获取时间函数,耗时和精度大致如下:

time (s) => 4ns
ftime (ms) => 39ns
gettimeofday (us) => 30ns
clock_gettime (ns) => 26ns (CLOCK_REALTIME)
clock_gettime (ns) => 8ns (CLOCK_REALTIME_COARSE)
clock_gettime (ns) => 26ns (CLOCK_MONOTONIC)
clock_gettime (ns) => 9ns (CLOCK_MONOTONIC_COARSE)  (相对CLOCK_MONOTONIC不精确)
clock_gettime (ns) => 170ns (CLOCK_PROCESS_CPUTIME_ID)
clock_gettime (ns) => 154ns (CLOCK_THREAD_CPUTIME_ID)

time

函数原型为:

time_t time(time_t *t);

获取从UNIX元年开始计数的秒数,故精度为秒。参数t不为NULL,秒数会存储在t所指向的内存,time_t就是int64_t类型,可以直接返回结果,故线程安全,而无需提前分配内存空间。该函数得到的秒数是很多函数的输入,因为很多函数都只是对这些秒数做处理,得到更人性化的输出。

gmtime_r

函数原型为:

struct tm *gmtime_r(const time_t *timep, struct tm *result);

你可能会发现还有不带_r的函数版本,不带_r的都是线程不安全的,所以不推荐使用。该函数将time_t转换成struct tm,该结构体的定义如下。该函数得到的是没有时区,或者说是时区偏移为0的时间,所谓的格林威治时间(GMT),这也是为什么函数名字中有gm的原因。

逆操作函数为time_t timegm(struct tm *tm);struct tm的定义如下:

struct tm {
    int tm_sec;         /* seconds */
    int tm_min;         /* minutes */
    int tm_hour;        /* hours */
    int tm_mday;        /* day of the month */
    int tm_mon;         /* month */
    int tm_year;        /* year */
    int tm_wday;        /* day of the week */
    int tm_yday;        /* day in the year */
    int tm_isdst;       /* daylight saving time */
};

localtime_r

函数原型为:

struct tm *localtime_r(const time_t *timep, struct tm *result);

这个函数跟gmtime_r差不多,不同点就是会考虑用户机器所在的时区(/etc/timezone)。如果在程序性能要求很苛刻的条件下,不推荐使用该函数,因为时区的处理导致它会比gmtime_r慢很多,如果你是设计WEB服务器的,打印日志时会大量用到时间,这个时候有两种处理办法,使用gmtime_r后,打印时间时,同时输出+8,这样告诉了用户当前所在时区,但用户在理解这个时间时,需要把+8小时,算进去,包括Nginx在内的很多开源软件是这么干的。另一种方法是,在使用gmtime_r之前,将time(NULL)的结果加上28800,即8小时,这样就太平了,时间也易读,性能也高。有同事踩过这个坑,在做数据过滤时,每条数据打印一条日志(日志写到缓冲区),马上速度变得奇慢,后来发现就是localtime_r这玩意惹的祸。

其实tm还有两个关于时区的变量,即tm_zone和tm_gmtoff,前一个是字符串,如"GMT"或"CST"之类的时区代号,后一个是与格林威治时间的秒数差值。在linux的manpage里没有描述,估计是使用较少吧。

逆操作函数为time_t timelocal(struct tm *tm);timelocal等同于mktime,但建议使用后者。

asctime_r

函数原型为:

char *asctime_r(const struct tm *tm, char *buf);

将tm结构体的数据转化成人可读的字符串,结构可能是这样的“Fri Aug 9 06:43:28 2013”,buf大小需26字节以上。该函数也不推荐使用,理由很简单,不符合中国人的习惯。推荐使用strftime进行格式化,以得到自己喜欢的时间格式。我喜欢朴素简单的,像2013-09-06 06:43:28这样的,格式化代码如下。

strftime(time_request, 26, "%Y-%m-%d %H:%M:%S", &res);

ctime_r

函数原型如下:

char *ctime_r(const time_t *timep, char *buf);

ctime_r(t)就等于asctime(localtime_r(t)),方便一步到位。

mktime

函数原型为:

time_t mktime(struct tm *tm);

从参数上就能知道,它是localtime的逆过程,等同于timelocal,即将tm格式,转成秒数。

strftime和strptime

两函数原型为:

size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
char *strptime(const char *s, const char *format, struct tm *tm);

上面两个函数也是互逆的,前者进行格式化,后者进行反格式化。特别注意,使用strftime前应该将tm进行memset清零,因为strftime不会帮你把未使用的变量清零,使用举例如下:

const char *date = "2014-01-16";

struct tm my_tm;
memset(&my_tm, 0, sizeof(my_tm));
strptime(date, "%Y-%m-%d", &my_tm);

localtime_r和gmtime_r性能对比

最后附后上一段测试localtime_r和gmtime_r性能的代码:

int main()
{
    char buf[26];
    unsigned long long start_tick;
    struct tm gmtm;
    struct tm lotm;

    time_t now_timestamp = time(NULL);
    printf("%lu\n", now_timestamp);
    ctime_r(&now_timestamp, buf);
    printf("%s\n", buf);

    start_tick = rdtsc();
    localtime_r(&now_timestamp, &lotm);
    printf("rdtsc: %llu\n", rdtsc() - start_tick);
    asctime_r(&lotm, buf);
    printf("localtime_r: %s\n", buf);
    printf("%d-%d-%d %d:%d:%d %s %ld\n",
            lotm.tm_year, lotm.tm_mon, lotm.tm_mday, lotm.tm_hour,
            lotm.tm_min, lotm.tm_sec, lotm.tm_zone, lotm.tm_gmtoff);
    printf("%lu\n", mktime(&lotm));

    start_tick = rdtsc();
    gmtime_r(&now_timestamp, &gmtm);
    printf("rdtsc: %llu\n", rdtsc() - start_tick);
    asctime_r(&gmtm, buf);
    printf("gmtime_r: %s\n", buf);
    printf("%d-%d-%d %d:%d:%d %s %ld\n",
            gmtm.tm_year, gmtm.tm_mon, gmtm.tm_mday, gmtm.tm_hour,
            gmtm.tm_min, gmtm.tm_sec, gmtm.tm_zone, gmtm.tm_gmtoff);
    printf("%lu\n", mktime(&gmtm));

    return 0;
}
发表于 2013年08月10日 11:23   评论:0   阅读:4229  



回到顶部

首页 | 关于我 | 关于本站 | 站内留言 | rss
python logo   django logo   tornado logo