Как реализуется массив PHP на уровне C?
PHP array
- одна из основных функций PHP. Он разрежен, позволяет использовать многотипные ключи в одном массиве и поддерживает набор, словарь, массив, стек/очередь и итеративную функциональность.
Но после работы с PHP на некоторое время я обнаружил, что многие из функций array_*
намного медленнее, чем вы думаете на первый взгляд. Как и в случае array_rand
на очень большом массиве (10000+). array_rand
настолько медленен на самом деле, что в тех случаях, когда вы используете php-массив в качестве индексированного массива, функция типа rand( 0, array_length( $array ) - 1 )
работает MUCH быстрее, чем array_rand
.
Теперь на мой вопрос.
Как реализуется массив PHP на уровне C?. Это было бы очень полезно для прогнозирования Big O функции, которая сильно использует различные функции типа данных массива PHP.
Ответы
Ответ 1
Ассоциативные массивы PHP фактически реализуют HashTables.
Внутри можно создавать числовые массивы или ассоциативные массивы.
Если вы их объединяете, это ассоциативный массив.
В числовых массивах он очень похож на C. У вас есть массив указателей на структуры ZVAL.
Поскольку указатели имеют фиксированную длину (пусть называют это n), вычисление смещения (x) легко: x * n.
В PHP-типах это структуры ZVAL (потому что он реализует динамические типы), но также помогает в ассоциативном массиве, поскольку вы можете считать фиксированную длину. Поэтому, даже если прямой доступ к массиву медленнее, он все еще считается O (1).
Итак, что происходит в строковых клавишах? PHP использует хеш-функцию для преобразования их в intergers.
Поиск в числовом и ассоциативном массиве имеет схожую эффективность, потому что внутри они все числовые.
Только прямой доступ к ключам массива медленнее из-за дополнительного уровня (хэш-функция).
Ответ 2
После прочтения zend/zend_hash.h и ext/standard/array.c Я думаю, что нашел ответ (Thankyou Chris и gumbo для предложений).
Массив PHP представляет собой цепочку хеш-таблицы (поиск O (c) и O (n) при столкновении ключей), которая позволяет использовать int и строковые ключи. Он использует 2 разных алгоритма хэширования, чтобы соответствовать двум типам в одно и то же пространство хеш-ключа. Также каждое значение, хранящееся в хеше, связано со значением, хранящимся перед ним, и значением, сохраненным после (связанный список). Он также имеет временный указатель, который используется для хранения текущего элемента, чтобы хэш мог быть итерирован.
Уловка для функции array_rand
заключается в том, что для обеспечения того, что ключ действительно случайный, функция array_rand
должна перебирать массив rand(0, count($array))
раз (O (n)). Это связано с тем, что нет способа переместить на смещение в хэш-таблице в O (c), потому что нет гарантии, что в этом диапазоне нет недостающих клавиш.
Это открытие несколько смутило меня, потому что это означает, что в PHP нет типа данных, который имеет обычные характеристики массива C. В большинстве случаев это нормально, потому что поиск хэшей выполняется очень быстро, но ошибки отображаются в таких случаях, как array_rand
.
Другая проблема, которая меня беспокоила, - это разница между реализацией array_key_exists
и in_array
. array_key_exists
использует хэш-поиск (большую часть времени O (c)), чтобы увидеть, находится ли ключ в массиве, а in_array
должен линейно искать хеш (O (n)).
Рассмотрим два примера ниже:
версия in_array
$array = range(0, 100000);
if( in_array( $random_key, $array ) ) {
//we found a value
}
версия array_key_exists
$array = array_fill_keys( range(0, 100000), NULL );
if( array_key_exists( $random_key, $array ) ) {
//we found a value, err key
}
В то время как версия in_array выглядит намного чище и проще для понимания, она очень медленная на больших массивах (O (n)), где array_key_exists (несмотря на раздражающе длинное имя) очень быстро (O (c) или close).
В заключение:
Я хочу, чтобы в структуре данных zend HashTable был прозрачный флаг, который будет установлен в случаях, когда массив был создан с использованием array_push
или array[] = $value
, который позволит масштабировать, как массив C, вместо связанного списка.
Ответ 3
Так как массивы PHP являются упорядоченными картами (даже при использовании смежных целочисленных индексов) array_rand()
, вероятно, необходимо отобразить список ключей для выбора элемент из. Я предполагаю, что было бы непомерно время и время неэффективно кэшировать (часто недействительный) список ключей, поэтому каждый вызов будет по меньшей мере обходить обход и стоимость строительства.
Поскольку ваша реализация rand(... length ...)
использует специальные знания, которые у вас есть, что ключи являются смежными целыми числами, вы получите затраты на поиск O (log n). Это похоже на данные Chacha102.
Ответ 4
См. этот комментарий в документации, подтверждающей вашу дилемму, что array_rand, в то время как быстрый для небольших массивов, масштабируется очень плохо.
Я модифицировал fake_array_rand, чтобы всегда возвращать только один элемент и выполнял некоторые тесты против вызова array_rand со вторым параметром как 1. Я выполнил 100 выборок для каждой функции для каждого числа элементов и принял средний результат. Хотя внутренний array_rand быстрее для небольшого количества элементов, он очень мало масштабируется.
1 elements: 2.0619630813599E-05 sec. for array_rand,8.4352493286133E-05 sec.
for fake_array_rand
10 elements: 2.1675825119019E-05 sec. for array_rand,8.427619934082E-05 sec.
for fake_array_rand
100 elements: 2.9319524765015E-05 sec. for array_rand,8.4599256515503E-05 sec.
for fake_array_rand
1000 elements: 0.0001157283782959 sec. for array_rand,8.5572004318237E-05 sec.
for fake_array_rand
10000 elements: 0.0016669762134552 sec. for array_rand,8.5201263427734E-05 sec.
for fake_array_rand
100000 elements: 0.015599734783173 sec. for array_rand,8.5580348968506E-05 sec.
for fake_array_rand
1000000 elements: 0.18011983394623 sec. for array_rand,8.6690187454224E-05 sec. for fake_array_rand
<?php
function fake_array_rand ($array)
{
$count = count ($array);
# Help keep the number generator random :)
$randval and usleep ("0.$randval");
# Seed the random number generator
# Generate a random number
srand ((double) microtime() * 10000000);
$randval = rand();
# Use the random value to 'pick' an entry from the array
# Count the number of times that the entry is picked
++$index[$randval % $count];
return $array[$randval % $count];
}
?>
http://us.php.net/manual/en/function.array-rand.php#22360
Ответ 5
Взгляните на zend/zend_hash.c
и zend/zend_hash.h
Я не знаю c, но для меня это похоже на цепочку хеш-таблицы.