一、问题提出

会议中有同学提到使用mktime遇到一些问题: 1) 设置tm_isdst后速度很慢 2) 设置TZ环境变量提速极大 所以想调查下具体情况。

 

mktime真的这么慢?如果是,为什么?

二、测试和检验

环境(不同环境可能结果迥异,以下所述仅对本环境有效)

$ cat /proc/version
Linux version --tlinux2-.tl2 (mockbuild@TENCENT64.site)
(gcc version   (Red Hat -) (GCC) )
# SMP Fri Apr  :: CST 

$ getconf -a | grep glibc -i
GNU_LIBC_VERSION                   glibc 2.17

首先写了个简单的mktime测试。

#include <sys/time.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

typedef int64_t timestamp_t;

static timestamp_t get_timestamp()
{
    struct timeval tv = {};
    gettimeofday(&tv, );
     + (timestamp_t)tv.tv_usec;
}

static void call_mktime(int isdst)
{
    struct tm tm = {};
    tm.tm_year =  - ;
    tm.tm_mon  =  - ;
    tm.tm_mday = ;
    tm.tm_hour = ;
    tm.tm_min  = ;
    tm.tm_sec  = ;
    tm.tm_isdst = isdst;
     == mktime(&tm)) {
        abort();
    }
}

int main()
{
    , };

    for (const auto &isdst: isdsts) {
        timestamp_t t1 = get_timestamp();
        ;
        ; i < N; ++i) {
            call_mktime(isdst);
        }
        timestamp_t t2 = get_timestamp();
        printf("isdst=%d rounds %d avg cost %4.2f us\n", isdst, N, 1.0*(t2-t1)/N);
    }

    ;
}

跑一下,得到结果如下:

$ TZ="Asia/Shanghai" ./app_test_mktime 
isdst=1 rounds 100 avg cost 484.32 us 
isdst=0 rounds 100 avg cost 2.17 us

还真的很慢啊!慢的掉渣了! 
 
But!真的是这样吗? 
 
要不试试其他时区?就用美国东部时间好了。

$ TZ="US/Eastern" ./app_test_mktime 
isdst=1 rounds 100 avg cost 2.31 us 
isdst=0 rounds 100 avg cost 0.20 us

奇迹发生了,不慢啊,也就几微秒而已,虽然慢了一点点,但绝对没有那么夸张。

三、基本结论

跟着这个思路,稍微扩大一下可变的参数,包括下面几个因子:

  1. 日历时间
  2. 时区配置
  3. 输入isdst

最后跑出一个结果(源代码见后):

 

 

 

isdst

日历时间

时区配置

夏令时

1

0

-1

1688-06-01 02:00

Asia/Shanghai

N

103.95

0.23

0.23

 

US/Eastern

N

114.6

0.26

0.23

 

America/Jujuy

N

125.26

0.27

0.25

1960-06-01 02:00

Asia/Shanghai

N

158.9

0.3

0.29

 

US/Eastern

Y

0.6

4.3

0.39

 

America/Jujuy

Y

0.48

67.24

0.34

1986-06-01 02:00

Asia/Shanghai

Y

0.48

2.47

0.3

 

US/Eastern

Y

0.44

2.73

0.3

 

America/Jujuy

N

54.67

0.34

0.32

2016-01-01 02:00

Asia/Shanghai

N

501.6

0.76

0.76

 

US/Eastern

N

4.49

0.32

0.31

 

America/Jujuy

N

507.45

0.81

0.78

2016-05-01 02:00

Asia/Shanghai

N

505.49

0.7

0.7

 

US/Eastern

Y

0.64

3.77

0.31

 

America/Jujuy

N

514.04

0.76

0.77

最后两列是一次mktime调用消耗的微秒数。 注意America/Jujuy这个时区,非常有趣。

从这张表格可以总结出一个基本结论: 当tm_isdst设置不当时,调用mktime会消耗更多的时间。

  • 如果当时的日历时间是夏令时,那么isdst=1速度比isdst=0 快 ;
  • 如果当时的日立时间是常规时,那么isdst=1速度比isdst=0 慢 ;
  • 调用mktime,可以传入isdst=-1,让glibc根据时区自动决定DST标记。

The mktime() function converts a broken-down time structure, expressed as local time, to calendar time representation. The function ignores the values supplied by the caller in the tm_wday and tm_yday fields. The value specified in the tm_isdst field informs mktime() whether or not daylight saving time (DST) is in effect for the time supplied in the tm structure: a positive value means DST is in effect; zero means that DST is not in effect; and a negative value means that mktime() should (use timezone information and system databases to) attempt to determine whether DST is in effect at the specified time.

至于KM文章中提到的设置TZ环境变量导致的性能差异,是非常小的,有兴趣的可以做个测试。

四、进一步调查

为什么isdst配置不当时,速度会相差这么多?这和mktime的实现有关。 
 
mktime转换年月日格式的时间到时间戳,分几个步骤

  1. 6次循环,猜测得到一个时间戳t1,调用localtime(t1)能够得到正确的年月日表示。但它的夏令时标记(isdst)可能和用户传递进来的不一致。
  2. 如果夏令时标记和用户调用传递的isdst不同(0 vs 1, 1 vs 0),以t1为基准,前后搜寻合适的日历时,如果找到一个日历时,它的DST标记符合,以该日历时所处的DST为准。如果无法搜寻到合适的结果,直接返回t1
  3. 搜寻思想:以当前时间为中心,前后搜寻,找到一个与输入参数匹配的夏令时/冬令时区间,以该区间的配置来校准t1。
  4. 搜寻算法: 
    • 步长:601200秒,这是所有夏令时区间中最短的一个周期:7天,时区为:America/Recife
    • 范围:以t1为中心的536454000秒区间。这是所有夏令时区间中最长的一个周期:17年,时区为America/Jujuy。范围:[t-536454000/2+601200,t+536454000/2+601200],最大故迭代次数894次。
    • 迭代: 对当前时间戳tx调用localtime(tx),若结果的isdst和输入的isdst相同,命中,跳出循环。否则继续。
    • 命中:根据命中时的DST设置,找到正确的时间戳t3,转换成功。

迭代次数越多,耗时就越多。如果步骤1转换的posix time距离最近的匹配区间(夏令时/冬令时)很远,搜寻耗时就很长。 
 
Asio/Shanghai的夏令时从1991年废除,而US/Eastern每年都有夏令时,所以,大部分情况下前者的迭代次数远大于后者,这也能很好的解释上面的图表。 
 
zdump看一下不同时区数据库的信息,注意16881986两个测试日历时间的选取。

$ zdump -v Asia/Shanghai
Asia/Shanghai -9223372036854775808 = NULL
Asia/Shanghai -9223372036854689408 = NULL
Asia/Shanghai Mon Dec 31 15:54:16 1900 UTC = Mon Dec 31 23:59:59 1900 LMT isdst=0 gmtoff=29143
Asia/Shanghai Mon Dec 31 15:54:17 1900 UTC = Mon Dec 31 23:54:17 1900 CST isdst=0 gmtoff=28800
Asia/Shanghai Sun Jun 2 15:59:59 1940 UTC = Sun Jun 2 23:59:59 1940 CST isdst=0 gmtoff=28800
Asia/Shanghai Sun Jun 2 16:00:00 1940 UTC = Mon Jun 3 01:00:00 1940 CDT isdst=1 gmtoff=32400
Asia/Shanghai Mon Sep 30 14:59:59 1940 UTC = Mon Sep 30 23:59:59 1940 CDT isdst=1 gmtoff=32400
Asia/Shanghai Mon Sep 30 15:00:00 1940 UTC = Mon Sep 30 23:00:00 1940 CST isdst=0 gmtoff=28800
Asia/Shanghai Sat Mar 15 15:59:59 1941 UTC = Sat Mar 15 23:59:59 1941 CST isdst=0 gmtoff=28800
Asia/Shanghai Sat Mar 15 16:00:00 1941 UTC = Sun Mar 16 01:00:00 1941 CDT isdst=1 gmtoff=32400
Asia/Shanghai Tue Sep 30 14:59:59 1941 UTC = Tue Sep 30 23:59:59 1941 CDT isdst=1 gmtoff=32400
Asia/Shanghai Tue Sep 30 15:00:00 1941 UTC = Tue Sep 30 23:00:00 1941 CST isdst=0 gmtoff=28800
Asia/Shanghai Sat May 3 15:59:59 1986 UTC = Sat May 3 23:59:59 1986 CST isdst=0 gmtoff=28800
Asia/Shanghai Sat May 3 16:00:00 1986 UTC = Sun May 4 01:00:00 1986 CDT isdst=1 gmtoff=32400
Asia/Shanghai Sat Sep 13 14:59:59 1986 UTC = Sat Sep 13 23:59:59 1986 CDT isdst=1 gmtoff=32400
Asia/Shanghai Sat Sep 13 15:00:00 1986 UTC = Sat Sep 13 23:00:00 1986 CST isdst=0 gmtoff=28800
Asia/Shanghai Sat Apr 11 15:59:59 1987 UTC = Sat Apr 11 23:59:59 1987 CST isdst=0 gmtoff=28800
Asia/Shanghai Sat Apr 11 16:00:00 1987 UTC = Sun Apr 12 01:00:00 1987 CDT isdst=1 gmtoff=32400
Asia/Shanghai Sat Sep 12 14:59:59 1987 UTC = Sat Sep 12 23:59:59 1987 CDT isdst=1 gmtoff=32400
Asia/Shanghai Sat Sep 12 15:00:00 1987 UTC = Sat Sep 12 23:00:00 1987 CST isdst=0 gmtoff=28800
Asia/Shanghai Sat Apr 9 15:59:59 1988 UTC = Sat Apr 9 23:59:59 1988 CST isdst=0 gmtoff=28800
Asia/Shanghai Sat Apr 9 16:00:00 1988 UTC = Sun Apr 10 01:00:00 1988 CDT isdst=1 gmtoff=32400
Asia/Shanghai Sat Sep 10 14:59:59 1988 UTC = Sat Sep 10 23:59:59 1988 CDT isdst=1 gmtoff=32400
Asia/Shanghai Sat Sep 10 15:00:00 1988 UTC = Sat Sep 10 23:00:00 1988 CST isdst=0 gmtoff=28800
Asia/Shanghai Sat Apr 15 15:59:59 1989 UTC = Sat Apr 15 23:59:59 1989 CST isdst=0 gmtoff=28800
Asia/Shanghai Sat Apr 15 16:00:00 1989 UTC = Sun Apr 16 01:00:00 1989 CDT isdst=1 gmtoff=32400
Asia/Shanghai Sat Sep 16 14:59:59 1989 UTC = Sat Sep 16 23:59:59 1989 CDT isdst=1 gmtoff=32400
Asia/Shanghai Sat Sep 16 15:00:00 1989 UTC = Sat Sep 16 23:00:00 1989 CST isdst=0 gmtoff=28800
Asia/Shanghai Sat Apr 14 15:59:59 1990 UTC = Sat Apr 14 23:59:59 1990 CST isdst=0 gmtoff=28800
Asia/Shanghai Sat Apr 14 16:00:00 1990 UTC = Sun Apr 15 01:00:00 1990 CDT isdst=1 gmtoff=32400
Asia/Shanghai Sat Sep 15 14:59:59 1990 UTC = Sat Sep 15 23:59:59 1990 CDT isdst=1 gmtoff=32400
Asia/Shanghai Sat Sep 15 15:00:00 1990 UTC = Sat Sep 15 23:00:00 1990 CST isdst=0 gmtoff=28800
Asia/Shanghai Sat Apr 13 15:59:59 1991 UTC = Sat Apr 13 23:59:59 1991 CST isdst=0 gmtoff=28800
Asia/Shanghai Sat Apr 13 16:00:00 1991 UTC = Sun Apr 14 01:00:00 1991 CDT isdst=1 gmtoff=32400
Asia/Shanghai Sat Sep 14 14:59:59 1991 UTC = Sat Sep 14 23:59:59 1991 CDT isdst=1 gmtoff=32400
Asia/Shanghai Sat Sep 14 15:00:00 1991 UTC = Sat Sep 14 23:00:00 1991 CST isdst=0 gmtoff=28800
Asia/Shanghai 9223372036854689407 = NULL
Asia/Shanghai 9223372036854775807 = NULL 

$ zdump -v US/Eastern
US/Eastern -9223372036854775808 = NULL
US/Eastern -9223372036854689408 = NULL
US/Eastern Sun Nov 18 16:59:59 1883 UTC = Sun Nov 18 12:03:57 1883 LMT isdst=0 gmtoff=-17762
US/Eastern Sun Nov 18 17:00:00 1883 UTC = Sun Nov 18 12:00:00 1883 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 31 06:59:59 1918 UTC = Sun Mar 31 01:59:59 1918 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 31 07:00:00 1918 UTC = Sun Mar 31 03:00:00 1918 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Oct 27 05:59:59 1918 UTC = Sun Oct 27 01:59:59 1918 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Oct 27 06:00:00 1918 UTC = Sun Oct 27 01:00:00 1918 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 30 06:59:59 1919 UTC = Sun Mar 30 01:59:59 1919 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 30 07:00:00 1919 UTC = Sun Mar 30 03:00:00 1919 EDT isdst=1 gmtoff=-14400
…省略若干…
US/Eastern Sun Mar 9 06:59:59 2498 UTC = Sun Mar 9 01:59:59 2498 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 9 07:00:00 2498 UTC = Sun Mar 9 03:00:00 2498 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Nov 2 05:59:59 2498 UTC = Sun Nov 2 01:59:59 2498 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Nov 2 06:00:00 2498 UTC = Sun Nov 2 01:00:00 2498 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 8 06:59:59 2499 UTC = Sun Mar 8 01:59:59 2499 EST isdst=0 gmtoff=-18000
US/Eastern Sun Mar 8 07:00:00 2499 UTC = Sun Mar 8 03:00:00 2499 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Nov 1 05:59:59 2499 UTC = Sun Nov 1 01:59:59 2499 EDT isdst=1 gmtoff=-14400
US/Eastern Sun Nov 1 06:00:00 2499 UTC = Sun Nov 1 01:00:00 2499 EST isdst=0 gmtoff=-18000
US/Eastern 9223372036854689407 = NULL
US/Eastern 9223372036854775807 = NULL

五、附录

1. test_timezone.cpp

测试不同时区的mktime性能

#include <sys/time.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

typedef int64_t timestamp_t;

static timestamp_t get_timestamp()
{
    struct timeval tv = {};
    gettimeofday(&tv, );
     + (timestamp_t)tv.tv_usec;
}

struct calendar_time {
    int year;
    int month;
    int day;
    int hour;
    int minute;
    int second;
};

static void call_mktime(
        const calendar_time &calendar,
        int isdst)
{
    struct tm tm = {};
    tm.tm_year = calendar.year - ;
    tm.tm_mon  = calendar.month- ;
    tm.tm_mday = calendar.day;
    tm.tm_hour = calendar.hour;
    tm.tm_min  = calendar.minute;
    tm.tm_sec  = calendar.second;
    tm.tm_isdst = isdst;
     == mktime(&tm)) {
        abort();
    }
}

int main()
{
    const char *timeonzes[] = { "Asia/Shanghai", "US/Eastern", "America/Jujuy"};

    calendar_time times[] = {
        {, , , , , },
        {, , , , , },
        {, , , , , },
        {, , , , , },
        {, , , , , },
        {, , , , , },
        {, , , , , },
    };

    , , -};

    for (const auto &calendar: times) {
        ];

        for (const auto &tz: timeonzes) {
            for (const auto &isdst: isdsts) {
                setenv();
                timestamp_t t1 = get_timestamp();
                ;
                ; i < N; ++i) {
                    call_mktime(calendar, isdst);
                }
                timestamp_t t2 = get_timestamp();

                printf("calendar: %04d-%02d-%02d %02d:%02d:%02d ",
                        calendar.year, calendar.month, calendar.day,
                        calendar.hour, calendar.minute, calendar.second);

                printf("%-20s isdst=%2d rounds %d avg cost %4.2f us\n", tz, isdst, N, 1.0*(t2-t1)/N);
            }
            printf("\n");
        }
        printf("-------------------------------------------\n");
    }

    ;
}

2. test_setenv.cpp

测试setenv("TZ")和不设置时的性能差别。

#include <sys/time.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

typedef int64_t timestamp_t;

static timestamp_t get_timestamp()
{
    struct timeval tv = {};
    gettimeofday(&tv, );
     + (timestamp_t)tv.tv_usec;
}

struct calendar_time {
    int year;
    int month;
    int day;
    int hour;
    int minute;
    int second;
};

static void call_mktime(
        const calendar_time &calendar,
        int isdst)
{
    struct tm tm = {};
    tm.tm_year = calendar.year - ;
    tm.tm_mon  = calendar.month- ;
    tm.tm_mday = calendar.day;
    tm.tm_hour = calendar.hour;
    tm.tm_min  = calendar.minute;
    tm.tm_sec  = calendar.second;
    tm.tm_isdst = isdst;
     == mktime(&tm)) {
        abort();
    }
}

int main()
{
    const char *tz = "Asia/Shanghai";

    calendar_time times[] = {
        {, , , , , },
        {, , , , , },
        {, , , , , },
        {, , , , , },
    };

    for (const auto &calendar: times) {
        printf("calendar: %04d-%02d-%02d %02d:%02d:%02d\n",
                calendar.year, calendar.month, calendar.day,
                calendar.hour, calendar.minute, calendar.second);

        {
            unsetenv("TZ");
            timestamp_t t1 = get_timestamp();
            ;
            ; i < N; ++i) {
                call_mktime(calendar, );
            }
            timestamp_t t2 = get_timestamp();
            printf("unsetenv %-20s rounds %d avg cost %4ld us\n", tz, N, (t2-t1)/N);
        }

        {
            setenv();

            timestamp_t t1 = get_timestamp();
            ;
            ; i < N; ++i) {
                call_mktime(calendar, );
            }
            timestamp_t t2 = get_timestamp();
            printf("setenv   %-20s rounds %d avg cost %4ld us\n", tz, N, (t2-t1)/N);
        }
        printf("\n");
    }

    ;
}

六、参考文档

  1. https://github.com/lattera/glibc/blob/master/time/mktime.c

mktime性能问题调查的更多相关文章

  1. mktime性能问题

    #include <time.h> int main() { for (int i = 0; i < 100000; ++i) { struct tm tm = {}; tm.tm_ ...

  2. 测试mktime和localtime_r性能及优化方法

    // 测试mktime和localtime_r性能及优化方法 // // 编译方法:g++ -g -o x x.cpp或g++ -O2 -o x x.cpp,两种编译方式性能基本相同. // // 结 ...

  3. python 自然语言处理(六)____N-gram标注

    1.一元标注器(Unigram Tagging) 一元标注器利用一种简单的统计算法,对每个标注符分配最有可能的标记.例如:它将分配标记JJ给词frequent,因为frequent用作形容词更常见.一 ...

  4. Image Processing and Computer Vision_Review:Recent Advances in Features Extraction and Description Algorithms: A Comprehensive Survey——2017.03

    翻译 特征提取和描述算法的最新进展:全面的调查 摘要 - 计算机视觉是当今信息技术中最活跃的研究领域之一.让机器和机器人能够以视线的速度看到和理解周围的世界,创造出无穷无尽的潜在应用和机会.特征检测和 ...

  5. 利用Oracle RUEI+EM12c进行应用的“端到端”性能诊断

    概述 我们知道,影响一个B/S应用性能的因素,粗略地说,有以下几个大的环节: 1. 客户端环节 2. 网络环节(可能包括WAN和LAN) 3. 应用及中间层环节 4. 数据库层环节 能够对各个环节的问 ...

  6. PC虚拟现实应用的性能分析与优化:从CPU角度切入

    如今,虚拟现实 (VR) 技术正日益受到欢迎,这主要得益于遵循摩尔定律的技术进步让这一全新体验在技术上成为可能.尽管虚拟现实能给用户带来身临其境般的超凡体验,但相比传统应用,其具有双目渲染.低延迟.高 ...

  7. SQL 性能调优中可参考的几类Lock Wait

    在我们的系统出现性能问题时,往往避不开调查各种类型 Lock Wait,如Row Lock Wait.Page Lock Wait.Page IO Latch Wait等.从中找出可能的异常等待,为性 ...

  8. 怎么调试lua性能

    怎么调试lua性能 我们的游戏使用的是Cocos2dx-lua 3.9的项目,最近发现我们的游戏.运行比较缓慢.想做一次性能优化了.其实主要分为GPU.CPU的分别优化.GPU部分的优化.网上有很多优 ...

  9. Office 365使用情况调查不完全分析报告

    感谢大家参与了9月13日在Office 365技术群(O萌)中发起的一个关于Office 365使用情况的调查,在一天左右的时间内,我们一共收到了67份反馈,其中绝大部分是在3分钟内提交的. 本次调查 ...

随机推荐

  1. ucos互斥信号量解决优先级反转问题

    在可剥夺性的内核中,当任务以独占方式使用共享资源的时候,会出现低优先级任务高于高优先级任务运行的情况,这种情况叫做优先级反转,对于实时操作系统而言,这是一场灾难,下面我们来说说优先级反转的典型环境. ...

  2. PHP大小写问题

    PHP对于系统函数.用户自定义函数.类名称等是不区分大小写的如可以用EHCO也可以用echo调用显示函数, 但对于变量名称又是区分大小写的,如$Name和$NAME是2个不同的变量. 而对于文件名又因 ...

  3. 更深入一点理解switch语句及c/c++对const的处理

    首先看一到用 c 编写的程序/* -------------------- filename : ta.c --------------- */int switch_test_first( int x ...

  4. 安装arm-linux-gcc交叉编译器

    1.开发平台 虚拟机:VMware 12 操作系统:Ubuntu 14.04 2.准备交叉编译工具包(arm-linux-gcc-4.5.1) 编译uboot和linux kernel都需要gnu交叉 ...

  5. linux下启动tomcat----Cannot find ./catalina.sh

    参考:http://dearseven.blog.163.com/blog/static/1005379222013764440253/ linux 下启动tomcat [root@test233 b ...

  6. iOS 之 线性布局

    本来想自己写一个线性布局的类,看来不用了 ,网上已经有了,我先试试好不好用. https://github.com/youngsoft/MyLinearLayout 线性布局MyLinearLayou ...

  7. Bagging和Boosting 概念及区别

    Bagging和Boosting都是将已有的分类或回归算法通过一定方式组合起来,形成一个性能更加强大的分类器,更准确的说这是一种分类算法的组装方法.即将弱分类器组装成强分类器的方法. 首先介绍Boot ...

  8. centos6 安装mysql

    如果要在Linux上做j2ee开发,首先得搭建好j2ee的开发环境,包括了jdk.tomcat.eclipse的安装(这个在之前的一篇随笔中已经有详细讲解了Linux学习之CentOS(七)--Cen ...

  9. php判断IE浏览器

    <?php/** * 检测用户当前浏览器 * @return boolean 是否ie浏览器 */ function chk_ie_browser() { $userbrowser = $_SE ...

  10. Eclipse设置Tab键为空格!

    http://z-hua.iteye.com/blog/1056713 今天设置Eclipse中按Tab键为4个空格,这里标记下! Window-->Preferences-->Java- ...