що швидше: in_array або isset? [зачинено]


96

Це питання стосується лише мене, оскільки я завжди люблю писати оптимізований код, який може працювати також на дешевих повільних серверах (або серверах з ВЕЛИКИМ трафіком)

Я озирнувся і не зміг знайти відповіді. Мені було цікаво, що швидше між цими двома прикладами, маючи на увазі, що ключі масиву в моєму випадку не важливі (псевдокод, природно):

<?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 )


3
Якщо ви завжди прагнете написати оптимізований код, ви, звичайно, використовуєте профайлер, то раз у раз?
Маріо

59
Я голосую за відкриття. Питання добре сформоване, а відповіді підтверджені фактами та посиланнями. Хоча це мікрооптимізація , ці типи питань є конструктивними .
Джейсон Маккрірі

5
@JasonMcCreary секунда; ще лише один.
Ja͢ck

7
Це пройшло багато років потому, але я б навіть не вважав це мікрооптимізацією. Для великих наборів даних це може суттєво змінити !!
Роберт

2
... це питання для мене виглядає "конструктивним". Я розпочну ще одну кампанію з повторного відкриття.
mickmackusa

Відповіді:


117

Поки що відповіді на місця. Використання issetв цьому випадку швидше, оскільки

  • Він використовує хеш-пошук O (1) по ключу, тоді як in_arrayповинен перевіряти кожне значення, поки не знайде збіг.
  • Будучи операційним кодом, він має менше накладних витрат, ніж виклик in_arrayвбудованої функції.

Їх можна продемонструвати, використовуючи масив зі значеннями (10000 у тесті нижче), що змушує in_arrayпроводити додатковий пошук.

isset:    0.009623
in_array: 1.738441

Це базується на еталоні Джейсона, заповнюючи деякі випадкові значення та час від часу знаходячи значення, яке існує в масиві. Всі випадкові, тому остерігайтеся, що часи будуть коливатися.

$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;

Я знаю про хеші, але цікаво, чому щось подібне не робиться зі значеннями масивів, коли це можливо для пришвидшення функцій, це також зменшить споживання пам'яті, якщо використовувати подібні значення, просто додавши додаткове хешування значення .. правильно?
Фабріціо

3
@Fabrizio - Значення масиву можуть дублюватися і містити не хеш-об'єкти. Клавіші повинні бути унікальними та мати лише рядки та цілі числа, що робить їх легко хешируемыми. Незважаючи на те, що ви можете створити карту "один на один", яка хеш і ключі, і значення, це не те, як працює масив PHP.
Девід Харкнесс

3
Якщо ви впевнені, що ваш масив містить унікальні значення, існує інший варіант - flip + isset .
Аркадій Кужель

варто зазначити, що перевернутий набір усе ще швидший у цьому прикладі, ніж in_array: `` $ start = microtime (true); $ foo = array_flip ($ a); for ($ i = 0; $ i <10000; ++ $ i) {isset ($ foo [rand (1, 1000000)]); } $ total_time = мікрочас (true) - $ start; echo "Загальний час (перевернутий isset):", формат_числа ($ total_time, 6), PHP_EOL;
Андре Баумеєр,

@AndreBaumeier Що швидше, залежатиме від розміру масиву та кількості тестів. Перегортання десятитисячного масиву елементів для проведення трьох тестів, мабуть, неефективно.
Девід Харкнес

42

Що швидше: 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;

Додаткові ресурси

Я закликаю вас також переглянути:


Гарне рішення. Я здивований, що більше людей не розбивають час на свої функції / код більше за допомогою microtime()інших інструментів. Неймовірно цінно.
nickhar

1
Пошук порожнього масиву за тим самим ключем лише підкреслює накладні витрати на виклик in_arrayфункції порівняно з використанням issetвбудованого. Це було б краще з масивом, що містить купу випадкових ключів і зрідка шукаючи наявний ключ / значення.
Девід Харкнесс,

Я досить багато використовую тести та мікрочас, але я також зрозумів, коли тестував whileі foreachщо при кожному оновленні я отримував різних "переможців". це завжди залежить від забагато змінних сервера, і найкраще повторити дуже велику кількість разів у різний час і отримати того, хто виграє частіше, або просто знати, що відбувається у фоновому режимі, і знати, що він буде остаточним переможцем незважаючи ні на що
Фабріціо

@David Harkness, ти вже підібрав мою відповідь. Якщо ви хочете більше, станьте мені на плечі та опублікуйте власну відповідь. :) Тим не менше, якщо накладні витрати на функцію вже значно дорожчі порівняно з isset(), що змушує вас думати, що передача їй більшого масиву зробить це швидшим ?
Джейсон Маккрірі,


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

Мало того, що in_array()використовується відносно неефективний O(n)пошук, його також потрібно викликати як функцію ( DO_FCALL), тоді як для цього isset()використовується єдиний код операції ( ZEND_ISSET_ISEMPTY_DIM_OBJ).


7

Другий був би швидшим, оскільки він шукає лише цей конкретний ключ масиву і йому не потрібно перебирати весь масив, поки він не буде знайдений (буде розглядати кожен елемент масиву, якщо його не знайти)


але це також залежить від того, де знаходиться глобальний обшук вар-ва
el Dude

@ EL2002, чи можете Ви, будь ласка, детальніше описати це твердження?
Фабріціо

1
Майк, не дивився б на цілий масив, навіть isset()якщо його не знайти?
Фабріціо

1
@Fabrizio Ні, йому не потрібно повторювати. Внутрішньо (в C) масив PHP - це просто хеш-таблиця. Для пошуку одного значення індексу C просто робить хеш цього значення і шукає призначене йому місце в пам'яті. Там є або значення, або його немає.
Mike Brant

1
@Fabrizio У цій статті наведено хороший огляд того, як масиви внутрішньо представлені в C за допомогою PHP. nikic.github.com/2012/03/28/…
Майк Брант
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.