【问题标题】:Why is strtolower slightly slower than strtoupper?为什么 strtolower 比 strtoupper 稍慢?
【发布时间】:2017-11-30 14:45:08
【问题描述】:

出于好奇,我做了一个实验。我想看看strtolower()strtoupper() 之间是否存在细微差别。我预计strtolower() 在大多数小写字符串上会更快,反之亦然。我发现strtolower() 在所有情况下都较慢(尽管在您执行数百万次之前,它的速度完全无关紧要。)这是我的测试。

$string = 'hello world';
$start_time = microtime();
for ($i = 0; $i < 10000000; $i++) {
    strtolower($string);
}
$timed = microtime() - $start_time;
echo 'strtolower ' . $string . ' - ' . $timed . '<br>';

strtolower()strtoupper() 重复使用hello worldHELLO WORLDHello WorldHere is the full gist. 我已经多次运行代码并得到大致相同的结果。这是下面的一次测试。

strtolower hello world - 0.043829
strtoupper hello world - 0.04062
strtolower HELLO WORLD - 0.042691
strtoupper HELLO WORLD - 0.015475
strtolower Hello World - 0.033626
strtoupper Hello World - 0.017022

我相信控制这个的 php-src github 中的 C 代码在这里是 strtolower() 和这里是 strtoupper()

需要明确的是,这不会阻止我使用strtolower()。我只是想了解这里发生了什么。

为什么strtolower()strtoupper() 慢?

【问题讨论】:

  • 为什么标记为 C?
  • @MichaelWalz 因为 PHP 是用 C 编写的?
  • 你为什么要浪费时间分配给$max?你在这两个版本上都浪费时间吗?
  • 我很好奇为什么人们还要费心去测试这些东西?
  • @TripleDeal 我的意思不是很明显吗?

标签: php c performance micro-optimization


【解决方案1】:

这主要取决于您当前使用的是哪种字符编码,但速度差异的主要原因是特殊字符的每个编码字符的大小。

取自babelstone.co.uk

例如,带有 caron (ǰ) 的小写 j 表示为单个编码字符 (U+01F0 LATIN SMALL LETTER J WITH CARON),但对应的大写字符 (J̌) 在 Unicode 中表示为 序列两个编码字符(U+004A 拉丁大写字母 J + U+030C 组合 CARON)。

在 Unicode 字符索引中筛选更多数据将不可避免地需要更长的时间。

请记住,strtolower 使用您当前的语言环境,因此如果您的服务器使用的字符编码不支持 strtolower 的特殊字符(例如 'Ê'),它只会返回特殊字符。然而,UTF-8 上的字符映射已设置,可以通过运行mb_strtolower 来确认。

还可以将属于 uppercase 类别的字符数与您将在 lowercase 类别中找到的字符数进行比较,但同样,这取决于您的字符编码。

简而言之,strtolower 有一个更大的字符数据库,用于在检查字符是否为 uppercase 时将每个单独的字符串字符与它进行比较。

【讨论】:

  • 我不认为 strtolower/upper 做这种 unicode 编码,它只适用于纯 ASCII。我刚刚尝试了echo strtoupper('ç'); 并得到了ç。也许 mb_* 函数会这样做,但不是常规函数。
  • @this.lau_ 谢谢,据我所知,如果您的服务器语言环境已经使用 UTF-8,那么无论如何都应该进行转换。如果我正确理解推理,mb_strtolower 函数将用于强制 UTF-8 字符映射。我已经调整了答案以显示这一点。
【解决方案2】:

代码的实现有几个非常细微的差别:

PHPAPI char *php_strtoupper(char *s, size_t len)
{
    unsigned char *c, *e;

    c = (unsigned char *)s;
    e = (unsigned char *)c+len;    <-- strtolower uses e = c+len;

    while (c < e) {
        *c = toupper(*c);
        c++;
    }
    return s;
}

PHPAPI zend_string *php_string_toupper(zend_string *s)
{
    unsigned char *c, *e;

    c = (unsigned char *)ZSTR_VAL(s);
    e = c + ZSTR_LEN(s);

    while (c < e) {
        if (islower(*c)) {
            register unsigned char *r;
            zend_string *res = zend_string_alloc(ZSTR_LEN(s), 0);

            if (c != (unsigned char*)ZSTR_VAL(s)) {
                memcpy(ZSTR_VAL(res), ZSTR_VAL(s), c - (unsigned char*)ZSTR_VAL(s));
            }
            r = c + (ZSTR_VAL(res) - ZSTR_VAL(s));
            while (c < e) {
                *r = toupper(*c);
                r++;
                c++;
            }
            *r = '\0';
            return res;
        }
        c++;
    }
    return zend_string_copy(s);
}

PHP_FUNCTION(strtoupper)
{
    zend_string *arg;      <-- strtolower uses zend_string *str;

    ZEND_PARSE_PARAMETERS_START(1, 1)
        Z_PARAM_STR(arg)          <-- strtolower uses Z_PARAM_STR(str)
    ZEND_PARSE_PARAMETERS_END();

    RETURN_STR(php_string_toupper(arg));     <-- strtolower uses RETURN_STR(php_string_tolower(str));
}

对于strtolower

PHPAPI char *php_strtolower(char *s, size_t len)
{
    unsigned char *c, *e;

    c = (unsigned char *)s;
    e = c+len;                  <-- strtoupper uses e = (unsigned char *)c+len;

    while (c < e) {
        *c = tolower(*c);
        c++;
    }
    return s;
}

PHPAPI zend_string *php_string_tolower(zend_string *s)
{
    unsigned char *c, *e;

    c = (unsigned char *)ZSTR_VAL(s);
    e = c + ZSTR_LEN(s);

    while (c < e) {
        if (isupper(*c)) {
            register unsigned char *r;
            zend_string *res = zend_string_alloc(ZSTR_LEN(s), 0);

            if (c != (unsigned char*)ZSTR_VAL(s)) {
                memcpy(ZSTR_VAL(res), ZSTR_VAL(s), c - (unsigned char*)ZSTR_VAL(s));
            }
            r = c + (ZSTR_VAL(res) - ZSTR_VAL(s));
            while (c < e) {
                *r = tolower(*c);
                r++;
                c++;
            }
            *r = '\0';
            return res;
        }
        c++;
    }
    return zend_string_copy(s);
}

PHP_FUNCTION(strtolower)
{
    zend_string *str;     <-- strtoupper uses zend_string *arg; 

    ZEND_PARSE_PARAMETERS_START(1, 1)
        Z_PARAM_STR(str)        <-- strtoupper uses Z_PARAM_STR(arg)
    ZEND_PARSE_PARAMETERS_END();

    RETURN_STR(php_string_tolower(str));    <-- strtoupper uses RETURN_STR(php_string_tolower(arg));
}

这些微小的差异是否足以影响那几纳秒的性能,我不知道....不确定为什么甚至存在差异

【讨论】:

  • 大概就是这样 - 微小的实现差异显示了数百万次操作的微小差异。
  • 一个区别是变量的命名方式,而另一个区别是包含了冗余强制转换。我非常怀疑这些有什么不同。
  • 我觉得这个答案不够。在查看我链接的源代码时,我已经注意到了这些差异,但由于我不懂 C 语言,所以没有理解这些差异。这个答案对我来说并没有太大的意义。也许有人可以解释为什么存在差异以及它们可能产生的影响。
  • 确实,argstr 只是一个变量名,不可能有所作为。演员表...我不太确定,但是 PHP 7 不使用它 - php_strtolower()php_strtoupper() 是来自 PHP 5 源的遗留内部函数(一些扩展使用他们)。如果有任何真正的区别(这是我们拥有的一个幼稚的基准),它将来自 C 自己的 islower()isupper()tolower()toupper() 函数。
猜你喜欢
  • 2011-03-23
  • 2013-06-06
  • 2019-12-18
  • 2015-10-04
  • 2012-09-16
  • 2016-10-06
  • 2020-11-27
  • 2011-10-19
  • 2020-12-24
相关资源
最近更新 更多