这是一个汇编指令,可以获取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
函数原型如下:
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];
};
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_t time(time_t *t);
获取从UNIX元年开始计数的秒数,故精度为秒。参数t不为NULL,秒数会存储在t所指向的内存,time_t就是int64_t类型,可以直接返回结果,故线程安全,而无需提前分配内存空间。该函数得到的秒数是很多函数的输入,因为很多函数都只是对这些秒数做处理,得到更人性化的输出。
函数原型为:
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 */
};
函数原型为:
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,但建议使用后者。
函数原型为:
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);
函数原型如下:
char *ctime_r(const time_t *timep, char *buf);
ctime_r(t)就等于asctime(localtime_r(t))
,方便一步到位。
函数原型为:
time_t mktime(struct tm *tm);
从参数上就能知道,它是localtime的逆过程,等同于timelocal,即将tm格式,转成秒数。
两函数原型为:
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性能的代码:
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;
}