mktime为什么这么慢

来源:互联网 发布:手机号码淘宝被注册 编辑:程序博客网 时间:2024/05/16 05:06

/******************************************************************************************
*              版权声明
*   本文为本人原创,本人拥有此文的版权。鉴于本人持续受益于开源软件社区,
* 本人声明:任何个人及团体均可不受限制的转载和复制本文,无论是否用于盈利
* 之目的,但不得修改文章内容,并必须在转载及复制时同时保留本版权声明,否
*   则为侵权行为,本人保留追究相应主体法律责任之权利。
*               speng2005@gmail.com
*                  2015-11
******************************************************************************************/


       最近写了个C++小程序,没想到栽在mktime函数上,以前很少用过这个函数。

       事情是这样的,在Linux上(debain,kernel 2.6.26)写一个C++小程序,实现将txt文件中的数据提取后简单处理并插入mysql数据库。逻辑很简单,程序也不大,写完以后跑600万行的txt数据文件,感觉比较慢,于是开始使用一个较小的数据文件跑优化工具来分析代码瓶颈。先后使用了OProfile,gprof和google-perftools这三个工具,最后发现还是google-perftools最好用。经过分析找到程序热点并不在之前所认为的mysql接口函数上,而是在一个将"1990-12-19"格式的字符串转换为日历时间即time_t类型的函数上,代码如下:

time_t getTimeOfDate(const string & dateStr, string * timeStr = NULL)
{
        stringstream & mySS = getMyss();  //可以以'-'为分隔符的stringstream
        mySS.clear();
        mySS.str(dateStr);
        int year,month,day;
        mySS>>year>>month>>day;
        if( month > 12 || day > 31 ) return (time_t)-1;
        struct tm stm;
        stm.tm_year = year - 1900;
        stm.tm_mon = month - 1;
        stm.tm_mday = day;
        stm.tm_hour = 0;
        stm.tm_min = 0;
        stm.tm_sec = 0;
        time_t t = mktime(&stm);
        if(timeStr)
        {
                mySS.clear();
                mySS.str("");
                mySS<<t;
                *timeStr = mySS.str();
        }
        return t;

}

       如上代码跑完测试数据文件需要284秒,其中程序总运行时间的83.3%花在getTimeOfDate函数上,而在该函数内竟然主要时间消耗在mktime函数调用上,在该函数上消耗了程序总运行时间的82.3%,这真是出乎意料!此时google-perftools的调用图如下:

        上图中__GI_mktime函数以及其下层函数调用树即是我用的Linux平台上对标准c库中mktime函数的具体实现,可以说还是相当复杂的。根据调用图分析后又查了些资料,知道原因在于mktime函数调用中要处理时区和夏令时问题。仔细看看上面getTimeOfDate函数代码中,发现犯了个低级错误,未对struct tm stm结构的所有成员进行内存初始化,从而导致后面调用的mktime函数中将自动尝试设置正确的时区信息并调整夏令时,而这操作在本人使用的linux系统上意味着要扫描系统时区配置文件并分析(通过strace命令跟踪也证实程序运行过程中频繁地对/etc/localtime文件进行stat64系统api调用),这就是程序运行缓慢的主要原因。于是对getTimeOfDate函数代码进行改进,如下:

time_t getTimeOfDate(const string & dateStr, string * timeStr = NULL)
{
        stringstream & mySS = getMyss();  //可以以'-'为分隔符的stringstream
        mySS.clear();
        mySS.str(dateStr);
        int year,month,day;
        mySS>>year>>month>>day;
        if( month > 12 || day > 31 ) return (time_t)-1;
        struct tm stm[2];
        int * pStm = (int*)&stm[0];
        fill(pStm, pStm + sizeof(tm)/sizeof(int) + 1, 0);
        stm[0].tm_year = year - 1900;
        stm[0].tm_mon = month - 1;
        stm[0].tm_mday = day;
        time_t t = mktime(&stm[0]);
        if(timeStr)
        {
                mySS.clear();
                mySS.str("");
                mySS<<t;
                *timeStr = mySS.str();
        }
        return t;
}

       用同样的测试环境跑优化了getTimeOfDate函数的程序来处理同样多的数据文件,跑完仅需要43秒,性能提高了5倍!此时google-perftools分析的调用图如下:

       从图中可以看到getTimeOfDate函数仅占用了程序总运行时间的26.3%,而其中mktime函数调用仅占用了程序总运行时间的12.6%,程序性能已经得到优化。

       后来看到网上资料说如果设置了TZ环境变量可以加快mktime的执行速度,于是又测了一下,在启动我的测试程序之前先设置TZ环境变量:export TZ=UTC0,然后跑完同样数据文件花了40秒,性能略有提高,表面看效果不明显。但如果分析google-perftools产生的调用图,还是可以看到明显区别的,如下:

       从图中可以看到getTimeOfDate函数仅占用了程序总运行时间的17.6%,而其中mktime函数调用仅占用了程序总运行时间的1.4%,关于对mktime标准c函数的调用已经得到很大的优化。

       综上来看,如果你的程序要频繁调用mktime函数,一定要在调用之前对struct tm的所有成员尤其是时区进行合理初始化。查了一下struct tm的完整定义,发现不同平台和开发环境的定义还不太相同,也没工夫去查标准c的定义,暂以下面这个参考吧:

struct tm {
  int tm_sec; /* 秒–取值区间为[0,59] */
  int tm_min; /* 分 - 取值区间为[0,59] */
  int tm_hour; /* 时 - 取值区间为[0,23] */
  int tm_mday; /* 一个月中的日期 - 取值区间为[1,31] */
  int tm_mon; /* 月份(从一月开始,0代表一月) - 取值区间为[0,11] */
  int tm_year; /* 年份,其值从1900开始 */
  int tm_wday; /* 星期–取值区间为[0,6],其中0代表星期天,1代表星期一,以此类推 */
  int tm_yday; /* 从每年的1月1日开始的天数–取值区间为[0,365],其中0代表1月1日,1代表1月2日,以此类推 */
  int tm_isdst; /* 夏令时标识符,实行夏令时的时候,tm_isdst为正。不实行夏令时的进候,tm_isdst为0;不了解情况时,tm_isdst()为负。*/
  long int tm_gmtoff; /*指定了日期变更线东面时区中UTC东部时区正秒数或UTC西部时区的负秒数*/
  const char *tm_zone; /*当前时区的名字(与环境变量TZ有关)*/

};

在不同环境中struct tm结构定义的差别主要在tm_isdst,tm_gmtoff,tm_zone这三个成员上,有的系统没定义这三个成员,有的系统定义了一部分,有的系统定义了但名称不一样。不管怎么样,在使用mktime前你应该根据系统情况对这三个成员进行初始化,最简单的方法就是全部置0。



0 0
原创粉丝点击