为啥整数上的 in_array 严格模式比非严格模式慢?

Posted

技术标签:

【中文标题】为啥整数上的 in_array 严格模式比非严格模式慢?【英文标题】:Why is in_array strict mode on integers slower than non-strict mode?为什么整数上的 in_array 严格模式比非严格模式慢? 【发布时间】:2019-11-14 04:03:48 【问题描述】:

我一直认为in_array严格模式会更快或者至少和非严格模式一样的速度。但是经过一些基准测试后,我注意到在搜索整数时它们之间的执行时间存在巨大差异。字符串和数组测试表明严格模式更快。为什么?

测试代码 - (php 7.2.1):

<?php

$array = array_fill(0, 10000, 12345);

for ($i=0; $i<100000; $i++) 
    in_array($i, $array, true);


时间 php test.php

php -c test.php 12.98s user 0.04s system 98% cpu 13.234 total


<?php

$array = array_fill(0, 10000, 12345);

for ($i=0; $i<100000; $i++) 
    in_array($i, $array, false);


时间 php test.php

php -c test.php 6.44s user 0.04s system 99% cpu 6.522 total

【问题讨论】:

根据文档:If the third parameter strict is set to TRUE then the in_array() function will also **check the types** of the needle in the haystack. - 所以会有额外的操作(类型比较) @B001ᛦ 不确定 C 中的实际实现,但可以想象严格检查只是启用 === 而不是 ===== 在 PHP 代码中客观上更快。 在您的示例中,它们都匹配类型。如果没有一个类型匹配怎么办?尝试搜索字符串而不是整数?也许 in_array 首先检查类型,然后如果匹配,则检查是否相等。因此,如果类型匹配,则需要稍长一些,如​​果不匹配,则速度会稍快。 对于任何擅长 C 编程的人,请参阅 github.com/php/php-src/blob/master/ext/standard/array.c#L1616 和 github.com/php/php-src/blob/master/ext/standard/array.c#L1529 固定在github.com/php/php-src/commit/…。 【参考方案1】:

这似乎与针和/或干草堆中的元素类型有关,请注意:

来自http://sandbox.onlinephpfunctions.com/的PHP 7.3.5

$iterations = 10000000;
$needle = false;
$haystack = [ true ];

$start = microtime( true );
for( $i = 0; $i < $iterations; ++$i )

    in_array( $needle, $haystack, true );

echo ( microtime( true ) - $start ).' strict'.PHP_EOL;

$start = microtime( true );
for( $i = 0; $i < $iterations; ++$i )

    in_array( $needle, $haystack, false );

echo ( microtime( true ) - $start ).' not strict';

产生:

0.29996585845947 strict
0.40397191047668 not strict

但如果我们使用:

$needle = 1;
$haystack = [ 2 ];

然后我们得到:

0.34480714797974 strict
0.28275084495544 not strict

但是,PHP 5.6.29 产生的差异可以忽略不计,并且多次运行相同的测试可以将严格优先于非严格,反之亦然。

【讨论】:

在查看我的测试后,我注意到了相同的结果。 “int”是我发现的唯一受影响的。【参考方案2】:

我可以通过跟踪 in_array 的 C 源代码提供一些小见解。

事实证明,在比较整数时,到达非严格模式的实际相等性检查的路径比严格模式涉及的操作更少。

严格模式

in_arraystrict 标志为真的情况下,会发生以下情况:

    We call fast_is_identical_function for each element in the array

    fast_is_identical_function 第一 测试每个操作数的类型是否不同(Z_TYPE_P(op1) != Z_TYPE_P(op2)),希望能够尽早返回false;这是比较#1

    如果类型相同(在您的测试用例中它们是相同的),我们然后测试(Z_TYPE_P(op1) &lt;= IS_TRUE;我不知道这是做什么的,但它是比较#2

    在两个比较都评估为false、we jump into zend_is_identical 之后,我们的第一个函数调用。

    zend_is_identical再次 测试 Z_TYPE_P(op1) != Z_TYPE_P(op2) 开始,这是另一次提前失败的尝试。这是比较#3

    如果类型相同,我们可以通过switch (Z_TYPE_P(op1))语句下降,比较#4

    最后我们得到了比较Z_LVAL_P(op1) == Z_LVAL_P(op2),它实际上测试了两个值的相等性,comparison #5

总的来说,为了测试数组的每个元素是否等于我们正在搜索的值,有 5 次比较和 1 次函数调用。

非严格模式

相比之下,专门针对整数的非严格流程(真的是LONGs)要简单得多,如下:

    数组中的每个元素都不是fast_is_identical_function,而是we instead use fast_equal_check_function

    fast_equal_check_function 方法启动了一个更复杂的过程,将两个值与各种类型转换逻辑进行比较。但是,它所做的 first 测试恰好针对整数进行了优化,如下所示:

    if (EXPECTED(Z_TYPE_P(op1) == IS_LONG)) 
        if (EXPECTED(Z_TYPE_P(op2) == IS_LONG)) 
            return Z_LVAL_P(op1) == Z_LVAL_P(op2);
    

    我们可以看到它...

      立即测试op1的类型是否为LONG,是的,然后 立即测试op2的类型是否为LONG,是的,然后 立即返回Z_LVAL_P(op1) == Z_LVAL_P(op2)的结果

非严格情况下总共进行了 3 次简单相等比较和 0 次函数调用,而严格情况下至少进行了 5 次比较和 1 次跳转。

这似乎是这样一种情况,即尝试进行早期优化会使严格检查变慢(通过反复测试操作数的类型,希望我们能更快地找到不等式)而不是特定的非严格情况比较两个整数。

【讨论】:

所以这可能是下一个 PHP 版本中优化的地方,对吧? @tom 我不认为这真的是个问题。该代码已经进行了相当大的优化,您可能会冒险通过更改它来为某些其他类型的输入引入其他类型的性能问题。我认为最好将当前严格的性能视为正常,而将非严格的 long-long 情况视为一个不错的奖励。 是的。你说得对。我考虑过以非严格模式为代价来关注严格模式(如果需要提高严格模式的速度) @tom 这已在github.com/php/php-src/commit/… 中“修复” :)

以上是关于为啥整数上的 in_array 严格模式比非严格模式慢?的主要内容,如果未能解决你的问题,请参考以下文章

js中的严格模式和非严格模式的比较

为啥我的 JavaScript 在 Safari 的严格模式下无法正常工作?

为啥严格模式会使如此简单的动作如此不同?

为啥我的函数在严格模式(函数形式)下未定义?

为啥在严格模式下不允许使用八进制数字文字(以及解决方法是啥?)

为啥 `"foo".bar = 42;` 在 ES6 的严格模式下会抛出 `TypeError`?