什么更快:in_array 还是 isset?

108

这个问题只是为了我自己,因为我总是喜欢编写可以在廉价慢速服务器上运行(或者在流量很大的服务器上运行)的优化代码。

我找了一圈,但没有找到答案。我想知道在这两个示例中哪一个更快,同时要记住,在我的情况下,数组键不重要(伪代码自然):

<?php
$a = array();
while($new_val = 'get over 100k email addresses already lowercased'){
    if(!in_array($new_val, $a){
        $a[] = $new_val;
        //do other stuff
    }
}
?>

<?php
$a = array();
while($new_val = 'get over 100k email addresses already lowercased'){
    if(!isset($a[$new_val]){
        $a[$new_val] = true;
        //do other stuff
    }
}
?>

由于问题的焦点不在于数组碰撞,如果你担心$a[$new_value]的插入碰撞,你可以使用$a[md5($new_value)]。虽然仍然可能会导致碰撞,但可以避免读取来自用户提供的文件时可能发生的DoS攻击(http://nikic.github.com/2011/12/28/Supercolliding-a-PHP-array.html)。


4
如果你一直在努力编写优化的代码,那么你肯定会时不时地使用分析器,对吧? - mario
66
我投票支持重新开放。该问题表述清晰,答案依据事实和参考资料。虽然这是一种微小的优化,但这类问题是有建设性的。 - Jason McCreary
5
@JasonMcCreary,第二个;再来一个就好了。 - Ja͢ck
7
这是多年以后的事情,但我甚至不认为这是微小的优化。对于大型数据集,这可能会产生很大的差异!! - Robert
4
我认为这个问题看起来是“有建设性的”。我将开始另一次重新开放的活动。 - mickmackusa
显示剩余9条评论
4个回答

133

到目前为止,回答都是准确的。在这种情况下使用 isset 更快,因为:

  • 它在键上使用了 O(1) 的哈希搜索,而 in_array 必须检查每个值,直到找到匹配项。
  • 作为一个操作码,它比调用内置函数 in_array 的开销要少。

这些可以通过使用一个包含值的数组(以下测试中有 10,000 个值)来演示,强制 in_array 进行更多搜索。

isset:    0.009623
in_array: 1.738441

在 Jason 的基础上,通过填入一些随机值并偶尔找到存在于数组中的值来构建。所有值都是随机的,因此请注意时间会波动。

$a = array();
for ($i = 0; $i < 10000; ++$i) {
    $v = rand(1, 1000000);
    $a[$v] = $v;
}
echo "Size: ", count($a), PHP_EOL;

$start = microtime( true );

for ($i = 0; $i < 10000; ++$i) {
    isset($a[rand(1, 1000000)]);
}

$total_time = microtime( true ) - $start;
echo "Total time: ", number_format($total_time, 6), PHP_EOL;

$start = microtime( true );

for ($i = 0; $i < 10000; ++$i) {
    in_array(rand(1, 1000000), $a);
}

$total_time = microtime( true ) - $start;
echo "Total time: ", number_format($total_time, 6), PHP_EOL;

我知道哈希表,但想知道为什么在可能的情况下没有对数组值进行类似的操作以加快函数速度,如果相似的值被使用,通过在值上添加额外的哈希处理也可以减少内存消耗.. 对吗? - Fabrizio
3
@Fabrizio - 数组的值可以重复且包含非可哈希对象。键必须是唯一的,只能是字符串和整数,这使它们易于哈希。虽然您可以创建一个将键和值都哈希化的一对一映射,但这不是 PHP 数组的工作方式。 - David Harkness
3
如果您确定数组中的值是唯一的,那么还有另一个选项-flip+isset - Arkadij Kuzhel
值得注意的是,在这个例子中,翻转后的isset仍然比in_array更快:$start = microtime( true); $foo = array_flip($a); for ($i = 0; $i < 10000; ++$i) { isset($foo[rand(1, 1000000)]); } $total_time = microtime( true ) - $start; echo "总时间(翻转后的isset):", number_format($total_time, 6), PHP_EOL; - Andre Baumeier
@AndreBaumeier 具体哪个更快将取决于数组的大小以及您将进行多少次测试。翻转一个有一万个元素的数组来执行三次测试可能不是很高效。 - David Harkness
我知道这些都是古老的东西,但在安德烈的代码中,你要翻转的数组的大小并不重要。然而,更好的测试方法是在循环内部翻转数组。在这种情况下,数组翻转的性能表现不佳。 - jonlink

49

哪个更快:isset()in_array()

isset()更快。

显然,isset()仅测试单个值。而in_array()将遍历整个数组,测试每个元素的值。

使用microtime()进行粗略基准测试非常容易。

结果:

Total time isset():    0.002857
Total time in_array(): 0.017103

注意:结果不管存在与否,都是相似的。

代码:

<?php
$a = array();
$start = microtime( true );

for ($i = 0; $i < 10000; ++$i) {
    isset($a['key']);
}

$total_time = microtime( true ) - $start;
echo "Total time: ", number_format($total_time, 6), PHP_EOL;

$start = microtime( true );

for ($i = 0; $i < 10000; ++$i) {
    in_array('key', $a);
}

$total_time = microtime( true ) - $start;
echo "Total time: ", number_format($total_time, 6), PHP_EOL;

exit;

额外资源

我建议你也查看以下内容:


1
在空数组中搜索相同的键只会突显调用in_array函数的开销,而不是使用内置的isset更好。最好使用包含一堆随机键并偶尔搜寻现有键/值的数组。 - David Harkness
我确实经常使用基准测试和微秒计,但是当我测试while和foreach时,我也意识到每次刷新都会得到不同的“赢家”。这总是取决于太多的服务器变量,最好的方法是在不同的时间内迭代很多次并获得更多胜利者,或者只是了解后台发生了什么,并知道它将成为最终的赢家,无论如何。 - Fabrizio
@JasonMcCreary,实际上我确实明白他想说什么。你的例子甚至更加显示了in_array较慢,但是内部会发生什么呢?是一个使用trai而另一个扫描数组还是...? - Fabrizio
1
@Fabrizio - 阅读有关hashing函数hash表的内容。 - David Harkness
@DavidHarkness 我知道哈希,但想知道为什么在可能的情况下对数组值不执行类似操作以加快函数速度,如果相似的值被使用,通过在值上添加额外的哈希可以减少内存消耗..正确吗? - Fabrizio
显示剩余5条评论

19

使用isset()可以利用更快的查找速度,因为它使用哈希表,避免了需要进行O(n)搜索的需求。

首先使用djb哈希函数将关键字进行哈希运算,以确定在O(1)时间内具有相似哈希值的桶。然后迭代地搜索该桶,直到在O(n)中找到确切的关键字。

除非出现故意的哈希碰撞,否则这种方法比in_array()更具性能优势。

请注意,在像您展示的方式中使用isset(),将最终值传递给另一个函数需要使用array_keys()创建一个新数组。通过将数据存储在键和值中,可以做出内存折衷。

更新

想要了解你的代码设计决策如何影响运行时性能,可以查看脚本的编译版本:

echo isset($arr[123])

compiled vars:  !0 = $arr
line     # *  op                           fetch      ext  return  operands
-----------------------------------------------------------------------------
   1     0  >   ZEND_ISSET_ISEMPTY_DIM_OBJ              2000000  ~0      !0, 123
         1      ECHO                                                 ~0
         2    > RETURN                                               null

echo in_array(123, $arr)

compiled vars:  !0 = $arr
line     # *  op                           fetch      ext  return  operands
-----------------------------------------------------------------------------
   1     0  >   SEND_VAL                                             123
         1      SEND_VAR                                             !0
         2      DO_FCALL                                 2  $0      'in_array'
         3      ECHO                                                 $0
         4    > RETURN                                               null
< p> in_array() 不仅使用相对低效的 O(n) 搜索,而且需要作为函数(DO_FCALL)调用,而 isset() 则使用单个操作码(ZEND_ISSET_ISEMPTY_DIM_OBJ)进行此操作。


8
第二种方法速度更快,因为它仅查找特定的数组键,并且不需要在找到之前迭代整个数组(如果未找到,则将查看每个数组元素)。

但也取决于所搜寻变量在全局作用域中的位置。 - el Dude
@EL2002,您能否详细说明一下那个声明? - Fabrizio
1
Mike,如果使用isset()函数没有找到整个数组,那么他不会查看整个数组吗? - Fabrizio
@Fabrizio 不需要迭代数组来评估键,因为查找将直接在指定的数组键上执行。 - Mike Brant
1
@Fabrizio 不需要迭代。在C语言内部,PHP数组只是一个哈希表。为了查找单个索引值,C只需对该值进行哈希并查找其在内存中分配的位置。那里要么有一个值,要么没有。 - Mike Brant
1
@Fabrizio 这篇文章很好地概述了PHP中数组是如何通过C语言内部表示的。http://nikic.github.com/2012/03/28/Understanding-PHPs-internal-array-implementation.html - Mike Brant

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