Я всегда слышал и искал новую «хорошую практику написания», например: «Лучше (для производительности) лучше проверить, существует ли массив, чем поиск в массиве, но также лучше для памяти:
Предполагая, что мы имеем:
$array = array ( 'one' => 1, 'two' => 2, 'three' => 3, 'four' => 4, );
это выделяет 1040 байт памяти,
а также
$array = array ( 1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four', );
требуется 1136 байт
Я понимаю, что key
и value
наверняка будут иметь другой механизм хранения, но, пожалуйста, можете ли вы на самом деле указать мне принцип, как он работает?
Пример 2 (для @teuneboon) :
$array = array ( 'one' => '1', 'two' => '2', 'three' => '3', 'four' => '4', );
1168 байт
$array = array ( '1' => 'one', '2' => 'two', '3' => 'three', '4' => 'four', );
1136 байт
потребляя одну и ту же память:
4 => 'four',
'4' => 'four',
Примечание. Ответ ниже применим для PHP до версии 7, так как в PHP 7 были внесены значительные изменения, которые также включают структуры значений.
Ваш вопрос заключается не в том, «как работает память в PHP» (здесь, я полагаю, вы имели в виду «распределение памяти»), но о том, «как массивы работают в PHP», – и эти два вопроса разные. Подводя итог тому, что написано ниже:
ulong
(unsigned long) хеш-карт, поэтому реальная разница будет в значениях, где опция строковых ключей имеет целое число (фиксированное -length), в то время как параметр integer-keys имеет значения строк (значения, зависящие от символов). Но это может не всегда быть правдой из-за возможных столкновений. '4'
, будут рассматриваться как целые ключи и переведены в целочисленный хеш-результат, поскольку он является целым ключом. Таким образом, '4'=>'foo'
и 4 => 'foo'
– одни и те же вещи. Кроме того, важно отметить : графики здесь являются авторскими правами на внутреннюю книгу PHP
Массивы PHP и массивы C
Вы должны понимать одну очень важную вещь: PHP написан на C, где таких вещей, как «ассоциативный массив», просто не существует. Таким образом, в C "array" это именно то, что «массив» – то есть это просто последовательная область в памяти, к которой можно получить доступ с помощью последовательного смещения. Ваши «ключи» могут быть только числовыми, целыми и только последовательными, начиная с нуля. Вы не можете иметь, например, 3
, -6
, 'foo'
качестве ваших «ключей».
Таким образом, для реализации массивов, которые находятся на PHP, существует опция hash-map, она использует хеш-функцию для хэширования ваших ключей и преобразования их в целые числа, которые могут использоваться для C-массивов. Однако эта функция никогда не сможет создать биекцию между строковыми клавишами и целыми хэшированными результатами. И легко понять, почему: потому что мощность набора строк намного, намного больше, чем мощность целочисленного множества. Давайте проиллюстрируем пример: мы перечислим все строки длиной до 10, которые имеют только буквенно-цифровые символы (так, 0-9
, az
и AZ
, всего 62): это всего 62 10 возможных строк. Это около 8.39E + 17 . Сравните его со значением 4E + 9, который у нас есть для целых чисел без знака (длинный целочисленный, 32-разрядный), и вы получите идею – будут столкновения .
PHP-хэш-карты ключей и коллизий
Теперь, чтобы разрешить конфликты, PHP будет просто помещать элементы, имеющие один и тот же результат хэш-функции, в один связанный список. Таким образом, хеш-карта не будет просто «списком хешированных элементов», но вместо этого она будет хранить указатели на списки элементов (каждый элемент в определенном списке будет иметь один и тот же ключ хэш-функции). И здесь вы указываете, как это повлияет на распределение памяти: если в вашем массиве есть строковые ключи, которые не приводят к коллизиям, тогда никаких дополнительных указателей внутри этого списка не потребуется, поэтому объем памяти будет уменьшен (на самом деле, это очень небольшие накладные расходы, но, поскольку мы говорим о точном распределении памяти, это следует учитывать). И так же, если ваши строковые ключи приведут к множеству коллизий, тогда будут созданы дополнительные указатели, поэтому суммарный объем памяти будет немного больше.
Чтобы проиллюстрировать эти отношения в этих списках, вот график:
Выше показано, как PHP будет разрешать столкновения после применения хэш-функции. Итак, одна из ваших частей вопроса лежит здесь, указатели внутри списков разрешения конфликтов. Кроме того, элементы связанных списков обычно называются ведрами, а массив, содержащий указатели на arBuckets
этих списков, внутренне называется arBuckets
. Из-за оптимизации структуры (поэтому, чтобы сделать такие вещи, как удаление элементов, быстрее), элемент реального списка имеет два указателя, предыдущий элемент и следующий элемент – но это только немного изменит объем памяти для массивов без столкновений / столкновений, но не изменит сама концепция.
Еще один список: заказ
Чтобы полностью поддерживать массивы, как они есть в PHP, также необходимо поддерживать порядок , так что это достигается с помощью другого внутреннего списка. Каждый элемент массивов также входит в этот список. Это не повлияет на распределение памяти, так как в обоих вариантах этот список должен поддерживаться, но для полного изображения я упоминаю этот список. Вот графика:
В дополнение к pListLast
и pListNext
указатели на головку и хвост списка заказов. Опять же, это не связано напрямую с вашим вопросом, но в дальнейшем я сброшу внутреннюю структуру ковша, где присутствуют эти указатели.
Элемент массива изнутри
Теперь мы готовы изучить: что такое элемент массива, так что ведро :
typedef struct bucket { ulong h; uint nKeyLength; void *pData; void *pDataPtr; struct bucket *pListNext; struct bucket *pListLast; struct bucket *pNext; struct bucket *pLast; char *arKey; } Bucket;
Мы здесь:
h
– целочисленное (ulong) значение ключа, это результат хэш-функции. Для целых ключей это то же самое, что и сам ключ (функция хеш-функции возвращается сама) pNext
/ pLast
– указатели внутри связанного списка конфликтов pListNext
/ pListLast
– указатели внутри связанного списка с разрешением порядка pData
– указатель на сохраненное значение. На самом деле, значение не такое же, как и в создании массива, оно копируется , но, чтобы избежать ненужных накладных расходов, PHP использует pDataPtr
(поэтому pData = &pDataPtr
) С этой точки зрения вы можете получить следующее, где разница: поскольку строковый ключ будет хэширован (таким образом, h
всегда является ulong
и, следовательно, того же размера), это будет вопрос того, что хранится в значениях. Таким образом, для вашего массива строковых ключей будут целочисленные значения, тогда как для массива с целыми ключами будут значения строк, и это имеет значение. Однако – нет, это не волшебство : вы не можете «сохранять память» с сохранением строковых ключей таким образом все время, потому что, если ваши ключи будут большими и их будет много, это вызовет накладные расходы на столкновение ( ну, с очень высокой вероятностью, но, конечно, не гарантируется). Он будет «работать» только для произвольных коротких строк, что не вызовет многих столкновений.
Сама хэш-таблица
Уже говорилось об элементах (ведрах) и их структуре, но есть и сама хэш-таблица, которая, по сути, является структурой данных массива. Итак, это называется _hashtable
:
typedef struct _hashtable { uint nTableSize; uint nTableMask; uint nNumOfElements; ulong nNextFreeElement; Bucket *pInternalPointer; /* Used for element traversal */ Bucket *pListHead; Bucket *pListTail; Bucket **arBuckets; dtor_func_t pDestructor; zend_bool persistent; unsigned char nApplyCount; zend_bool bApplyProtection; #if ZEND_DEBUG int inconsistent; #endif } HashTable;
Я не буду описывать все поля, так как я уже предоставил много информации, которая связана только с вопросом, но я кратко опишу эту структуру:
arBuckets
– это то, что было описано выше, хранение ведер, pListHead
/ pListTail
– указатели на список разрешений nTableSize
определяет размер хеш-таблицы. И это напрямую связано с распределением памяти: nTableSize
всегда имеет значение 2. Таким образом, неважно, будет ли у вас 13 или 14 элементов в массиве: фактический размер будет равен 16. Принимайте это за счет, когда вы хотите оценить размер массива , Это действительно трудно предсказать, будет ли один массив больше другого в вашем случае. Да, есть рекомендации, которые следуют из внутренней структуры, но если строковые ключи сопоставимы по длине с целыми значениями (например, 'four'
, 'one'
в вашем примере), реальная разница будет заключаться в том, что – сколько коллизий произошло, сколько байтов было выделено для сохранения значения.
Но выбор правильной структуры должен иметь смысл, а не память. Если вы хотите построить соответствующие индексированные данные, выбор всегда будет очевиден. Сообщение выше – это только одна цель: показать, как массивы действительно работают на PHP и где вы можете найти разницу в распределении памяти в своем примере.
Вы также можете проверить статью о массивах и хэш-таблицах в PHP: это хэш-таблицы в PHP по внутренней книге PHP: я использовал некоторые графики оттуда. Кроме того, чтобы понять, как распределяются значения в PHP, проверьте статью zval Structure , это может помочь вам понять, каковы будут различия между распределением строк и целых чисел для значений ваших массивов. Здесь я не привел объяснений, так как для меня гораздо важнее – показать структуру данных массива и что может быть разницей в контексте строковых ключей / целых ключей для вашего вопроса.
Хотя оба массива получают доступ по-другому (т. Е. Через строковое или целочисленное значение), шаблон памяти в основном похож.
Это связано с тем, что распределение строк происходит либо как часть создания zval, либо когда нужно выделить новый массив; малая разница заключается в том, что числовые индексы не требуют цельной структуры zval, потому что они сохраняются как (без знака) долго.
Наблюдаемые различия в распределении памяти настолько минимальны, что их можно в значительной степени отнести либо к неточности memory_get_usage()
либо к распределениям из-за создания дополнительного ведра.
Как вы хотите использовать свой массив, должен быть руководящим принципом при выборе способа его индексирования; память должна только стать исключением из этого правила, когда вы исчерпаете его.
Из руководства PHP Сбор мусора http://php.net/manual/en/features.gc.php
gc_enable(); // Enable Garbage Collector var_dump(gc_enabled()); // true var_dump(gc_collect_cycles()); // # of elements cleaned up gc_disable(); // Disable Garbage Collector
PHP не очень хорошо возвращает выпущенную память; Его основное использование в Интернете не требует этого, и эффективная сборка мусора требует времени от предоставления продукции; Когда сценарий заканчивается, память все равно будет возвращена.
Уборка мусора происходит.
Когда вы расскажете
int gc_collect_cycles ( void )
Когда вы оставляете функцию
Лучшее понимание коллекции мусора PHP от веб-хостинга (без аффилиации). http://www.sitepoint.com/better-understanding-phps-garbage-collection/
Если вы рассматриваете байты по байтам, как данные устанавливаются в памяти. Различные порты будут использовать эти значения. Производительность 64-битных процессоров лучше всего, когда данные находятся на первом бите 64-битного слова. Для максимальной производительности конкретный двоичный код они выделили бы начало блока памяти на первом бите, оставив до 7 байтов неиспользованным. Этот специфичный для процессора материал зависит от того, какой компилятор использовался для компиляции PHP.exe. Я не могу предложить какой-либо способ предсказать точное использование памяти, учитывая, что он будет отличаться по-разному от разных компиляторов.
Alma Do, сообщение переходит к особенностям источника, который отправляется компилятору. То, что запрашивает PHP-источник, и компилятор оптимизируется.
Посмотрите на конкретные примеры, которые вы опубликовали. Когда ключ является буквой ascii, они берут на 4 байта (64 бит) больше за запись … это говорит мне (не предполагая, что нет дыр или дыр в памяти, ect), что ключи ascii больше 64 бит, но числовые клавиши вписываются в 64-битное слово. Он предлагает мне использовать 64-битный компьютер, а ваш PHP.exe скомпилирован для 64-битных процессоров.
Массивы в PHP реализованы как hashmaps. Следовательно, длина значения, которое вы используете для ключа, мало влияет на требования к данным. В более ранних версиях PHP значительная деградация производительности с большими массивами, так как размер хеша фиксировался при создании массива – когда столкновение, начинающееся с того, что происходит, а затем увеличение количества хеш-значений, будет сопоставляться со связанными списками значений, которые затем необходимо было искать (с алгоритм O (n)) вместо одного значения, но в последнее время хэш, по-видимому, использует либо гораздо больший размер по умолчанию, либо динамически изменен (он просто работает – я не могу потрудиться, читая исходный код).
Сохранение 4 байтов из ваших сценариев не вызовет у Google никаких бессонных ночей. Если вы пишете код, который использует большие массивы (где сбережения могут быть более значительными), вы, вероятно, ошибаетесь – время и ресурсы, необходимые для заполнения массива, могут быть лучше потрачены в другом месте (например, индексированное хранилище).