Ответ 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 будет просто помещать элементы, имеющие один и тот же результат хэш-функции, в один связанный список. Таким образом, хеш-карта не будет просто "списком хешированных элементов", но вместо этого она будет хранить указатели на списки элементов (каждый элемент в определенном списке будет иметь один и тот же ключ хэш-функции). И здесь вы указываете, как это повлияет на распределение памяти: если в вашем массиве есть строковые ключи, которые не приводят к коллизиям, тогда никаких дополнительных указателей внутри этого списка не потребуется, поэтому объем памяти будет уменьшен (фактически, это очень небольшие накладные расходы, но, поскольку мы говорим о точном распределении памяти, это следует учитывать). И так же, если ваши строковые ключи приведут к множеству коллизий, тогда будут созданы дополнительные указатели, поэтому суммарный объем памяти будет немного больше.
Чтобы проиллюстрировать эти отношения в этих списках, вот графический пример:
Выше описано, как PHP будет разрешать конфликты после применения хэш-функции. Итак, одна из ваших частей вопроса лежит здесь, указатели внутри списков разрешения конфликтов. Кроме того, элементы связанных списков обычно называются ведрами, а массив, содержащий указатели на заголовки этих списков, внутренне называется arBuckets
. Из-за оптимизации структуры (поэтому, чтобы сделать такие вещи, как удаление элемента, быстрее), элемент реального списка имеет два указателя, предыдущий элемент и следующий элемент - но это лишь немного изменит объем памяти для массивов без столкновений/столкновений, но не изменит сама концепция.
Еще один список: порядок
Чтобы полностью поддерживать массивы, как они есть в PHP, также необходимо поддерживать порядок, поэтому это достигается с помощью другого внутреннего списка. Каждый элемент массивов также входит в этот список. Это не повлияет на распределение памяти, так как в обоих вариантах этот список должен поддерживаться, но для полного изображения я упоминаю этот список. Здесь рисунок:
В дополнение к 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
(sopData = &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 статью, это может помочь вам понять, какие будут различия между распределением строк и целых чисел для значений ваших массивов. Здесь я не приводил объяснений, так как для меня гораздо важнее то, что нужно показать структуру данных массива и что может быть различием в контексте строковых ключей/целых ключей для вашего вопроса.