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


346

Протягом деякого часу користуючись 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?

* або принаймні цікавого

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


31
Повністю поза темою, але 1 не є головним.
Джейсон Пуньйон

24
Масиви в PHP - хештелі. Це повинно розповісти вам все, що потрібно знати. Пошук ключа в хештайлі - це O (1). Пошук значення - це O (n) - яке не можна перемогти на несортованому наборі. Більшість функцій, які вам цікаві, ймовірно, O (n). Звичайно, якщо ви дійсно хочете знати, ви можете прочитати джерело: cvs.php.net/viewvc.cgi/php-src/ext/standard/…
Френк Фермер

11
Для запису найшвидшою реалізацією того, що ви намагаєтесь зробити, було б (замість використання NULL для своїх значень), trueа потім перевірка на наявність присутності isset($large_prime_array[$number]). Якщо я правильно пам’ятаю, це в порядку бути в сотні разів швидше in_arrayфункції.
mattbasta

3
Позначення Big O - не про швидкість. Йдеться про обмеження поведінки.
Gumbo

3
@Kendall Я не порівнюю array_key_exists, я порівнюю in_array. in_arrayітератує кожен елемент у масиві і порівнює значення з голкою, яку ви переходите до нього. Якщо ви перевернете значення до ключа (і просто заміните кожне зі значень фіктивного значення, як true, наприклад , використання issetв багато разів швидше. Це тому, що ключі масиву індексуються PHP (як хеш-таблиця). Отже, пошук таким чином масив може мати значне поліпшення швидкості
mattbasta

Відповіді:


650

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

Примітка: Усі великі-O, де обчислюються при допущенні пошуку хешу, є O (1), хоча це дійсно O (n). Коефіцієнт n настільки низький, накладні накладні витрати на зберігання достатньо великого масиву зашкодять вам до того, як характеристики пошуку Big-O почнуть діяти. Наприклад, різниця між викликом до array_key_existsN = 1 і N = 1 000 000 - це ~ 50% збільшення часу.

Цікаві бали :

  1. isset/ array_key_existsнабагато швидше ніж in_arrayіarray_search
  2. +(союз) трохи швидше, ніж array_merge(і виглядає приємніше). Але це працює інакше, тому майте це на увазі.
  3. shuffle знаходиться на тому ж рівні Big-O array_rand
  4. array_pop/ array_pushшвидше, ніж array_shift/ array_unshiftвнаслідок повторного покарання

Пошуки :

array_key_existsO (n), але насправді близький до O (1) - це через лінійне опитування при зіткненнях, а тому, що шанс зіткнень дуже малий, коефіцієнт також дуже малий. Я вважаю, що ви розглядаєте хеш-купе як O (1), щоб отримати більш реалістичний big-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 (Макс (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 (Макс (param_i_size) * ∑param_i_count, для всіх i), якщо перетин 0% перетинається O (∑param_i_size, для всіх i)

array_diff O (π param_i_size, для всіх i) - Це добуток усіх параметри_розмірів

array_diff_key O (∑ param_i_size, для i! = 1) - це тому, що нам не потрібно повторювати перший масив.

array_merge O (∑ array_i, i! = 1) - не потрібно перебирати перший масив

+ (об'єднання) O (n), де n - розмір 2-го масиву (тобто array_first + array_second) - менший накладні витрати, ніж array_merge, оскільки він не повинен перенумеровувати

array_replace O (∑ array_i, для всіх i)

Випадкові :

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), якщо довжина = 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 з функцій. Це дивовижна безкоштовна програма, яка може знайти найкращу функцію пристосування для довільних даних.

Редагувати:

Для тих, хто сумнівається в пошуку масивів PHP O(N), я написав орієнтир, щоб перевірити це (вони все ще ефективно O(1)для більшості реалістичних значень).

графік пошуку масиву php

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

5
@Kendall: Дякую! Я трохи прочитав, і виявляється, що PHP використовує "вкладені" хештелі для зіткнень. Тобто, замість структури входу для зіткнень вона просто використовує інший хештель. І я розумію, що практично кажучи хеш-верси PHP дають O (1) продуктивність, або принаймні O (1) в середньому - це те, для чого є хештелі. Мені було просто цікаво, чому ви сказали, що вони "дійсно O (n)", а не "дійсно O (logn)". Чудовий пост до речі!
Кам

10
Часові складності повинні бути включені в документацію! Вибір правильної функції може заощадити вам стільки часу, або скаже вам уникати того, що ви планували: p Дякую за цей список вже!
Самуель

41
Я знаю, що це старе ... але що? Ця крива взагалі не показує O (n), вона показує O (log n), en.wikipedia.org/wiki/Logarithm . Що також точно з тим, що ви очікували для вкладених хеш-карт.
Андреас

5
Що таке Big-O unset на елементі масиву?
Чандрю

12
Хоча хештелі справді мають найгірший складний спосіб пошуку O (n), середній випадок - це O (1), і конкретний випадок, коли ваш тест тестується, навіть гарантується O (1), оскільки це нульовий, постійний, чисельно-індексований масив, який ніколи не матиме хеш-колізій. Причина, чому ви все ще бачите залежність від розміру масиву, не має нічого спільного з алгоритмічною складністю, це викликано ефектами кешу процесора. Чим більший масив, тим більше шансів на те, що пошук у випадковому доступі призведе до пропусків кешу (а кеш пропустить вище в ієрархії).
NikiC

5

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

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


Це насправді не відповідь. Як я вже говорив у запитанні, я вже намагався вивчити вихідний код PHP. Оскільки реалізація PHP написана на C, використовуючи складні макроси, що може ускладнювати "бачення" основного великого O для функцій.
Кендалл Хопкінс

@Kendall Я не помітив вашої посилання на занурення у вихідний код. Однак у моїй відповіді є відповідь: "Я не думаю, що існує специфічна вичерпна документація алгоритмічної складності методів PHP". "Ні" - цілком коректна відповідь. (c:
Датан

4

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

Один з "gotcha", на який слід стежити:

$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 секунд

Звичайно, це не показує складності в часі, але воно показує, як дві функції порівнюють одна одну.

Щоб перевірити складність часу, порівняйте кількість часу, необхідного для запуску однієї з цих функцій на першій та останній клавішах.


9
Це неправильно. Я на 100% впевнений, що array_key_exists не потрібно перебирати кожну клавішу. Якщо ви не вірите, перегляньте посилання нижче. Причина настільки швидша, що це мовна конструкція. Що означає, що у нього немає накладних витрат на виклик функції. Крім того, я думаю, що це може кешувати пошук через це. Також це не є відповіддю на ЗАПИТАННЯ! Я хотів би список Big (O) для функцій PHP (як зазначено в питанні). Жодного еталону моїх прикладів. svn.php.net/repository/php/php-src/branches/PHP_5_3/ext/…
Кендалл Хопкінс

Якщо ви все ще не вірите мені, я створив невеликий орієнтир, щоб продемонструвати точку. pastebin.com/BdKpNvkE
Кендалл Хопкінс

Що не так у вашому орієнтирі, це те, що ви повинні відключити xdebug. =)
Гільгерме Бланко

3
Є дві критичні причини, чому ви хочете використовувати isset над array_key_exists. По-перше, isset - це мовна конструкція, що зменшує вартість виклику функції. Це схоже на аргумент $arrray[] = $appendvs. array_push($array, $append)По-друге, array_key_exists також розрізняє невстановлені та нульові значення. Бо $a = array('fred' => null); array_key_exists('fred', $a)повернеться правдою, а isset($['fred'])повернеться хибним. Цей додатковий крок нетривіальний і значно збільшить час виконання.
orca

0

Якщо люди натрапляли на проблеми на практиці при ключових зіткненнях, вони реалізовували б контейнери з вторинним пошуковим хешем або збалансованим деревом. Збалансоване дерево дало б O (log n) найгірший випадок і O (1) avg. справа (сам хеш). Накладні витрати не варто цього в більшості практичних програм для пам'яті, але, можливо, є бази даних, які реалізують цю форму змішаної стратегії як їх за замовчуванням.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.