一、问题提出

会议中有同学提到使用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. s3c2440的GPIO驱动

    多个通用的GPIO,同时这些端口也拥有一些复用功能(如ADC输入),有部分端口只能输入,有部分端口只能输出,今天我们来看看如何设置一个GPIO的输出电平以及如何获取一个端口的GPIO电平 对GPIO进 ...

  2. easyui 异步json tree跨域访问问题解决

    最近在用easyui中的异步tree时发现了跨域访问问题,我们都知道jquery ajax提供get请求的跨域访问.所以解决easyui tree跨域访问的问题便是将数据通过jquery ajax将数 ...

  3. angular登录状态检查

    待补充!!!!!!!!!!! 参加链接: http://www.brafox.com/post/2015/javascript/angularjs/angularjs-router-intercept ...

  4. iOS调试-LLDB学习总结

    from:http://www.jianshu.com/p/d6a0a5e39b0e LLDB阐述 LLDB 是一个有着 REPL 的特性和 C++ ,Python 插件的开源调试器.LLDB 绑定在 ...

  5. UVa 727 - Equation

    题目大意:给一个中缀表达式,转换成后缀表达式. 这类题一直不太会,让我想就是建一棵表达式树,然后后续遍历算了,可是建树的过程实在太麻烦了.今天才看到有中缀表达式转换成后缀表达式的算法,可以用栈进行实现 ...

  6. java系列--过滤器

    在web.xml配置过滤器:过滤器一定要放在所以Servlet前面 过滤器的生命周期: 过滤器的应用: 1.编码格式 2.权限验证 3.数据库关闭

  7. UVa 10827 - Maximum sum on a torus

    题目大意:UVa 108 - Maximum Sum的加强版,求最大子矩阵和,不过矩阵是可以循环的,矩阵到结尾时可以循环到开头.开始听纠结的,想着难道要分情况讨论吗?!就去网上搜,看到可以通过补全进行 ...

  8. iOS 类库列表

    1. LinqToObjectiveC  #import "NSArray+LinqExtensions.h" 它为NSArray添加了许多方法,能让你用流式API来转换.排序.分 ...

  9. 用蓝牙芯片CC2541/CC2540实现一个智能恒温箱

    最近突然想自己做一个智能小冰箱玩一玩,于是决定动手试一试. 成品效果图 原材料 半导体制冷片一只 散热风扇 12V电源一台 智能恒温箱电路板 控制板的PCB图 原理图 供电部分原理图 制冷片控制部分原 ...

  10. mysql 自带的压力测试

    注意mysqlslap 是mysql5.1之后新添加的功能,之前的版本是没有此功能的