一、问题提出

会议中有同学提到使用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. XCode里的模拟器到底在哪里?我的App被放到哪里了?如何寻找真机的沙盒文件?

    一. 开发iOS,必然少不了和XCode这个家伙打交道.平时我们调试自己的App的时候,最常用到的就是模拟器Simulator了,调试的时候,我们的App会自动被XCode安装到模拟器中去,不过: 你 ...

  2. UDP网络程序模型设计

    UDP网络程序设计 1. UDP网络编程模型程序初始化 1.1服务器使用的函数 创建socket----->socket 绑定地址-------->bind 接受数据--------> ...

  3. CSS判断不同分辨率显示不同宽度布局CSS3技术支持IE6到IE8

    CSS判断不同分辨率浏览器(显示屏幕)显示不同宽度布局CSS3技术支持IE6到IE8.将用到css3 @media样式进行判断,但IE9以下版本不支持CSS3技术,这里DIVCSS5给大家介绍通过JS ...

  4. 浅析IoC框架

    今日拜读了一篇关于IOC的文章,特意转载,和大家分享一下 1 IoC理论的背景    我们都知道,在采用面向对象方法设计的软件系统中,它的底层实现都是由N个对象组成的,所有的对象通过彼此的合作,最终实 ...

  5. leetcode--011 copy list with random pointer

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAA3UAAABjCAIAAACzC75sAAAMTElEQVR4nO3cyYHivBYG0D8n0nIojo ...

  6. IOS开发-UI学习-NSBundle和NSURL的区别(读取文件以及写入文件)

    NSBundle和NSURL的区别: 在项目的工程中添加一个文件,本例程添加的是aa.txt,文件的内容为百度: www.baidu.com,现在要使用NSBundle和NSURL分别去获取内容,代码 ...

  7. IOS开发-OC学习-Info.plist文件解析

    Info.plist文件是新建ios项目完成后自动生成的一个配置文件,在Xcode中如下图: 通过解析可以获得配置的具体细节,解析过程如下: // 定义一个nsstring用来获取Info.plist ...

  8. Zookeeper的基本概念

    Reference:  http://mp.weixin.qq.com/s?src=3&timestamp=1477979201&ver=1&signature=bBZqNrN ...

  9. 用python实现模拟登录人人网

    用python实现模拟登录人人网 字数4068 阅读1762 评论19 喜欢46 我决定从头说起.懂的人可以快速略过前面理论看最后几张图. web基础知识 从OSI参考模型(从低到高:物理层,数据链路 ...

  10. 在vhd中安装win7,并建立分差vhd

    准备:硬盘分区激活第一个分区; imagex.exe; install.wim; winpe boot pc 1.cmd命令下,创建主vhd      (1)diskpart       (打开dis ...