PHP in_array () ужасная производительность. Самый верный способ поиска значения в массиве

#php #arrays

#php #массивы

Вопрос:

У меня есть следующий простой код для проверки на коллизию с первичным ключом, который я создаю:

 $machine_ids = array();

for($i = 0; $i < 100000; $i  ) {
    //Generate machine id returns a 15 character alphanumeric string
    $mid = Functions::generate_machine_id();

    if(in_array($mid, $machine_ids)) {
        die("Collision!");
    } else {
        $machine_ids[] = $mid;  
    }
}

die("Success!");
  

Есть идеи, почему на выполнение этого уходит много минут? В любом случае, чтобы ускорить это?

Комментарии:

1. Вы определили, что in_array является виновником, а что нет Functions::generate_machine_id() ?

2. У вас есть код для Functions::generate_machine_id удобного?

Ответ №1:

 for($i = 0; $i < 100000; $i  ) 
{
  //Generate machine id returns a 15 character alphanumeric string
  $mid = Functions::generate_machine_id();
  if (isset($machine_ids[$mid]))
  {
    die("Collision!");
  }
  $machine_ids[$mid] = true;
}
  

Ответ №2:

Для этого используйте $mid в качестве ключей и фиктивное значение в качестве значения. В частности, вместо

 if(in_array($mid, $machine_ids)) {
    die("Collision!");
} else {
    $machine_ids[] = $mid;  
}
  

используйте

 if(isset($machine_ids[$mid])) {
    die("Collision!");
} else {
    $machine_ids[$mid] = 1;  
}
  

В конце вы можете извлечь массив, который вы изначально хотели использовать array_keys($machine_ids) .

Это должно быть намного быстрее. Если он все еще медленный, значит, ваш Functions::generate_machine_id() медленный.

ОТРЕДАКТИРОВАНО, чтобы добавить isset согласно комментариям.

Комментарии:

1. Опереди меня в этом. 🙂 Хотя вам следует использовать isset($machine_ids[$mid]) .

2. Согласен с deceze, и я верю, что if($machine_ids[$mid]) вернет false (на самом деле это не коллизия), если значение равно целому числу 0, пустой строке, NULL и т.д. В любом случае isset() это правильный путь. Однако я только что заметил, что OP сказал, что это всегда будет буквенный символ с 15 символами.

3. @deceze: Есть причина? Является ли снижение производительности isset ниже, чем снижение производительности неявного преобразования в логическое значение? Или это теоретически — поскольку в PHP 0 также false, что отличалось бы от isset ? (в данном случае у нас есть только пустые значения и единицы, так что это безопасно)

4. @Amadan: Он также сгенерирует уведомление, когда $machine_ids[$mid] значение не определено.

5. @Amadan Как сказал Уэсли, таким образом вы получите массу уведомлений, для запуска которых также требуется время. Это срабатывание может значительно замедлить работу скрипта и помешать пользователю, наблюдающему за загрузкой.

Ответ №3:

Проверка принадлежности к массиву — это операция O (n), поскольку вам приходится сравнивать значение с каждым элементом в массиве. После того, как вы добавляете целую кучу материала в массив, естественно, он становится медленнее.

Если вам нужно выполнить целую кучу тестов на членство, как в данном случае, вам следует использовать другую структуру данных, которая поддерживает O (1) тестов на членство, таких как хэш.

Ответ №4:

Реорганизуйте свой код так, чтобы он использовал связанный массив для хранения идентификаторов компьютеров и использования isset для проверки

 if( isset($machine_id[$mid]) ) die("Collision");

$machine_ids[$mid] = $mid;
  

Использование isset должно быть быстрее

Ответ №5:

Если вам нужна максимальная производительность в вашем случае, вам нужно сохранить ваши данные в виде ключа массива и использовать вместо этого isset or array_key_exists (поскольку php > = 7.4 array_key_exists теперь так же быстр, как isset ) in_array .

Внимание. Верно, что isset в хэш-карте выполняется быстрее, чем поиск значения в массиве (in_array), но имейте в виду, что преобразование массива значений [«foo», «bar», «baz»] в хэш-карту [«foo» => true, «bar» => true, «baz» => true] требует затрат памяти (а также потенциального построения хэш-карты, в зависимости от того, как и когда ты делаешь это). Как и во всем остальном, вам придется взвесить все «за» и «против» в каждом конкретном случае, чтобы определить, подходит ли хэш-карта или массив (список) значений лучше всего для ваших нужд. Это не специфично для PHP, а скорее относится к общему проблемному пространству информатики.

И некоторые тесты производительности от https://gist.github.com/alcaeus/536156663fac96744eba77b3e133e50a

 <?php declare(strict_types = 1);

function testPerformance($name, Closure $closure, $runs = 1000000)
{
    $start = microtime(true);
    for (; $runs > 0; $runs--)
    {
        $closure();
    }
    $end = microtime(true);

    printf("Function call %s took %.5f secondsn", $name, $end - $start);
}

$items = [1111111];
for ($i = 0; $i < 100000; $i  ) {
    $items[] = rand(0, 1000000);
}
$items = array_unique($items);
shuffle($items);

$assocItems = array_combine($items, array_fill(0, count($items), true));

$in_array = function () use ($items) {
    in_array(1111111, $items);
};

$isset = function () use ($assocItems) {
    isset($items[1111111]);
};

$array_key_exists = function () use ($assocItems) {
    array_key_exists(1111111, $assocItems);
};

testPerformance('in_array', $in_array, 100000);
testPerformance('isset', $isset, 100000);
testPerformance('array_key_exists', $array_key_exists, 100000);
  

Вывод:

 Function call in_array took 5.01030 seconds
Function call isset took 0.00627 seconds
Function call array_key_exists took 0.00620 seconds