PHP e Performance, parte 1

Algo que alguns criticam no PHP é questão de performance, sempre digo que PHP é uma linguagem performática para o que ele se propõe a fazer. Contudo, performance não é algo simples, existem centenas de milhares de fatores que podem interferir, começando pelo hardware usado até a alocação desnecessária de variáveis.

Pesquisando um pouco sobre, decidi fazer uma série de artigos falando sobre performance no PHP, dando algumas dicas de melhorias que todos podem adotar. Veja abaixo a primeira delas, o uso da função unset() para desalocar memória.

unset()

Para entender melhor o problema considere o seguinte código abaixo:

echo "Quantidade de memória usada antes " . memory_get_usage() . PHP_EOL;
$arr = array();
for ($i = 0; $i < 1000000; ++$i) {
  $arr[] = rand();
}
echo "Quantidade de memória usada depois " . memory_get_usage() . PHP_EOL;

Executando este programa algo como o texto abaixo deve ser mostrado:

Quantidade de memória usada antes 232888
Quantidade de memória usada depois 233368

Claro que os valores serão diferentes para cada ambiente, porém, nesse caso, a diferença de uso de memória é de 480 bytes, contudo podemos melhorar o uso de memória colocando manualmente a chamada da função “unset()”, veja:

echo "Quantidade de memória usada antes " . memory_get_usage() . PHP_EOL;
$arr = array();
for ($i = 0; $i < 1000000; ++$i) {
  $arr[] = rand();
}
unset($arr); // <- chamando unset()
echo "Quantidade de memória usada depois " . memory_get_usage() . PHP_EOL;

No caso dessa execução os valores foram os seguintes:

Quantidade de memória usada antes 232936
Quantidade de memória usada depois 233256

A diferença agora foi de 320 bytes, o que significa um uso de 33,3% a menos de memória, um ganho considerável com uma alteração simples.

Em linguagens que possuem um sistema de coleta de lixo ou do inglês “Garbage Collector”, existem pontos chaves durante a execução que executarão o coletor de lixo, no PHP isso acontece em três momentos, ao chamar a função “unset()”, ao final da execução de uma função e ao final da execução do programa. O tempo de execução é o mesmo nos três casos, pois todos chamam a função “unset()”, porém existe a vantagem que ao chamar manualmente você conseguirá liberar memória no meio da execução, diferente das outras alternativas.

Bom pessoal, essa dica foi rápida e simples, não há contra indicação, por fim, deixando um último ponto, a performance não deveria ser sua única e principal prioridade, deve ser levada em conta, porém, muito mais vale um código legível, funcional e de fácil manutenção do que um código totalmente performático. Até a próxima.

  • Guilherme

    Ficou meio confuso o artigo. Primeiro que você não explica o que os valores 480 e 320 representam na memória (são bytes, a propósito). E que versão do PHP você está usando? Se eu rodar seu primeiro script aqui (PHP x64 7.0.5), ele alocou muito mais memória que no seu exemplo.

    No seu primeiro exemplo, após o loop, foram alocados apenas 480 bytes. LoL. Isso não faz sentido algum, a não ser que este seja um comportamento de versões antigas do PHP, porque 1 inteiro ocupa 4 bytes na memória (na maioria de processadores x86). E se estivéssemos alocando estes valores em uma linguagem de programação mais low-level como o C, depois daquele loop, deveriam ser alocados na memória, no mínimo 3.81 MB. Como PHP é uma linguagem interpretada, é óbvio que este valor é muito mais elevado.

    Pra mim não ficou claro a matemática que você usou para explicar um ganho de 33,3%. Se compararmos a quantidade final de bytes alocados do primeiro exemplo (233.368 bytes ou 227.89 KB) e a quantidade de bytes no segundo exemplo após o uso de unset() (233.256 bytes ou 227.78 KB), a diferença seria apenas de 0.04%.

    Quando rodei o script aqui, antes da alocação o script usava 342.57 KB de memória, e depois da alocação, o uso de memória pelo script pulou para 34 MB, e finalmente, após o uso de unset(), voltou novamente para 342.57 KB).

    Finalizando, o unset() funciona bem sim (embora, se procurarmos mais a fundo sobre isso, algumas pessoas recomendarão que você não fique chamando unset() toda hora, porque primeiramente nem sempre o PHP ou o sistema necessariamente irá desalocar esta memória, e no fim pode ser um tiro no pé em relação à performance), mas na minha opinião não ficou bem explicado isso no artigo.

    • Jonathan Schweder

      Olá, agradeço o comentário, os valores colocados ali são todos em bytes, a versão do PHP que uso é a 7.1, branch master do repositório https://github.com/php/php-src com último commit do dia 10/04/2016. A quantidade de memória e seus valores que coloquei foi considerando apenas o trecho do loop, fazendo a conta ((memory_usage() depois do loop) – (memory_usage() antes do loop)), eu digo que esses valores são a diferença no texto. De fato liberar a memória manualmente com o unset() fará apenas efeito se você tiver mais código depois da função, é comum termos vários processos que usam vários loops, dar unset() nas variáveis que não são mais usadas diminui o consumo de memória, para verificar isso pode-se também usar a função memory_get_peak_usage(). Como eu disse no texto existem 3 momentos que o PHP chama o unset(), na chamada manual da função, ao fim de alguma function() e no fim da execução. Por fim como também disse no texto, performance não é nada simples, existem N fatores a se considerar, minha ideia aqui é mostrar algumas dicas.

  • Guilherme

    Ficou meio confuso o artigo. Primeiro que você não explica o que os valores 480 e 320 representam na memória (são bytes, a propósito). E que versão do PHP você está usando? Se eu rodar seu primeiro script aqui (PHP x64 7.0.5), ele alocou muito mais memória que no seu exemplo.

    No seu primeiro exemplo, após o loop, foram alocados apenas 480 bytes. LoL. Isso não faz sentido algum, a não ser que este seja um comportamento de versões antigas do PHP, porque 1 inteiro ocupa 4 bytes na memória (na maioria de processadores x86). E se estivéssemos alocando estes valores em uma linguagem de programação mais low-level como o C, depois daquele loop, deveriam ser alocados na memória, no mínimo 3.81 MB. Como PHP é uma linguagem interpretada, é óbvio que este valor é muito mais elevado.

    Pra mim não ficou claro a matemática que você usou para explicar um ganho de 33,3%. Se compararmos a quantidade final de bytes alocados do primeiro exemplo (233.368 bytes ou 227.89 KB) e a quantidade de bytes no segundo exemplo após o uso de unset() (233.256 bytes ou 227.78 KB), a diferença seria apenas de 0.04%.

    Quando rodei o script aqui, antes da alocação o script usava 342.57 KB de memória, e depois da alocação, o uso de memória pelo script pulou para 34 MB, e finalmente, após o uso de unset(), voltou novamente para 342.57 KB).

    Finalizando, o unset() funciona bem sim (embora, se procurarmos mais a fundo sobre isso, algumas pessoas recomendarão que você não fique chamando unset() toda hora, porque primeiramente nem sempre o PHP ou o sistema necessariamente irá desalocar esta memória, e no fim pode ser um tiro no pé em relação à performance), mas na minha opinião não ficou bem explicado isso no artigo.

    • Jonathan Schweder

      Olá, agradeço o comentário, os valores colocados ali são todos em bytes, a versão do PHP que uso é a 7.1, branch master do repositório https://github.com/php/php-src com último commit do dia 10/04/2016. A quantidade de memória e seus valores que coloquei foi considerando apenas o trecho do loop, fazendo a conta ((memory_usage() depois do loop) – (memory_usage() antes do loop)), eu digo que esses valores são a diferença no texto. De fato liberar a memória manualmente com o unset() fará apenas efeito se você tiver mais código depois da função, é comum termos vários processos que usam vários loops, dar unset() nas variáveis que não são mais usadas diminui o consumo de memória, para verificar isso pode-se também usar a função memory_get_peak_usage(). Como eu disse no texto existem 3 momentos que o PHP chama o unset(), na chamada manual da função, ao fim de alguma function() e no fim da execução. Por fim como também disse no texto, performance não é nada simples, existem N fatores a se considerar, minha ideia aqui é mostrar algumas dicas.

  • Guilherme

    Ficou meio confuso o artigo. Primeiro que você não explica o que os valores 480 e 320 representam na memória (são bytes, a propósito). E que versão do PHP você está usando? Se eu rodar seu primeiro script aqui (PHP x64 7.0.5), ele alocou muito mais memória que no seu exemplo.

    No seu primeiro exemplo, após o loop, foram alocados apenas 480 bytes. LoL. Isso não faz sentido algum, a não ser que este seja um comportamento de versões antigas do PHP, porque 1 inteiro ocupa 4 bytes na memória (na maioria de processadores x86). E se estivéssemos alocando estes valores em uma linguagem de programação mais low-level como o C, depois daquele loop, deveriam ser alocados na memória, no mínimo 3.81 MB. Como PHP é uma linguagem interpretada, é óbvio que este valor é muito mais elevado.

    Pra mim não ficou claro a matemática que você usou para explicar um ganho de 33,3%. Se compararmos a quantidade final de bytes alocados do primeiro exemplo (233.368 bytes ou 227.89 KB) e a quantidade de bytes no segundo exemplo após o uso de unset() (233.256 bytes ou 227.78 KB), a diferença seria apenas de 0.04%.

    Quando rodei o script aqui, antes da alocação o script usava 342.57 KB de memória, e depois da alocação, o uso de memória pelo script pulou para 34 MB, e finalmente, após o uso de unset(), voltou novamente para 342.57 KB).

    Finalizando, o unset() funciona bem sim (embora, se procurarmos mais a fundo sobre isso, algumas pessoas recomendarão que você não fique chamando unset() toda hora, porque primeiramente nem sempre o PHP ou o sistema necessariamente irá desalocar esta memória, e no fim pode ser um tiro no pé em relação à performance), mas na minha opinião não ficou bem explicado isso no artigo.

    • Jonathan Schweder

      Olá, agradeço o comentário, os valores colocados ali são todos em bytes, a versão do PHP que uso é a 7.1, branch master do repositório https://github.com/php/php-src com último commit do dia 10/04/2016. A quantidade de memória e seus valores que coloquei foi considerando apenas o trecho do loop, fazendo a conta ((memory_usage() depois do loop) – (memory_usage() antes do loop)), eu digo que esses valores são a diferença no texto. De fato liberar a memória manualmente com o unset() fará apenas efeito se você tiver mais código depois da função, é comum termos vários processos que usam vários loops, dar unset() nas variáveis que não são mais usadas diminui o consumo de memória, para verificar isso pode-se também usar a função memory_get_peak_usage(). Como eu disse no texto existem 3 momentos que o PHP chama o unset(), na chamada manual da função, ao fim de alguma function() e no fim da execução. Por fim como também disse no texto, performance não é nada simples, existem N fatores a se considerar, minha ideia aqui é mostrar algumas dicas.