为什么在代码中设置时区后,date()函数的速度会快两倍?

32

你有没有注意到,如果在脚本的任何 date() 调用之前设置实际时区, date() 函数会比平常快2倍?我对此非常好奇。

看看这个简单的代码:

<?php

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

?>

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

如果我在开始之前再添加一行荒谬的代码来设置相同的时区:

date_default_timezone_set(date_default_timezone_get());

我得到了一个低于800ms的时间;比原来快了约2倍(相同服务器)。

我在寻找任何合理的解释这种行为但没有成功。从我的角度来看,这个额外的代码行是无用的,但PHP并不同意我。

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

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

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

有人能提供答案吗?


3
哇,行为重复了,5.3.3在CentOS 5.x上(但分别为0.65秒和0.31秒)。如果这在5.3.6中仍然发生(如果没有其他人测试的话,我将能够在本周晚些时候测试),我认为应该报告一个错误。 - Charles
@Charles - 是的...在Linux上,PHP 5.3.2中,它是4.16和0.7(没有时区始终约为4秒,有时区则为0.7或更少)。 - Wh1T3h4Ck5
抱歉,它是PHP 5.2.13版本,在5.3.2版本中是2:1。 - Wh1T3h4Ck5
2个回答

42

PHP 5.4更新内容:

根据date_default_timezone_get的说明,从PHP 5.4.0开始,从系统信息中猜测时区的算法已从代码中删除(与PHP 5.3代码相比)。因此,这种行为已不再存在。

在我的开发服务器上运行定时测试以查看其运作情况,结果如下:

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

原始答案:

我刚刚查看了PHP源代码。具体而言,所有相关代码都在/ext/date/php_date.c中。

我假设如果您没有为date提供时区,则会调用date_default_timezone_get来获取时区。这是该函数

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看起来是什么样子?可以参考这里

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变量中读取(满足第一个条件并立即返回)。否则,它需要花费一些时间来确定默认时区,该时区未被缓存(我猜是为了简单起见),如果在循环中执行此操作,则延迟开始显示。

4
干得好。所以这并不是由PHP层级错误触发的。我会删除我的。 - Pekka
1
由于没有人会运行代码100k次,他们不会费心缓存猜测的时区。 - nerkn
哇,谢谢Jon,这真是一项不错的工作...看来我没有朝正确的方向寻找。我对这个答案完全满意。再次感谢。 - Wh1T3h4Ck5
@Jon,有趣...我没有注意到...感谢更新。现在它是如何工作的?猜测它使用一些变量使得TZ值可以从日期函数中访问而无需猜测(我们可以手动从代码中更改)。 - Wh1T3h4Ck5
@Wh1T3h4Ck5:有两个变量timezonedefault_timezone可以影响结果;如果没有设置,则系统默认为UTC。 - Jon
显示剩余2条评论

2

我想它必须每次调用时确定时区,除非明确指定,这会增加函数运行时间。

但是,这真的很重要吗?你可能会编写多少次调用date() 100,000次的脚本呢?


3
如果在php.ini中设置了默认时区,为什么它还需要确定自己的时区?请问原因是什么? - Pekka
这个问题与加速无关,而是与PHP内部有关。 - nerkn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接