如果我们从代码中设置时区,为什么 date() 的工作速度会提高一倍?

2022-01-16 00:00:00 datetime timezone php micro-optimization

您是否注意到,如果您在任何 date() 之前在脚本中设置实际时区,date() 函数的运行速度比平常快 2 倍称呼?我对此非常好奇.

Have you noticed that date() function works 2x faster than usual if you set actual timezone inside your script before any date() call? I'm very curious about this.

看看这段简单的代码:

<?php

  $start = microtime(true);
  for ($i = 0; $i < 100000; $i++) date('Y-m-d H:i:s');
  echo (microtime(true) - $start);

?>

它只是使用 for 循环调用 date() 函数 100,000 次.我得到的结果总是在 1.6 秒 左右(Windows,PHP 5.3.5)但是……

It just calls date() function using for loop 100,000 times. The result I’ve got is always around 1.6 seconds (Windows, PHP 5.3.5) but…

如果我再次设置相同的时区,在开始前添加一条荒谬的线:

If I set same time zone again adding one absurd line before start:

date_default_timezone_set(date_default_timezone_get());

我得到的时间低于 800 毫秒;~ 2 倍快(相同的服务器).

I get a time below 800ms; ~2x faster (same server).

我四处寻找对此行为的任何合理解释,但没有任何成功.从我的角度来看,这个额外的行是没用的,但 PHP 不同意我的观点.

I was looking around to find any reasonable explanation for this behavior but did not have any success. From my angle, this additional line is useless but PHP doesn’t agree with me.

我在两台 linux 服务器(不同的 PHP 版本)上尝试了这个测试,得到了不同的结果时间,但比例 ~6:1.

I have tried this test on two linux servers (different PHP versions) and got different resulting times but in proportion ~6:1.

注意:php.ini 中的 date.timezone 属性已正确设置(欧洲/巴黎).

Note: date.timezone property in php.ini has been properly set (Europe/Paris).

我在这里搜索相关问题,但没有找到类似的问题.我还检查了 date_default_time_zone() 函数 @ php 的手册.net 发现我不仅注意到了这一点,但仍然无法理解为什么会发生这种情况?

I was searching for related questions here and did not find anything similar. I've also checked manual for date_default_time_zone() function @ php.net and found that I'm not only one who noticed this, but still can't understand why that happens?

有人吗?

推荐答案

PHP 5.4 更新:

date_default_timezone_get,从PHP 5.4.0开始算法从系统信息中猜测时区已从代码中删除(与 PHP 5.3 源代码相比),因此此行为不再存在.

Update for PHP 5.4:

As documented in the description of date_default_timezone_get, starting from PHP 5.4.0 the algorithm to guess the timezone from system information has been removed from the code (contrast with the PHP 5.3 source) so this behavior no longer exists.

在我的开发服务器上运行计时测试以查看它的实际效果,我得到:

Running the timing test on my dev server to see it in action, I got:

  • PHP 5.3.11:~720 毫秒
  • PHP 5.4.3:~470 毫秒

我刚刚研究了 PHP 源代码.具体来说,所有相关代码都在 /ext/date/php_date.c.

I 've just looked into PHP source. Specifically, all relevant code is in /ext/date/php_date.c.

我开始假设如果您不为 date 提供时区,则调用 date_default_timezone_get 来获取时区.这是那个函数:

I started with the assumption that if you don't provide a timezone for date, date_default_timezone_get is called to get one. Here's that function:

PHP_FUNCTION(date_default_timezone_get)
{
    timelib_tzinfo *default_tz;

    default_tz = get_timezone_info(TSRMLS_C);
    RETVAL_STRING(default_tz->name, 1);
}

好的,那么 get_timezone_info 是什么样的?这个:

OK, so what does get_timezone_info look like? This:

PHPAPI timelib_tzinfo *get_timezone_info(TSRMLS_D)
{
    char *tz;
    timelib_tzinfo *tzi;

    tz = guess_timezone(DATE_TIMEZONEDB TSRMLS_CC);
    tzi = php_date_parse_tzfile(tz, DATE_TIMEZONEDB TSRMLS_CC);
    if (! tzi) {
        php_error_docref(NULL TSRMLS_CC, E_ERROR, "Timezone database is corrupt - this should *never* happen!");
    }
    return tzi;
}

guess_timezone 呢?这里是:

static char* guess_timezone(const timelib_tzdb *tzdb TSRMLS_DC)
{
    char *env;

    /* Checking configure timezone */
    if (DATEG(timezone) && (strlen(DATEG(timezone)) > 0)) {
        return DATEG(timezone);
    }
    /* Check environment variable */
    env = getenv("TZ");
    if (env && *env && timelib_timezone_id_is_valid(env, tzdb)) {
        return env;
    }
    /* Check config setting for default timezone */
    /*  ..... code omitted ....... */
#if HAVE_TM_ZONE
    /* Try to guess timezone from system information */
    /*  ..... code omitted ....... */
#endif
#ifdef PHP_WIN32
    /*  ..... code omitted ....... */
#elif defined(NETWARE)
    /*  ..... code omitted ....... */
#endif
    /* Fallback to UTC */
    php_error_docref(NULL TSRMLS_CC, E_WARNING, DATE_TZ_ERRMSG "We had to select 'UTC' because your platform doesn't provide functionality for the guessing algorithm");
    return "UTC";
}

好的,那么它如何与 date_default_timezone_set 交互?让我们看看 那个函数:

PHP_FUNCTION(date_default_timezone_set)
{
    char *zone;
    int   zone_len;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &zone, &zone_len) == FAILURE) {
        RETURN_FALSE;
    }
    if (!timelib_timezone_id_is_valid(zone, DATE_TIMEZONEDB)) {
        php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Timezone ID '%s' is invalid", zone);
        RETURN_FALSE;
    }
    if (DATEG(timezone)) {
        efree(DATEG(timezone));
        DATEG(timezone) = NULL;
    }
    DATEG(timezone) = estrndup(zone, zone_len);
    RETURN_TRUE;
}

长话短说:如果您调用一次 date_default_timezone_set,那么 guess_timezone 会采用从 timezone 变量读取的快速路径(第一个条件满足,立即返回).否则需要一些时间来计算默认时区,它没有被缓存(我猜是为了简单起见),如果你在循环中这样做,延迟就会开始显示.

Long story short: if you call date_default_timezone_set once, then guess_timezone takes the fast path of reading from the timezone variable (the very first conditional is satisfied, and it returns immediately). Otherwise it takes some time to work out the default timezone, which is not cached (I guess for simplicity), and if you do that in a loop the delay starts to show.

相关文章