Como a memory do PHP realmente funciona

Sempre ouvi e procurei novas “boas práticas de escrita”, por exemplo: é melhor (para desempenho) verificar se existe uma chave de matriz que procurar em matriz, mas também parece melhor para a memory:

Assumindo que temos:

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

isso aloca 1040 bytes de memory,

e

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

requer 1136 bytes

Eu entendo que a key e o value certamente terão um mecanismo de armazenamento diferente, mas por favor, você pode realmente me apontar para o princípio como funciona?

Exemplo 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

consumindo a mesma memory:

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

Nota: a resposta abaixo é aplicável para o PHP antes da versão 7, como no PHP 7 foram introduzidas grandes mudanças que também envolvem estruturas de valores.

TL; DR

Sua pergunta não é sobre “como a memory funciona em PHP” (aqui, suponho, você quis dizer “alocação de memory”), mas sobre “como funcionam as matrizes em PHP” – e essas duas questões são diferentes. Para resumir o que está escrito abaixo:

  • Arrays PHP não são “arrays” no sentido clássico. Eles são mapas de hash
  • Hash-map para PHP array tem estrutura específica e usa muitas coisas de armazenamento adicionais, como pointers de links internos
  • Os itens Hash-map para hash-map do PHP também usam campos adicionais para armazenar informações. E – sim, não só as chaves de linha / inteiro, mas também as seqüências de caracteres, que são usadas para suas chaves.
  • A opção com teclas de string no seu caso “ganhará” em termos de quantidade de memory porque ambas as opções serão esboçadas em ulong (unsigned long) keys hash-map, então a diferença real será em valores, onde a opção string-keys tem número inteiro (corrigido – comprimento), enquanto a opção de teclas inteiras possui valores de strings (length dependentes de caracteres). Mas isso pode não ser sempre verdadeiro devido a possíveis colisões.
  • As teclas “String-numeric”, como '4' , serão tratadas como teclas inteiras e traduzidas para o resultado hash inteiro, pois era uma chave inteira. Assim, '4'=>'foo' e 4 => 'foo' são as mesmas coisas.

Além disso, nota importante : os charts aqui são direitos autorais do livro interno do PHP

Hash-map para arrays PHP

Arrays PHP e Arrays C

Você deve perceber uma coisa muito importante: o PHP está escrito em C, onde coisas como “matriz associativa” simplesmente não existem. Então, em C “array” é exatamente o que “array” é – ou seja, é apenas uma área consecutiva na memory, que pode ser acessada por um deslocamento consecutivo . Suas “chaves” podem ser apenas numéricas, inteiras e apenas consecutivas, começando a partir de zero. Você não pode ter, por exemplo, 3 , -6 , 'foo' como suas “chaves” lá.

Então, para implementar arrays, que estão no PHP, há uma opção de hash-map, ele usa hash-function para hash suas chaves e transformá-las em números inteiros, que podem ser usados ​​para arrays C. Essa function, no entanto, nunca será capaz de criar uma bijeção entre as chaves de string e seus resultados de hash inteiros. E é fácil entender o porquê: porque a cardinalidade das cordas é muito maior do que a cardinalidade do conjunto inteiro. Vamos ilustrar com o exemplo: recontaremos todas as cordas, até o comprimento 10, que têm apenas símbolos alfanuméricos (assim, 0-9 , az e AZ , total 62): é possível adicionar 62 10 cordas totais. Está em torno de 8.39E + 17 . Compare-o com cerca de 4E + 9 que temos para o tipo inteiro não assinado (inteiro inteiro, 32 bits) e você terá a idéia – haverá colisões .

PHP hash-map keys & colisions

Agora, para resolver colisões, o PHP apenas colocará itens, que têm o mesmo resultado de function hash, em uma linked list. Então, hash-map não seria apenas “lista de elementos hash”, mas, em vez disso, irá armazenar indicadores para listas de elementos (cada elemento em determinada lista terá a mesma tecla de function hash). E é aí que você aponta para como afetará a alocação de memory: se sua matriz possui chaves de string, o que não resultou em colisões, então não serão necessárias ponteiras adicionais nessa lista, então a quantidade de memory será reduzida (na verdade, é uma sobrecarga muito pequena, mas, uma vez que estamos falando de alocação precisa da memory, isso deve ser levado a conta). E, da mesma forma, se suas chaves de seqüência resultarem em muitas colisões, mais novos pointers serão criados, então a quantidade total de memory será um pouco mais.

Para ilustrar essas relações dentro dessas listas, aqui está um gráfico:

insira a descrição da imagem aqui

Acima, existe como o PHP resolverá colisões depois de aplicar a function hash. Então, uma das suas partes da pergunta está aqui, dicas dentro de listas de resolução de colisão. Além disso, os elementos das listas ligadas geralmente são chamados de baldes e a matriz, que contém pointers para as cabeças dessas listas, é internamente chamada arBuckets . Devido à otimização de estrutura (por isso, para fazer coisas como eliminação de elemento, mais rápido), o elemento de lista real tem dois pointers, elemento anterior e próximo elemento – mas isso só fará diferença na quantidade de memory para conjuntos de colisão / colisão pouco mais largos, mas não vai mudar o conceito em si.

Mais uma lista: encomendar

Para suportar totalmente os arrays como estão no PHP, também é necessário manter a ordem , de modo que seja alcançado com outra lista interna. Cada elemento de arrays também é um membro dessa lista. Não vai fazer diferença em termos de alocação de memory, uma vez que em ambas as opções esta lista deve ser mantida, mas para a imagem completa, eu menciono essa lista. Aqui está o gráfico:

insira a descrição da imagem aqui

Além de pListLast e pListNext , os pointers para a cabeça e a cauda da lista de pedidos são armazenados. Novamente, não está diretamente relacionado à sua pergunta, mas, além disso, vou despejar a estrutura interna do balde, onde esses pointers estão presentes.

Array elemento de dentro

Agora estamos prontos para analisar: o que é elemento de matriz, então, balde :

 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; 

Aqui estamos:

  • h é um valor inteiro (ulong) da chave, é um resultado da function hash. Para as teclas inteiras é exatamente o mesmo que a própria chave (a function hash retorna em si mesma)
  • pNext / pLast são pointers dentro da linked list à resolução de colisão
  • pListNext / pListLast são pointers dentro da linked list à resolução de pedidos
  • pData é um ponteiro para o valor armazenado. Na verdade, o valor não é o mesmo inserido na criação da matriz, é uma cópia , mas, para evitar despesas gerais desnecessárias, o PHP usa pDataPtr (então pData = &pDataPtr )

A partir deste ponto de vista, você pode obter a próxima coisa para onde é a diferença: uma vez que a chave de string será hash (assim, h é sempre ulong e, portanto, o mesmo tamanho), será uma questão do que está armazenado em valores. Então, para sua matriz de chaves de string, haverá valores inteiros, enquanto que para a matriz de chaves inteiras haverá valores de seqüência de caracteres, e isso faz diferença. No entanto , não é uma mágica : você não pode “salvar a memory” com o armazenamento de teclas de cadeias de todas as maneiras, porque se suas chaves fossem grandes e haveria muitas delas, isso causará colisões sobre a cabeça ( bem, com probabilidade muito alta, mas, é claro, não garantido). Ele “funcionará” apenas para cordas curtas arbitrárias, o que não causará muitas colisões.

Hash-table própria

Já foi falado sobre elementos (baldes) e sua estrutura, mas também há tabela de hash própria, que é, de fato, matriz de estrutura de dados. Então, é chamado de _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; 

Não descreverei todos os campos, já que eu já forneci muita informação, que só está relacionada à questão, mas descreverei esta estrutura brevemente:

  • arBuckets é o que foi descrito acima, o armazenamento de baldes,
  • pListHead / pListTail são pointers para a lista de resolução de pedidos
  • nTableSize determina o tamanho da tabela hash. E isso está diretamente relacionado à alocação de memory: nTableSize é sempre poder de 2. Assim, não importa se você terá 13 ou 14 elementos na matriz: tamanho real será 16. Pegue isso para conta quando você deseja estimar o tamanho da matriz .

Conclusão

É realmente difícil de prever, uma matriz será maior do que outra em seu caso. Sim, há diretrizes que estão seguindo a partir da estrutura interna, mas se as chaves de string são comparáveis ​​pelo seu comprimento a valores inteiros (como 'four' , 'one' em sua amostra) – a diferença real será em coisas como – quantas colisões ocorreu, quantos bytes foram alocados para salvar o valor.

Mas escolher uma estrutura adequada deve ser uma questão de sentido, não de memory. Se a sua intenção é construir os dados indexados correspondentes, a escolha sempre será óbvia. O post acima é apenas sobre um objective: mostrar como os arrays funcionam realmente no PHP e onde você pode encontrar a diferença na alocação de memory em sua amostra.

Você também pode verificar o artigo sobre arrays & hash-tables em PHP: é Hash-tables em PHP pelo livro interno do PHP: usei alguns charts a partir daí. Além disso, para perceber, como os valores são alocados em PHP, verifique o artigo da estrutura do zval , ele pode ajudá-lo a entender, quais serão as diferenças entre alocação de strings e números inteiros para os valores de suas matrizes. Eu não incluí as explicações aqui, desde um ponto muito mais importante para mim – é mostrar a estrutura de dados da matriz e o que pode ser a diferença no contexto das chaves de cadeia / chaves inteiras para sua pergunta.

Embora ambos os arrays sejam acessados ​​de uma maneira diferente (ou seja, através de um valor de string ou inteiro), o padrão de memory é principalmente similar.

Isso ocorre porque a alocação de string acontece como parte da criação do zval ou quando uma nova chave de matriz precisa ser alocada; a pequena diferença é que os índices numéricos não requerem uma estrutura total de zval, porque eles são armazenados como longos (sem assinatura).

As diferenças observadas na alocação de memory são tão mínimas que podem ser atribuídas em grande parte à imprecisão de memory_get_usage() ou alocações devido à criação de balde adicional.

Conclusão

Como você deseja usar sua matriz deve ser o princípio orientador na escolha de como deve ser indexada; A memory só deve se tornar uma exceção a esta regra quando você acabar com isso.

Do manual do 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 

O PHP não retorna a memory liberada muito bem; Seu uso primário on-line não exige isso e a garbage collection efetiva leva tempo longe de fornecer a saída; Quando o script termina, a memory será devolvida de qualquer maneira.

A garbage collection acontece.

  1. Quando você diz para

    int gc_collect_cycles ( void )

  2. Quando você deixa uma function

  3. Quando o script termina

Melhor compreensão da coleção de lixo do PHP de um host, (sem afiliação). http://www.sitepoint.com/better-understanding-phps-garbage-collection/

Se você está considerando byte por byte como os dados são definidos na memory. Diferentes portas vão afetar esses valores. O desempenho das CPUs de 64 bits é melhor quando os dados se enquadram no primeiro bit de uma palavra de 64 bits. Para o desempenho máximo, um binário específico alocaria o início de um bloco de memory no primeiro bit, deixando até 7 bytes não utilizados. Este material específico da CPU depende do compilador usado para compilar o PHP.exe. Não posso oferecer nenhuma maneira de prever o uso exato da memory, uma vez que será determinado de forma diferente por compiladores diferentes.

Alma Do, a publicação explica os detalhes da fonte que é enviada para o compilador. O que a fonte PHP solicita e o compilador otimiza.

Olhando para os exemplos específicos que você postou. Quando a chave é uma letra ascii eles estão levando 4 bytes (64 bits) mais por input … isso sugere para mim, (assumindo que nenhum lixo ou buracos de memory, ect), que as chaves ascii são maiores que 64 bits, mas a As teclas numéricas estão em forma de uma palavra de 64 bits. Isso me sugere o uso de um computador com 64 bits e seu PHP.exe é compilado para CPUs de 64 bits.

Arrays em PHP são implementados como hashmaps. Portanto, o comprimento do valor que você usa para a chave tem pouco impacto sobre os requisitos de dados. Nas versões mais antigas do PHP, houve uma degradação significativa do desempenho com grandes matrizes, já que o tamanho do hash foi fixado na criação da matriz – quando as colisões começaram a ocorrer, o aumento de números de valores de hash seria mapeado para listas vinculadas de valores que então deveriam ser mais procurados (com um algoritmo O (n) em vez de um único valor, mas, mais recentemente, o hash parece usar um tamanho padrão muito maior ou é redimensionado dinamicamente (apenas funciona – não posso realmente ser incomodado ao ler o código-fonte).

Salvar 4 bytes de seus scripts não vai causar a Google noites sem dormir. Se você estiver escrevendo um código que usa matrizes grandes (onde as economias podem ser mais significativas) você provavelmente está fazendo isso errado – o tempo e os resources necessários para preencher a matriz podem ser melhor gastos em outros lugares (como o armazenamento indexado).