博客搬家,原地址:https://langzi989.github.io/2019/01/02/mktime性能分析/
mktime是一个将break-down时间(struct tm)转化为日历时间(time_t)的转换函数。它的转换与struct tm中的 tm_wday、tm_yday无关,当进行转换时,mktime会通过struct tm的其他成员重新矫正该值。若struct tm中的成员是非法的的,mktime将会自动校正,如2018-12-32 00:00:00,矫正后为2019-01-01 00:00:00。若给定struct tm不能转换为日历时间,则mktime返回-1。----man mktime
1. 背景
背景:最近工作中遇到一个奇怪的问题,在将原先在32位机器上编译的程序放在64位机器上重新编译之后,然后放到IDC机器运行,发现性能降了100倍左右。在经过性能分析和查阅相关资料后发现,是由于mktime使用不当导致。
#include <iostream>
#include <string>
#include <sys/time.h>
#include <unistd.h>
using namespace std;
#define PrintTime(id) struct timeval __now1105##id;\
gettimeofday(&__now1105##id, 0);
#define PrintTimeDone(id) struct timeval __now21105##id; \
gettimeofday(&__now21105##id, 0); \
printf("timer_%s spend time:%d us\n",#id,(__now21105##id.tv_sec-__now1105##id.tv_sec)* 1000000 + (__now21105##id.tv_usec-__now1105##id.tv_usec));
static void get_time(const std::string& time_str)
{
struct tm temp_tm;
strptime(time_str.c_str(), "%Y-%m-%d %H:%M:%S", &temp_tm);
PrintTime(mktime);
time_t temp = mktime(&temp_tm);
PrintTimeDone(mktime);
}
int main()
{
for (int i = 0; i < 10; i++)
get_time("2018-12-27 00:00:00");
return 0;
}
32位机器(i686)编译运行结果:
timer_mktime spend time:51 us
timer_mktime spend time:4 us
timer_mktime spend time:2 us
timer_mktime spend time:3 us
timer_mktime spend time:3 us
timer_mktime spend time:3 us
timer_mktime spend time:3 us
timer_mktime spend time:2 us
timer_mktime spend time:2 us
timer_mktime spend time:2 us
64位机器(x86_64 )编译运行结果:
timer_mktime spend time:181 us
timer_mktime spend time:156 us
timer_mktime spend time:138 us
timer_mktime spend time:138 us
timer_mktime spend time:137 us
timer_mktime spend time:145 us
timer_mktime spend time:143 us
timer_mktime spend time:138 us
timer_mktime spend time:138 us
timer_mktime spend time:145 us
造成上述问题的原因究竟是什么呢?
2. 源码分析mktime性能
2.1 mktime源码:
mktime.c
/* Convert *TP to a time_t value. */
time_t
mktime (struct tm *tp)
{
#ifdef _LIBC
/* POSIX.1 8.1.1 requires that whenever mktime() is called, the
time zone names contained in the external variable 'tzname' shall
be set as if the tzset() function had been called. */
__tzset ();
#endif
return __mktime_internal (tp, __localtime_r, &localtime_offset);
}
/* Convert *TP to a time_t value, inverting
the monotonic and mostly-unit-linear conversion function CONVERT.
Use *OFFSET to keep track of a guess at the offset of the result,
compared to what the result would be for UTC without leap seconds.
If *OFFSET's guess is correct, only one CONVERT call is needed.
This function is external because it is used also by timegm.c. */
time_t
__mktime_internal (struct tm *tp,
struct tm *(*convert) (const time_t *, struct tm *),
time_t *offset)
{
time_t t, gt, t0, t1, t2;
struct tm tm;
/* The maximum number of probes (calls to CONVERT) should be enough
to handle any combinations of time zone rule changes, solar time,
leap seconds, and oscillations around a spring-forward gap.
POSIX.1 prohibits leap seconds, but some hosts have them anyway. */
int remaining_probes = 6;
/* Time requested. Copy it in case CONVERT modifies *TP; this can
occur if TP is localtime's returned value and CONVERT is localtime. */
int sec = tp->tm_sec;
int min = tp->tm_min;
int hour = tp->tm_hour;
int mday = tp->tm_mday;
int mon = tp->tm_mon;
int year_requested = tp->tm_year;
int isdst = tp->tm_isdst;
/* 1 if the previous probe was DST. */
int dst2;
/* Ensure that mon is in range, and set year accordingly. */
int mon_remainder = mon % 12;
int negative_mon_remainder = mon_remainder < 0;
int mon_years = mon / 12 - negative_mon_remainder;
long_int lyear_requested = year_requested;
long_int year = lyear_requested + mon_years;
/* The other values need not be in range:
the remaining code handles minor overflows correctly,
assuming int and time_t arithmetic wraps around.
Major overflows are caught at the end. */
/* Calculate day of year from year, month, and day of month.
The result need not be in range. */
int mon_yday = ((__mon_yday[leapyear (year)]
[mon_remainder + 12 * negative_mon_remainder])
- 1);
long_int lmday = mday;</