PHP: Como escrever um script de alto desempenho
Agência web » Notícias digitais » PHP: Como escrever um script de alto desempenho

PHP: Como escrever um script de alto desempenho

O PHP pode muito bem ser uma linguagem semelhante a um script e criticada por ser lenta, mas continua sendo uma das linguagens de servidor da web mais usadas. Ao longo do tempo, as grandes empresas web que utilizam PHP buscaram otimizar o motor. A introdução do conceito de Coletor de lixo na versão 5.3 foi um avanço notável. Mas há muitas maneiras de'otimiza o desempenho de um script.

Usadas principalmente no contexto de geração simples de páginas, essas otimizações, embora possam trazer um certo ganho, não são visíveis o suficiente para o usuário. A otimização de bancos de dados, servidores web com o uso de Nginx, otimização de motores com a chegada de HHVM ou mesmo HippyVM permitirão simplesmente acelerar a renderização de suas páginas e otimizar o tempo de respostas às suas consultas de maneira mais simples . Apesar disso, às vezes desenvolvemos scripts PHP com o objetivo de tornar tratamento pesado, ou os servidores da web não podem fazer nada.

Neste post vou detalhar três áreas de otimização que apliquei recentemente a um script que precisava processar arquivos CSV ou XLS contendo uma grande quantidade de informações. A quantidade de memória usada atingiu 1 GB sem problemas e pode durar mais de 1/2 hora.

Antes de apresentar a você as três noções de PHP que podem permitir que vocêotimizar o tempo de execução assim como a quantidade de memória ocupada, saiba que antes de reclamar de uma linguagem X, o algoritmo do seu script é responsável por boa parte do seu processamento. C++ pode ser infinitamente mais rápido que PHP, mas se seus algoritmos C++ forem ruins, ele não resolverá seus problemas imediatamente.

Desaloque suas variáveis, limite o consumo de memória

Antes do PHP 5.3 ser lançado, o problema do PHP era o consumo excessivo de memoria. Além disso, muitas vezes ouvi dizer que o consumo de memória de um script PHP nunca poderia ser reduzido… Se isso foi verdade por um tempo, felizmente não é mais. Esse gerenciamento utiliza a noção de Garbage Collector que veremos um pouco mais adiante.

Um bom hábito perdido...

Em alguns idiomas, isso era obrigatório. No PHP, essa noção foi completamente esquecida! Esta pequena função nativa unset(), é parauma utilidade formidável. É equivalente à função free() em C++ e permite desalocar e portanto de imediatamente libere a memória usado pela variável. A variável é completamente destruída. Isso inicialmente libera o PHP de variáveis ​​não utilizadas. Isso tem outro interesse, nos ajudando a estruturar melhor nossos algoritmos. É claro que quando um método termina as variáveis ​​são desalocadas automaticamente, mas você verá a seguir que isso permite otimizar o tempo que o "catador de lixo" gasta trabalhando ou simplesmente fazendo seu trabalho no caso de ser desativado . Há, portanto, também um ganho em termos de velocidade. E acredite, o GC consome muitos recursos para liberar memória.

Como eu disse no parágrafo anterior, ao final de uma função ou método, o PHP remove as variáveis ​​não utilizadas. Mas no caso de um script processando dados em massa, é possível que uma função administre as outras. Como em algumas linguagens, se for uma função principal, você provavelmente tenderá a armazenar as variáveis ​​da função antes de passá-las para outras funções. Podemos acabar rapidamente com uma grande quantidade de dados. Assim que não for mais usado, não adianta "carregar" com ele, nós o desalocamos.

Recentemente, enquanto escrevia um script onde estava extraindo um arquivo inteiro com mais de 15 MB, depois de usá-lo, excluí-o e permiti ganhar memória !

Entendendo o coletor de lixo

Foi enquanto pesquisava sobre gerenciamento de memória que me deparei com o artigo de um colega. Neste artigo já um pouco antigo, Pascal Martin explica a novidade que já não existe, o Garbage Collector.

O que é coletor de lixo?

Para quem não conhece ou já ouviu falar mas nunca teve oportunidade de usar, o Garbage Collector que se traduz como "recolhedor de migalhas" em francês, funcionalidade de baixo nível que no intervalo X, interrompe o processo em execução e realiza uma varredura da memória alocada pelo script para detectar quais variáveis ​​não estão mais acessíveis para excluí-las.

Não entendo, antes me disseram que no final de um método o PHP destrói as variáveis.
Isso não é inteiramente verdade. Da mesma forma que C ou C++ (pai do PHP), ao final de um método, o PHP sai e perde a referência que tinha no objeto. Em C, existe apenas a noção de ponteiro, em C++ coexistem as noções de ponteiro e referência.

Uma vez perdida a referência ao objeto, e uma vez iniciado o Garbage Collector, este determinará que a variável não está mais acessível e a destruirá.

Este conceito está muito presente em JAVA assim como em outras linguagens mais recentes e consequentemente de nível superior.

O que o coletor de lixo trouxe?

O GC forneceu considerável flexibilidade de desenvolvimento. É óbvio que desenvolver em C é mais técnico do que em Java, PHP ou outros. Apesar disso, algumas facetas agora estão esquecidas, como gerenciamento de memória e é nosso código que às vezes simpatiza. Se isso trouxe agilidade em nosso desenvolvimento, também retardou a execução de nossos programas.

Com essas ferramentas, o que não podemos esquecer é que sempre podemos controlar a memória de alguma forma. Questão de vontade, ou de necessidade...

Por fim, acho que esse tipo de ferramenta é muito prática, mas não pode substituir as instruções dadas pelos desenvolvedores. De minha parte, acho que a noção C++ de shared_ptr é muito mais interessante e pode oferecer ganhos de desempenho descomunais no futuro se Zend decidir usar esse método em alguns casos.

E em PHP?

Desde o PHP 5.3, quatro funções foram adicionadas.

  • gc_enable
  • gc_enabled
  • gc_disable
  • gc_collect_cycles
    Deixo a vocês o lazer de ler a documentação, mas para ser rápido, eles permitem ativar o GC, saber se está ativo, desativá-lo e iniciar manualmente a coleta.

Pascal Martin publicou no post que te indiquei acima, um roteiro onde ele realiza uma bateria de testes. Podemos ver claramente no relatório de teste que a versão sem GC atinge um consumo enorme de memória, chegando até ao limite e travando.

referência

Aqui está o código executado. Isso foi retirado do blog de Pascal Martin e mais particularmente de seu artigo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
classe Node {
público $parentNode;
público $childNodes = ordem();
função Node () {
$ This->nodeValue = str_repeat('0123456789', 128);
}
}
função criarRelacionamento() {
$pai= novo nó();
$criança = novo nó();
$parent->childNodes[] = $child;
$filho->paiNode = $pai;
}
para ($i = 0; $i 500000; $i++) {
criarRelacionamento();
// Esta parte é executada durante o segundo teste
if ($opções[“gc_manual”]) {
gc_collect_cycles();
}
}

Realizo três testes separadamente:

  1. Realizo este teste com as opções básicas. O Garbage Collector está ativado e o PHP o executa regularmente. O roteiro tem duração 7.55 secondes e a memória usada atingiu 20.98 MB.
  2. Realizo o mesmo teste desativando o Garbage Collector e chamando-o a cada volta do loop. O script dura 3.79 segundos e a memória usada atingiu o pico em 244.77 KB.
  3. Realizo um terceiro teste desativando o Garbage Collector e nunca coletando manualmente. A memória deve, portanto, encher fortemente. O script dura 4.46 segundos e a memória chegou Ir 1.98.

Com este teste, podemos ver claramente a importância do gerenciamento de memória. No nosso exemplo, levado ao extremo, podemos ver claramente a importância. Por um lado, o Garbage Collector faz muito trabalho no consumo de memória, mas isso tende a desacelerar a execução do script. Podemos ver isso muito bem comparando o 1º teste (com GC) e o 3º teste sem manejo.

Onde nosso trabalho de desempenho é feito é no teste 2. Sem gerenciamento automático de memória, gerenciamento manual otimizado neste script. Conseguimos acelerar a velocidade de processamento em quase o dobro (7.55s / 3.79s = 1.99). Desta maneira, também limitamos nossa memória a 245 KB contra 21 MB para gerenciamento automático. Isso é um coeficiente de quase 88 ( (20.98 * 1024) / 244.77 = 87.77).

Conclusão

Embora o exemplo acima leve o gerenciamento de memória ao limite, este teste pretende nos mostrar o quanto ele pode ser. importante estudar o gerenciamento de memória em nossos scripts. Os pagamentos podem ser impressionantes em alguns casos. O tempo de processamento, a memória e tudo o que está relacionado podem ser salvos.

Entenda e use referências

Como te falei um pouco antes, o PHP implementa a passagem de variável por referência, e como te falei acima esse tipo de passagem é mais ou menos equivalente a passagem por ponteiro.

  • Entendendo a passagem por referência
  • Entendendo a passagem do ponteiro
    Esta é uma parte importante, não só para PHP porque esta noção já existia muito antes, mas para TI. Basicamente, quando você declara uma função, a passagem é feita por cópia.

    // Passa pela função de cópia pública foo($arg) {…}

    Isso implica alguns prós e contras dependendo do seu código. O objeto é copiado, o que significa que a memória alocada aumentará de acordo com o peso do objeto passado. Isso pode ser ridículo se você passar um booleano, mas pode ser muito mais importante se você passar um array por exemplo.

1
2
3
4
5
6
7
8
9
10
11
público função Foo() {
$ a = 1;
barra($a);
eco $a; //$a é 1
}
público função Barra($arg) {
$arg = 2;
}

Nesse caso, vemos que o valor não foi modificado, isso pode ser interessante se usarmos a variável $a como base e nunca quisermos mexer no seu valor. Poderíamos falar aqui de uma noção que não existe no PHP, de uma variável constante (const).

1
2
3
4
5
6
7
8
9
10
11
12
público função Foo() {
$ a = 1;
$a = bar($a);
eco $a; //$a é 2
}
público função Barra($arg) {
$arg = 2;
retorno $arg;
}

Tenho certeza que você já escreveu métodos como este. Neste caso, é claramente um erro. Claro, a sintaxe é perfeitamente verdadeira, mas foi feita uma alocação para a cópia do parâmetro e foi feita a desalocação de uma variável. Chamadas de memória caras e tempo de processamento desnecessário. O formulário abaixo seria equivalente, mas muito mais rápido.

1
2
3
4
5
6
7
8
9
10
11
público função Foo() {
$ a = 1;
barra($a);
eco $a; //$a é 2
}
público função Barra(&$arg) {
$arg = 2;
}

Aqui realizamos exatamente o mesmo tratamento, mas é simplesmente mais rápido e consome menos.

Essa otimização é muito simples de executar e pode até facilitar a leitura do seu código. Existem várias sintaxes para saber fazer passagem por referência.

  1. Parâmetro passando por referência (que acabamos de ver)
  2. Função retorno por referência (não detalhado aqui, pois o motor PHP otimiza essa parte e não senti nenhum ganho convincente)
  3. Copiando uma variável por referência, não muito útil, mas formidável. Eu deixei você ler a documentação do PHP que é extremamente detalhada. Você poderia facilmente aprender coisas que eu omiti 🙂

Outra otimização

Graças às três otimizações acima, você não deve ter problemas para acelerar seu processamento. No entanto, existem outros itens. Eu listo os artigos interessantes sobre diferentes otimizações que você pode fazer.

  • PHP String Concat vs Array Implode

Conclusão

As três partes que acabei de detalhar para você constituem uma boa alternativa para o meu gosto para otimizar seus scripts e evitar reiniciar os desenvolvimentos em um linguagem mais rápida como uma linguagem compilada.

Fiz essas três passagens em um dos meus roteiros e, entre outras coisas, consegui reduzir o consumo de memória em 3 aproximadamente e atingir um ganho de velocidade em 2.

★ ★ ★ ★ ★