¿Cómo funciona realmente la memoria PHP?

Siempre he escuchado y buscado nuevas ‘prácticas de redacción’ de php, por ejemplo: es mejor (para el rendimiento) comprobar si existe una matriz de claves que buscar en una matriz, pero también parece mejor para la memoria:

Suponiendo que tenemos:

$array = array ( 'one' => 1, 'two' => 2, 'three' => 3, 'four' => 4, ); 

esto asigna 1040 bytes de memoria,

y

 $array = array ( 1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four', ); 

requiere 1136 bytes

Entiendo que la key y el value seguramente tendrán diferentes mecanismos de almacenamiento, pero ¿pueden indicarme el principio de cómo funciona?

Ejemplo 2 (para @teuneboon) :

 $array = array ( 'one' => '1', 'two' => '2', 'three' => '3', 'four' => '4', ); 

1168 bytes

 $array = array ( '1' => 'one', '2' => 'two', '3' => 'three', '4' => 'four', ); 

1136 bytes

consumiendo la misma memoria:

  • 4 => 'four',
  • '4' => 'four',

Tenga en cuenta que la respuesta a continuación es aplicable para PHP antes de la versión 7, ya que en PHP 7 se introdujeron cambios importantes que también implican estructuras de valores.

TL; DR

Su pregunta no es sobre “cómo funciona la memoria en PHP” (en este caso, supongo que se refería a “asignación de memoria”), sino sobre “cómo funcionan las matrices en PHP” , y estas dos preguntas son diferentes. Para resumir lo que está escrito a continuación:

  • Los arrays de PHP no son “arrays” en sentido clásico. Son mapas de hash
  • Hash-map para PHP array tiene una estructura específica y utiliza muchas cosas adicionales de almacenamiento, como punteros de enlaces internos
  • Los elementos de mapa hash para PHP hash-map también usan campos adicionales para almacenar información. Y, sí, no solo importan las claves de cadena / entero, sino también cuáles son las cadenas, que se usan para las claves.
  • La opción con teclas de cadena en su caso “ganará” en términos de cantidad de memoria porque ambas opciones se dividirán en hash en long (long unsigned) hash-map, por lo que la diferencia real estará en valores, donde la opción string-keys tiene un entero (fijo -length) values, mientras que la opción integer-keys tiene valores de cadenas (longitud dependiente de caracteres). Pero eso no siempre será cierto debido a posibles colisiones.
  • Las claves “String-numeric”, como '4' , se tratarán como claves enteras y se traducirán en un resultado hash entero, ya que era una clave entera. Por lo tanto, '4'=>'foo' y 4 => 'foo' son las mismas cosas.

También, nota importante : los gráficos aquí son copyright del libro interno de PHP

Hash-map para matrices PHP

Arrays PHP y matrices C

Debería darse cuenta de algo muy importante: PHP está escrito en C, donde cosas tales como “matriz asociativa” simplemente no existen. Entonces, en C “array” es exactamente lo que es “array”, es decir, es solo un área consecutiva en la memoria a la que se puede acceder mediante un desplazamiento consecutivo . Sus “claves” pueden ser solo numéricas, enteras y solo consecutivas, comenzando desde cero. No puede tener, por ejemplo, 3 , -6 , 'foo' como sus “claves” allí.

Entonces, para implementar arrays, que están en PHP, hay una opción de hash-map, usa hash-function para manipular sus claves y transformarlas en enteros, que se pueden usar para arreglos-C. Sin embargo, esa función nunca podrá crear una biyección entre las claves de cadena y sus resultados de hash entero. Y es fácil entender por qué: porque la cardinalidad del conjunto de cuerdas es mucho, mucho mayor que la cardinalidad del conjunto de enteros. Vamos a ilustrar con un ejemplo: recuentaremos todas las cadenas, hasta la longitud 10, que solo tienen símbolos alfanuméricos (por lo tanto, 0-9 , az y AZ , total 62): son 62 10 cadenas posibles. Está alrededor de 8.39E + 17 . Compáralo con alrededor de 4E + 9 que tenemos para el entero sin signo (entero largo, 32 bits) y obtendrás la idea: habrá colisiones .

PHP hash-map keys & collisions

Ahora, para resolver colisiones, PHP simplemente colocará elementos, que tienen el mismo resultado de función de hash, en una lista vinculada. Por lo tanto, hash-map no sería solo una “lista de elementos hash”, sino que almacenará punteros en listas de elementos (cada elemento en cierta lista tendrá la misma clave de función hash). Y aquí es donde ha señalado cómo afectará la asignación de memoria: si su matriz tiene claves de cadena, que no provocaron colisiones, entonces no se necesitarían punteros adicionales dentro de esa lista, por lo que la cantidad de memoria se reducirá (en realidad, es una sobrecarga muy pequeña, pero, ya que estamos hablando de una asignación de memoria precisa , esto debe tenerse en cuenta). Y, de la misma manera, si las teclas de cadena darán lugar a muchas colisiones, se crearán más punteros adicionales, por lo que la cantidad total de memoria será un poco más.

Para ilustrar esas relaciones dentro de esas listas, aquí hay un gráfico:

enter image description here

Arriba está cómo PHP resolverá las colisiones después de aplicar la función hash. Entonces, una de las partes de su pregunta se encuentra aquí, los punteros dentro de las listas de resolución de colisión. Además, los elementos de las listas vinculadas se suelen denominar segmentos y la matriz, que contiene punteros a las cabeceras de esas listas, se denomina internamente arBuckets . Debido a la optimización de la estructura (para eliminar elementos más rápidamente), el elemento de lista real tiene dos punteros, elemento anterior y elemento siguiente, pero eso solo hará que la diferencia en la cantidad de memoria para matrices de no colisión / colisión sea un poco más amplia. pero no cambiará el concepto en sí mismo.

Una lista más: orden

Para admitir completamente las matrices tal como están en PHP, también es necesario para mantener el orden , de modo que se logra con otra lista interna. Cada elemento de las matrices también es miembro de esa lista. No hará la diferencia en términos de asignación de memoria, ya que en ambas opciones esta lista debe mantenerse, pero para una imagen completa, menciono esta lista. Aquí está el gráfico:

enter image description here

Además de pListLast y pListNext , se pListNext punteros al orden de la lista de encabezado y cola. De nuevo, no está directamente relacionado con su pregunta, pero más adelante eliminaré la estructura interna del contenedor, donde están presentes estos indicadores.

Elemento de matriz desde adentro

Ahora estamos listos para investigar: ¿qué es el elemento de matriz? Entonces, cubo :

 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; 

Aquí estamos:

  • h es un valor entero (ulong) de la clave, es el resultado de la función hash. Para las claves enteras, es lo mismo que la clave misma (la función hash se devuelve)
  • pNext / pLast son punteros dentro de la lista vinculada de resolución de colisión
  • pListNext / pListLast son punteros dentro de la lista vinculada de resolución de orden
  • pData es un puntero al valor almacenado. En realidad, el valor no es el mismo que el insertado en la creación de la matriz, es una copia , pero, para evitar una sobrecarga innecesaria, PHP usa pDataPtr (así pData = &pDataPtr )

Desde este punto de vista, puede obtener lo siguiente: ¿dónde está la diferencia? Dado que la clave de cadena será hash (por lo tanto, h siempre es ulong y, por lo tanto, del mismo tamaño), será una cuestión de lo que se almacena en los valores. Por lo tanto, para su matriz de claves de cadena habrá valores enteros, mientras que para la matriz de claves enteras habrá valores de cadena, y eso hace la diferencia. Sin embargo, no, no es una magia : no se puede “guardar memoria” almacenando las teclas de cadena de esa manera todo el tiempo, porque si las teclas serían grandes y habría muchas, provocará colisiones generales ( bueno, con muy alta probabilidad, pero, por supuesto, no garantizado). Solo “funcionará” para cadenas breves arbitrarias, lo que no causará muchas colisiones.

Hash-table en sí

Ya se ha hablado sobre los elementos (cubos) y su estructura, pero también hay hash-table en sí mismo, que es, de hecho, matriz de datos-estructura. Entonces, se llama _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; 

No describiré todos los campos, dado que ya proporcioné mucha información, que solo está relacionada con la pregunta, pero describiré esta estructura brevemente:

  • arBuckets es lo que se describió anteriormente, el almacenamiento de cubos,
  • pListHead / pListTail son punteros a la lista de resolución de pedidos
  • nTableSize determina el tamaño de hash-table. Y esto está directamente relacionado con la asignación de memoria: nTableSize siempre tiene una potencia de 2. Por lo tanto, no importa si tienes 13 o 14 elementos en la matriz: el tamaño real será 16. Toma eso en cuenta cuando quieras estimar el tamaño de la matriz .

Conclusión

Es realmente difícil de predecir, una matriz será más grande que otra en su caso. Sí, hay pautas que siguen de la estructura interna, pero si las claves de cadena son comparables por su longitud a valores enteros (como 'four' , 'one' en la muestra) – la diferencia real será en cosas tales como: cuántas colisiones ocurrió, cuántos bytes se asignaron para guardar el valor.

Pero elegir la estructura adecuada debería ser una cuestión de sentido, no de memoria. Si su intención es construir los datos indexados correspondientes, la elección siempre será obvia. La publicación anterior solo tiene un objective: mostrar cómo funcionan las matrices en PHP y dónde puede encontrar la diferencia en la asignación de memoria en su muestra.

También puede consultar el artículo sobre matrices y hash-tables en PHP: es Hash-tables en PHP por el libro interno de PHP: he usado algunos gráficos a partir de ahí. Además, para darse cuenta de cómo se asignan los valores en PHP, consulte el artículo de Estructura de zval , puede ayudarlo a comprender cuáles serán las diferencias entre las cadenas y la asignación de enteros para los valores de sus matrices. No incluí las explicaciones aquí, ya que un punto mucho más importante para mí es mostrar la estructura de datos de la matriz y lo que puede ser una diferencia en el contexto de las teclas de cadena / enteros para su pregunta.

Aunque se accede a ambas matrices de una manera diferente (es decir, a través de cadena o valor entero), el patrón de memoria es en su mayoría similar.

Esto se debe a que la asignación de cadena ocurre como parte de la creación de zval o cuando se necesita asignar una nueva clave de matriz; la pequeña diferencia es que los índices numéricos no requieren una estructura zval completa, porque están almacenados como largos (sin signo).

Las diferencias observadas en la asignación de memoria son tan mínimas que pueden atribuirse en gran medida a la inexactitud de memory_get_usage() o las asignaciones debido a la creación adicional de cubetas.

Conclusión

La forma en que desee utilizar su matriz debe ser el principio rector al elegir cómo se debe indexar; la memoria solo debe convertirse en una excepción a esta regla cuando te quedes sin ella.

Del manual de PHP Garbage Collection 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 no devuelve la memoria liberada muy bien; Su uso principal en línea no lo requiere y la recolección efectiva de basura toma tiempo para proporcionar la salida; Cuando el script finalice, la memoria se devolverá de todos modos.

La recolección de basura ocurre.

  1. Cuando lo dices

    int gc_collect_cycles ( void )

  2. Cuando dejas una función

  3. Cuando termina el guion

Mejor comprensión de la recolección de basura de PHP desde un host web, (sin afiliación). http://www.sitepoint.com/better-understanding-phps-garbage-collection/

Si está considerando byte por byte, cómo se configuran los datos en la memoria. Diferentes puertos van a afectar esos valores. El rendimiento de las CPU de 64 bits es mejor cuando los datos se encuentran en el primer bit de una palabra de 64 bits. Para el máximo rendimiento de un binario específico, asignarían el inicio de un bloque de memoria en el primer bit, dejando hasta 7 bytes sin usar. Estas cosas específicas de CPU dependen de qué comstackdor se utilizó para comstackr el PHP.exe. No puedo ofrecer ninguna forma de predecir el uso exacto de la memoria, dado que se determinará de forma diferente por diferentes comstackdores.

Alma Do, la publicación va a los detalles de la fuente que se envía al comstackdor. Lo que las fonts PHP solicitan y el comstackdor optimiza.

Mirando los ejemplos específicos que publicaste. Cuando la clave es una letra ASCII, toman 4 bytes (64 bits) más por entrada … esto me sugiere (suponiendo que no hay basura o agujeros de memoria, etc.) que las claves ASCII son mayores que 64 bits, pero el las teclas numéricas encajan en una palabra de 64 bits. Me sugiere que uses una computadora de 64 bits y tu PHP.exe está comstackdo para CPU de 64 bits.

Las matrices en PHP se implementan como hashpaps. Por lo tanto, la longitud del valor que utiliza para la clave tiene poco impacto en el requisito de datos. En las versiones anteriores de PHP había una degradación significativa del rendimiento con arreglos grandes ya que el tamaño del hash se fijaba en la creación del arreglo: cuando se producían colisiones, un número creciente de valores hash se correlacionaba con listas de valores vinculadas que luego se tenían que buscar (con un algoritmo de O (n) en lugar de un único valor, pero más recientemente el hash parece usar un tamaño predeterminado mucho más grande o se cambia de tamaño de forma dinámica (simplemente funciona, no me molesta leer el código fuente).

Guardar 4 bytes de tus scripts no hará que Google tenga noches sin dormir. Si está escribiendo código que utiliza matrices grandes (donde los ahorros pueden ser más significativos) probablemente lo esté haciendo mal: el tiempo y los recursos necesarios para llenar la matriz podrían invertirse mejor en otro lugar (como el almacenamiento indexado).