目录[-]
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]; };
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; }