Список функций Big-O для PHP

254

После использования PHP на некоторое время я заметил, что не все PHP встроены в функции так быстро, как ожидалось. Рассмотрим ниже две возможные реализации функции, которая находит, если число является простым, используя кешированный массив простых чисел.

//very slow for large $prime_array
$prime_array = array( 2, 3, 5, 7, 11, 13, .... 104729, ... );
$result_array = array();
foreach( $prime_array => $number ) {
    $result_array[$number] = in_array( $number, $large_prime_array );
}

//speed is much less dependent on size of $prime_array, and runs much faster.
$prime_array => array( 2 => NULL, 3 => NULL, 5 => NULL, 7 => NULL,
                       11 => NULL, 13 => NULL, .... 104729 => NULL, ... );
foreach( $prime_array => $number ) {
    $result_array[$number] = array_key_exists( $number, $large_prime_array );
}

Это связано с тем, что in_array реализуется с линейным поиском O (n), который будет линейно замедляться по мере роста $prime_array. Где функция array_key_exists реализована с помощью хэш-поиска O (1), который не будет замедляться, если хэш-таблица не будет чрезвычайно заполнена (в этом случае это только O (n)).

До сих пор мне приходилось обнаруживать большой-O через пробную версию и ошибку, а иногда глядя на исходный код. Теперь для вопроса...

Был ли список теоретических (или практических) больших O раз для всех * встроенных PHP функций?

* или, по крайней мере, интересные

Например, очень сложно предсказать, что такое большая функция функций, потому что возможная реализация зависит от неизвестных основных структур данных PHP: array_merge, array_merge_recursive, array_reverse, array_intersect, array_combine, str_replace (с входами массива) и т.д.

  • 28
    Совершенно не по теме, но 1 не является основным.
  • 0
    Добрый звонок Джейсон Пуньон
Показать ещё 10 комментариев
Теги:
arrays
performance
algorithm
big-o

4 ответа

514
Лучший ответ

Так как кажется, что кто-то не сделал этого, прежде чем я подумал, что было бы неплохо иметь его для справки где-то. Я пошел, хотя и с помощью бенчмарка или сглаживания кода, чтобы охарактеризовать функции array_*. Я попытался поставить более интересный Big-O ближе к вершине. Этот список не заполнен.

Примечание. Все значения Big-O, вычисляемые при хэш-поиске, равны O (1), хотя это действительно O (n). Коэффициент n настолько мал, что накладные расходы на хранение достаточно большого массива могут повредить вам, прежде чем характеристики поиска Big-O начнут действовать. Например, разница между вызовом array_key_exists при N = 1 и N = 1,000,000 увеличивается на 50%.

Интересные точки:

  • isset/array_key_exists намного быстрее, чем in_array и array_search
  • + (union) немного быстрее, чем array_merge (и выглядит лучше). Но он работает по-разному, поэтому имейте это в виду.
  • shuffle находится на том же уровне Big-O, что и array_rand
  • array_pop/array_push быстрее, чем array_shift/array_unshift из-за переиндексации штрафа

Lookups

array_key_exists O (n), но действительно близко к O (1) - это из-за линейного опроса при столкновении, а потому, что вероятность столкновения очень мала, коэффициент также очень мал. Я нахожу, что вы обрабатываете поиск хэша как O (1), чтобы дать более реалистичный большой-O. Например, разница между N = 1000 и N = 100000 замедляется только на 50%.

isset( $array[$index] ) O (n), но очень близко к O (1) - он использует тот же поиск, что и array_key_exists. Так как он построил язык, он будет кэшировать поиск, если ключ жестко закодирован, что приводит к ускорению в тех случаях, когда один и тот же ключ используется повторно.

in_array O (n) - это потому, что он выполняет линейный поиск, хотя массив до тех пор, пока не найдет значение.

array_search O (n) - он использует ту же основную функцию, что и in_array, но возвращает значение.

Функции очереди:

array_push O (Σ var_i, для всех i)

array_pop O (1)

array_shift O (n) - он должен переиндексировать все клавиши

array_unshift O (n + Σ var_i, для всех i) - он должен переиндексировать все ключи

Пересечение массива, объединение, вычитание:

array_intersect_key Если пересечение 100% делает O (Max (param_i_size) * Σparam_i_count для всех i), если пересечение 0% пересекает O (Σparam_i_size, для всех i)

array_intersect если пересечение 100% делает O (n ^ 2 * Σparam_i_count, для всех i), если пересечение 0% пересекает O (n ^ 2)

array_intersect_assoc Если пересечение 100% делает O (Max (param_i_size) * Σparam_i_count для всех i), если пересечение 0% пересекает O (Σparam_i_size, для всех i)

array_diff O (π param_i_size, для всех i) - это произведение всех param_sizes

array_diff_key O (Σ param_i_size, для я!= 1) - это потому, что нам не нужно перебирать первый массив.

array_merge O (Σ array_i, я!= 1) - не нужно перебирать первый массив

+ (объединение) O (n), где n - размер 2-го массива (т.е. array_first + array_second) - меньше накладных расходов, чем array_merge, поскольку ему не нужно перенумеровать

array_replace O (Σ array_i, для всех i)

Random

shuffle O (n)

array_rand O (n) - Требуется линейный опрос.

Очевидный Big-O:

array_fill O (n)

array_fill_keys O (n)

range O (n)

array_splice O (смещение + длина)

array_slice O (смещение + длина) или O (n), если length = NULL

array_keys O (n)

array_values O (n)

array_reverse O (n)

array_pad O (pad_size)

array_flip O (n)

array_sum O (n)

array_product O (n)

array_reduce O (n)

array_filter O (n)

array_map O (n)

array_chunk O (n)

array_combine O (n)

Я хотел бы поблагодарить Eureqa за то, что он легко нашел Big-O функций. Это потрясающая бесплатная программа, которая может найти наилучшую подходящую функцию для произвольных данных.

EDIT:

Для тех, кто сомневается в том, что поиск в PHP-массиве O(N), я написал тест, чтобы проверить это (они по-прежнему эффективно O(1) для большинства реалистичных значений).

Изображение 774

$tests = 1000000;
$max = 5000001;


for( $i = 1; $i <= $max; $i += 10000 ) {
    //create lookup array
    $array = array_fill( 0, $i, NULL );

    //build test indexes
    $test_indexes = array();
    for( $j = 0; $j < $tests; $j++ ) {
        $test_indexes[] = rand( 0, $i-1 );
    }

    //benchmark array lookups
    $start = microtime( TRUE );
    foreach( $test_indexes as $test_index ) {
        $value = $array[ $test_index ];
        unset( $value );
    }
    $stop = microtime( TRUE );
    unset( $array, $test_indexes, $test_index );

    printf( "%d,%1.15f\n", $i, $stop - $start ); //time per 1mil lookups
    unset( $stop, $start );
}
  • 0
    @sims Хорошо, что тогда он работает в вине :)
  • 0
    Может быть, это хорошо для драйверов устройств обратного проектирования. Я буду придерживаться виртуальной коробки;) Веселимся с ней сейчас. Спасибо!
Показать ещё 14 комментариев
2

Вы почти всегда хотите использовать isset вместо array_key_exists. Я не смотрю на внутренности, но я уверен, что array_key_exists - это O (N), потому что он выполняет итерацию по каждому ключу массива, а isset пытается получить доступ к элементу с использованием того же алгоритма хеширования который используется при доступе к индексу массива. Это должно быть O (1).

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

$search_array = array('first' => null, 'second' => 4);

// returns false
isset($search_array['first']);

// returns true
array_key_exists('first', $search_array);

Мне было любопытно, поэтому я сравнил разницу:

<?php

$bigArray = range(1,100000);

$iterations = 1000000;
$start = microtime(true);
while ($iterations--)
{
    isset($bigArray[50000]);
}

echo 'is_set:', microtime(true) - $start, ' seconds', '<br>';

$iterations = 1000000;
$start = microtime(true);
while ($iterations--)
{
    array_key_exists(50000, $bigArray);
}

echo 'array_key_exists:', microtime(true) - $start, ' seconds';
?>

is_set: 0.132308959961 секунд
array_key_exists: 2.33202195168 секунд

Конечно, это не показывает временную сложность, но показывает, как 2 функции сравниваются друг с другом.

Чтобы проверить временную сложность, сравните время, затрачиваемое на выполнение одной из этих функций на первом и последнем ключах.

  • 8
    Это не верно. Я на 100% уверен, что array_key_exists не нужно перебирать каждый ключ. Если вы не верите, посмотрите на ссылку ниже. Причина заключается в том, что намного быстрее, в том, что это языковая конструкция. Это означает, что нет необходимости выполнять вызов функции. Кроме того, я думаю, что это может быть кэширование поиска, из-за этого. Кроме того, это не ответ на вопрос! Я хотел бы список Big (O) для функций PHP (как говорится в вопросе). Ни одного эталона моих примеров. svn.php.net/repository/php/php-src/branches/PHP_5_3/ext/...
  • 0
    Если вы все еще не верите мне, я создаю небольшой тест, чтобы продемонстрировать это. pastebin.com/BdKpNvkE
Показать ещё 2 комментария
2

Объяснение конкретного случая, которое вы описываете, это то, что ассоциативные массивы реализованы как хеш-таблицы, поэтому поиск по ключу (и, соответственно, array_key_exists) равен O (1). Однако массивы не индексируются по значению, поэтому единственный способ в общем случае выяснить, существует ли значение в массиве, - это линейный поиск. Там нет ничего удивительного.

Я не думаю, что существует специальная всесторонняя документация по алгоритмической сложности методов PHP. Однако, если это достаточно важно, чтобы гарантировать это, вы всегда можете просмотреть исходный код.

  • 0
    Это не совсем ответ. Как я уже говорил в этом вопросе, я уже пытался изучить исходный код PHP. Поскольку PHP реализован, он написан на C с использованием сложных макросов, которые иногда могут затруднить «просмотр» лежащего в основе большого O для функций.
  • 0
    @Kendall Я пропустил вашу ссылку на погружение в исходный код. Однако в моем ответе есть ответ: «Я не думаю, что есть конкретная исчерпывающая документация об алгоритмической сложности методов PHP». «Нет» - совершенно правильный ответ. (С:
0

Если люди столкнулись с трудностями на практике с ключевыми столкновениями, они будут реализовывать контейнеры со вторичным поиском хэшей или сбалансированным деревом. Сбалансированное дерево дало бы поведение O (log n) худшего случая и O (1) avg. case (сам хэш). Накладные расходы не стоят в большинстве практических приложений в памяти, но, возможно, есть базы данных, которые реализуют эту форму смешанной стратегии в качестве случая по умолчанию.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню