Intereting Posts
SQL-запрос для сопоставления строки, разделенной запятыми, для строки с разделителями-запятыми? Как получить первый элемент из ассоциативного массива PHP? Ссылки на PHP-переменные и использование памяти утвердить идентификацию личности с помощью системы печати пальцев в php & mysql Заполнение формы HTML с помощью сеанса Zend Framework Как проверить, соответствует ли дата в заданном диапазоне дней + месяца? Как настроить Zend_Form для использования нотации массива? Проверка того, включена ли моя страница в iframe JSON показывает ошибку 403, пытаясь извлечь данные из базы данных в android Поделитесь аутентификацией / сеансом Laravel с PHP Файл Laravel env не загружается Преобразовать строку «десять» в целое число 10 Сохранение выбранного элемента в поле выбора после отправки Декларация, чтобы сделать PHP-скрипт полностью совместимым с Unicode рекурсивное заполнение базы данных многомерным массивом

Как работает PHP-память

Я всегда слышал и искал новую «хорошую практику написания», например: «Лучше (для производительности) лучше проверить, существует ли массив, чем поиск в массиве, но также лучше для памяти:

Предполагая, что мы имеем:

$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 были внесены значительные изменения, которые также включают структуры значений.

TL; DR

Ваш вопрос заключается не в том, «как работает память в PHP» (здесь, я полагаю, вы имели в виду «распределение памяти»), но о том, «как массивы работают в PHP», – и эти два вопроса разные. Подводя итог тому, что написано ниже:

  • Массивы PHP не являются «массивами» в классическом смысле. Это хэш-карты
  • Хэш-карта для массива PHP имеет определенную структуру и использует много дополнительных вещей для хранения, таких как внутренние указатели ссылок
  • Элементы хэш-карты для хэш-карты PHP также используют дополнительные поля для хранения информации. И – да, важны не только строковые / целые ключи, но и то, что сами строки, которые используются для ваших ключей.
  • Опция со строковыми клавишами в вашем случае будет «выигрывать» с точки зрения объема памяти, потому что обе опции будут хешированы в хеш-карте ulong (unsigned long) хеш-карт, поэтому реальная разница будет в значениях, где опция строковых ключей имеет целое число (фиксированное -length), в то время как параметр integer-keys имеет значения строк (значения, зависящие от символов). Но это может не всегда быть правдой из-за возможных столкновений.
  • Клавиши «String-numeric», такие как '4' , будут рассматриваться как целые ключи и переведены в целочисленный хеш-результат, поскольку он является целым ключом. Таким образом, '4'=>'foo' и 4 => 'foo' – одни и те же вещи.

Кроме того, важно отметить : графики здесь являются авторскими правами на внутреннюю книгу PHP

Хэш-карта для массивов 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 не очень хорошо возвращает выпущенную память; Его основное использование в Интернете не требует этого, и эффективная сборка мусора требует времени от предоставления продукции; Когда сценарий заканчивается, память все равно будет возвращена.

Уборка мусора происходит.

  1. Когда вы расскажете

    int gc_collect_cycles ( void )

  2. Когда вы оставляете функцию

  3. Когда скрипт заканчивается

Лучшее понимание коллекции мусора 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 никаких бессонных ночей. Если вы пишете код, который использует большие массивы (где сбережения могут быть более значительными), вы, вероятно, ошибаетесь – время и ресурсы, необходимые для заполнения массива, могут быть лучше потрачены в другом месте (например, индексированное хранилище).