Подтвердить что ты не робот

Как работает 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',
4b9b3361

Ответ 1

Примечание. Ответ ниже применим для PHP до до версии 7, так как в PHP 7 были внесены основные изменения, которые также включают структуры значений.

TL; DR

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

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

Также важно отметить: здесь авторские права защищены внутренняя книга PHP

Hash-map для массивов PHP

Массивы PHP и массивы C

Вы должны понимать одну очень важную вещь: PHP написан на C, где таких вещей, как "ассоциативный массив", просто не существует. Таким образом, в C "array" это именно то, что "массив" - то есть это просто последовательная область в памяти, к которой можно получить доступ с помощью последовательного смещения. Ваши "ключи" могут быть только числовыми, целыми и только последовательными, начиная с нуля. Вы не можете иметь, например, 3, -6, 'foo' как свои "ключи".

Итак, чтобы реализовать массивы, которые находятся на PHP, есть опция hash-map, она использует хеш-функцию для хэширования ваших ключей и преобразования их в целые числа, которые могут использоваться для C-массивов. Однако эта функция никогда не сможет создать bijection между строковыми клавишами и их целыми хэшированными результатами. И легко понять, почему: потому что cardinality набора строк намного, намного больше, чем мощность целочисленного набора. Давайте проиллюстрируем пример: мы перечислим все строки длиной до 10, которые имеют только буквенно-цифровые символы (так, 0-9, a-z и a-z, всего 62): it 62 10 возможны полные строки. Он вокруг 8.39E + 17. Сравните его с 4E + 9, который у нас есть для целых чисел без знака (длинный целочисленный, 32-разрядный), и вы получите идею - будут столкновения.

Ключи хеш-карты PHP и коллизии

Теперь, чтобы разрешить конфликты, PHP будет просто помещать элементы, имеющие один и тот же результат хэш-функции, в один связанный список. Таким образом, хеш-карта не будет просто "списком хешированных элементов", но вместо этого она будет хранить указатели на списки элементов (каждый элемент в определенном списке будет иметь один и тот же ключ хэш-функции). И здесь вы указываете, как это повлияет на распределение памяти: если в вашем массиве есть строковые ключи, которые не приводят к коллизиям, тогда никаких дополнительных указателей внутри этого списка не потребуется, поэтому объем памяти будет уменьшен (фактически, это очень небольшие накладные расходы, но, поскольку мы говорим о точном распределении памяти, это следует учитывать). И так же, если ваши строковые ключи приведут к множеству коллизий, тогда будут созданы дополнительные указатели, поэтому суммарный объем памяти будет немного больше.

Чтобы проиллюстрировать эти отношения в этих списках, вот графический пример:

enter image description here

Выше описано, как PHP будет разрешать конфликты после применения хэш-функции. Итак, одна из ваших частей вопроса лежит здесь, указатели внутри списков разрешения конфликтов. Кроме того, элементы связанных списков обычно называются ведрами, а массив, содержащий указатели на заголовки этих списков, внутренне называется arBuckets. Из-за оптимизации структуры (поэтому, чтобы сделать такие вещи, как удаление элемента, быстрее), элемент реального списка имеет два указателя, предыдущий элемент и следующий элемент - но это лишь немного изменит объем памяти для массивов без столкновений/столкновений, но не изменит сама концепция.

Еще один список: порядок

Чтобы полностью поддерживать массивы, как они есть в PHP, также необходимо поддерживать порядок, поэтому это достигается с помощью другого внутреннего списка. Каждый элемент массивов также входит в этот список. Это не повлияет на распределение памяти, так как в обоих вариантах этот список должен поддерживаться, но для полного изображения я упоминаю этот список. Здесь рисунок:

enter image description here

В дополнение к pListLast и pListNext сохраняются указатели на головку и хвост списка заказов. Опять же, это не связано напрямую с вашим вопросом, но в дальнейшем я буду сбрасывать внутреннюю структуру ковша, где присутствуют эти указатели.

Элемент массива изнутри

Теперь мы готовы рассмотреть: что такое элемент массива, поэтому bucket:

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 (so 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 статью, это может помочь вам понять, какие будут различия между распределением строк и целых чисел для значений ваших массивов. Здесь я не приводил объяснений, так как для меня гораздо важнее то, что нужно показать структуру данных массива и что может быть различием в контексте строковых ключей/целых ключей для вашего вопроса.

Ответ 2

Хотя оба массива доступны другим способом (т.е. с помощью строкового или целочисленного значения), шаблон памяти в основном похож.

Это связано с тем, что распределение строк происходит как часть создания zval или когда нужно выделить новый массив; небольшая разница состоит в том, что числовые индексы не требуют цельной структуры zval, потому что они сохраняются как (unsigned) long.

Наблюдаемые различия в распределении памяти настолько минимальны, что их можно в значительной степени отнести либо к погрешности memory_get_usage(), либо к распределениям из-за дополнительного создания ведра.

Заключение

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

Ответ 3

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

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

  • Когда вы расскажете об этом

    int gc_collect_cycles ( void )

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

  • Когда script заканчивается

Лучшее понимание коллекции PHP Garbage с веб-хостинга (без аффилиации). 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-битных процессоров.

Ответ 4

Массивы в PHP реализованы как hashmaps. Следовательно, длина значения, которое вы используете для ключа, мало влияет на требования к данным. В более ранних версиях PHP значительная деградация производительности с большими массивами, так как размер хеша фиксировался при создании массива - когда столкновение, начинающееся с того, что происходит, а затем увеличение количества хеш-значений, будет сопоставляться со связанными списками значений, которые затем необходимо было искать (с алгоритм O (n)), а не одно значение, но в последнее время хэш, по-видимому, использует либо гораздо больший размер по умолчанию, либо динамически изменен (он просто работает - я не могу потрудиться, читая исходный код).

Сохранение 4 байтов из ваших скриптов не вызовет у Google никаких бессонных ночей. Если вы пишете код, который использует большие массивы (где сбережения могут быть более значительными), вы, вероятно, делаете это неправильно - время и ресурсы, необходимые для заполнения массива, могут быть лучше потрачены в другом месте (например, индексированное хранилище).