PD
5 dicas de desempenho ao desenvolver com Adianti Framework e PHP
Fechado
Neste artigo vou mostrar cinco dicas úteis para aumentar o desempenho em aplicações desenvolvidas em PHP com o Adianti Framework.
O PHP deu um grande salto de performance nas últimas versões. Manter a versão de desenvolvimento e consequentemente a de produção atualizadas, vão garantir que possamos continuamente nos beneficiar dessas melhorias. Porém, sempre é importante lembrar que existem eventuais ajustes a serem feitos no código-fonte (práticas que já vinham sendo notificadas via DEPRECATED) que para atualizar agora tornam-se essenciais. Nos links a seguir, temos os passos para migração desde o 5.3 até o 7.0. Todas as sessões são importantes, principalmente "Backward Incompatible Changes", que são mudanças que provocam incompatibilidades:
php.net/manual/pt_BR/migration54.php
php.net/manual/pt_BR/migration55.php
php.net/manual/pt_BR/migration56.php
php.net/manual/pt_BR/migration70.php
Geralmente as mudanças são em poucas funções. Em contrapartida, as vantagens são grandes. O link a seguir mostra um benchmark no qual o tempo de processamento caiu em média de 3.9s (php-5.3) para 2.3s (php-5.6) somente com a atualização do PHP:
www.lornajane.net/posts/2014/php-5-6-benchmarks
Já este outro artigo mostra um benchmark no qual uma instalação e Workpress com php-7 responde 604 req/seg, enquanto a mesma com php-5.3 responde 213 req/seg:
https://kinsta.com/blog/hhvm-vs-php-7/
Já nesta outra apresentação, realizada pelo Rasmus Lerdorf, aponta que o php-7 deve apresentar um desempenho médio cerca de 100% superior que o php-5.6 na maioria das aplicações:
talks.php.net/velocity15#/php7
Além disso, este outro slide (#21) aponta uma redução no consumo de memória em torno de 50% entre o php-5.3 e o php-5.6:
pt.slideshare.net/wimg/the-why-and-how-of-moving-to-php-55-shorter
Por fim, este infográfico apresenta alguns saltos de desempenho entre o php5.6 e o php-7:
www.zend.com/en/resources/php7_infographic
Um dos recursos que provocou grande melhoria no desempenho do php foi sem dúvidas o cache de arquivos. No lugar de realizar o carregamento (require, include) dos arquivos à cada requisição, os scripts são mantidos pré-processados e compilados em memória, otimizando muito a execução do programa. Este recurso passou a ser distribuído nativamente a partir do php-5.5:
Para carregar a biblioteca, é necessário definir "zend_extension" no php.ini, ou em
algum arquivo carregado a partir dele:
Obs: Se for compilar o PHP na mão (configure, make, make install), é importante adicionar ao final do configure (--enable-opcache).
O cache de arquivos possui as seguintes configurações específicas no php.ini:
Maiores informações em:
php.net/manual/pt_BR/opcache.installation.php
O cache de arquivos melhora o desempenho da aplicação de maneira consistente e perceptível, mas é importante percebermos que a requisição de scripts é uma operação ínfima se comparada à outras, como a busca de dados em bases de dados relacionais (SQL), por exemplo. No próximo tópico abordaremos isso.
Como vimos no tópico anterior, o cache de arquivos é importante, mas o grande salto de desempenho está no cache de dados (objetos).
O cache de objetos no Adianti funciona por que utilizamos o padrão de projeto Active Record. Assim, cada registro do banco de dados é tratado como um objeto em memória. O framework possui de maneira nativa uma classe chamada TAPCache, que se comunica com o APC do PHP. Para quem não sabe, o APC permite armazenar variáveis em memória RAM. O funcionamento se dá como exposto na figura a seguir. Sempre que um objeto é gravado pelo método store(), ele é gravado tanto no banco de dados (a), quanto no cache (d). Agora, sempre que um objeto for requisitado por meio da instanciação direta (Ex: new Estado(10)), ou indireta ($matricula->aluno->cidade->nome), primeiro o framework verifica se o objeto está na memória RAM (c). Caso encontrado, ele é disponibilizado imediatamente para a aplicação. Caso ele não esteja na memória RAM (c), então ele é carregado do banco de dados (b), e gravado na memória RAM (d), para então ser disponibilizado para a aplicação.
Este recurso vem desabilitado por padrão. Mas para ligar é muito simples. Em primeiro lugar é preciso ter o APC instalado (apt-get install php5-apcu). E em seguida, ligar o cache nas classes que você deseja manter em memória. É importante lembrar que os objetos são carregados no cache na medida em que são acessados pela primeira vez. Veja a linha com a definição da constante CACHECONTROL. Você poderá usar a classe nativa TAPCache, ou indicar ali outra classe para manipular o cache. Você poderá implementar uma classe que armazena os dados em Memcached, por exemplo. A classe indicada deverá implementar a interface AdiantiRegistryInterface.
Mais referências em: www.adianti.com.br/forum/pt/view_1341?banco-de-dados-em-memoria-nao-
Algumas observações importantes:
- Os dados são carregados para o cache na medida em que são acessados (aos poucos);
- Você pode fazer um script para dar um loop em alguma classe e "forçar" a carga de toda uma tabela de vez;
- Se você mexer no registro do banco de dados, ele não estará atualizado no cache. Nesse caso, é melhor limpar o cache (reinicie o apache);
- Não armazene todas tabelas em RAM, você não terá RAM suficiente para isso. Escolha tabelas com até uma determinada quantidade de registros, faça cálculos.
O APC não é o único mecanismo de cache. Outra boa solução é o Redis (redis.io). Em breve, teremos uma classe para realizar comunicação com Redis.
Sempre que armazenamos dados em sessão ($_SESSION no PHP nativo, TSession::setValue() no Adianti Framework), os dados da sessão por default são armazenadas de maneira serializada em um arquivo no lado do servidor em pastas como /tmp ou /var/lib/php5/sessions, sendo que o caminho pode variar conforme a instalação do sistema operacional. Na dúvida, rode um phpinfo(); e busque pela variável "session.save_path" para descobrir onde os arquivos de sessão estão localizados.
Sempre que precisamos acessar o conteúdo da sessão, é realizada a leitura do arquivo serializado a partir do disco. Entretanto o acesso à disco é lento (claro que isso depende da tecnologia usada), mas em linhas gerais, a memória RAM é sempre mais rápida que o armazenamento em disco. Então neste tópico vamos aprender a armazenar as sessões em um diretório mapeado na memória RAM no Linux.
Os primeiros comandos criam o diretório e montam ele usando tmpfs.
Adicione isso ao /etc/fstab (para ficar permanente)
Em seguida, modifique a variável "session.save_path" para apontar para o diretório criado:
Pronto. Existem outras soluções usando Memcached ou Redis, mas essa solução é simples e rápida. Além disso, este post (stackoverflow.com/a/6022478) reforça que o acesso local (file/socket/pipe) sempre é mais rápido que o acesso à um socket de rede. Antes de adotar essa solução, não esqueça de verificar se você realmente possui memória RAM disponível para tal.
Obs: Para Windows tem a solução de usar Shared Memory. Mas não ninguém deveria rodar PHP no Windows, não é mesmo? Ele foi feito para rodar em ambientes Unix-like.
Referências:
https://amigotechnotes.wordpress.com/2014/04/06/improve-php-session-performance-by-utilizing-ram/
kvz.io/blog/2011/04/29/faster-php-sessions/
As classes da aplicação (subdiretório app no Adianti Framework) são localizadas dinamicamente a partir da estrutura de diretórios, o que pode tornar-se oneroso caso a estrutura de diretórios seja muito grande. Geralmente em ambientes Unix-like isso não é um problema, pois estes são bastante performáticos. Mas é possível indicarmos para o framework buscar as classes diretamente nos arquivos, sem precisar localizá-los. Para tal, basta criarmos no diretório raíz da aplicação um arquivo chamado map.php, com o seguinte conteúdo:
map.php
Neste arquivo, indicamos para cada classe sua exata localização. Agora precisamos executar o arquivo map.php logo após o carregamento do init.php dentro do engine.php:
Pronto, agora as classes serão localizadas diretamente a partir dos arquivos, não mais localizadas dinamicamente.
Outras técnicas importantes:
Neste artigo abordamos principalmente técnicas relativas ao desempenho na camada da aplicação. Porém, tão importante quanto preocupar-se com o desempenho da aplicação é preocupar-se com o desempenho do banco de dados. Quem nunca deparou-se com uma aplicação lenta quando na verdade só faltavam criar índices de busca?
Pois bem, aqui vão alguns links sobre ajustes de performance em PostgreSQL:
www.revsys.com/writings/postgresql-performance.html
leopard.in.ua/2013/09/05/postgresql-sessting-shared-memory/
https://wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server
www.postgresql.org/docs/9.4/static/runtime-config-resource.html
1. Usar sempre a última versão do PHP
O PHP deu um grande salto de performance nas últimas versões. Manter a versão de desenvolvimento e consequentemente a de produção atualizadas, vão garantir que possamos continuamente nos beneficiar dessas melhorias. Porém, sempre é importante lembrar que existem eventuais ajustes a serem feitos no código-fonte (práticas que já vinham sendo notificadas via DEPRECATED) que para atualizar agora tornam-se essenciais. Nos links a seguir, temos os passos para migração desde o 5.3 até o 7.0. Todas as sessões são importantes, principalmente "Backward Incompatible Changes", que são mudanças que provocam incompatibilidades:
php.net/manual/pt_BR/migration54.php
php.net/manual/pt_BR/migration55.php
php.net/manual/pt_BR/migration56.php
php.net/manual/pt_BR/migration70.php
Geralmente as mudanças são em poucas funções. Em contrapartida, as vantagens são grandes. O link a seguir mostra um benchmark no qual o tempo de processamento caiu em média de 3.9s (php-5.3) para 2.3s (php-5.6) somente com a atualização do PHP:
www.lornajane.net/posts/2014/php-5-6-benchmarks
Já este outro artigo mostra um benchmark no qual uma instalação e Workpress com php-7 responde 604 req/seg, enquanto a mesma com php-5.3 responde 213 req/seg:
https://kinsta.com/blog/hhvm-vs-php-7/
Já nesta outra apresentação, realizada pelo Rasmus Lerdorf, aponta que o php-7 deve apresentar um desempenho médio cerca de 100% superior que o php-5.6 na maioria das aplicações:
talks.php.net/velocity15#/php7
Além disso, este outro slide (#21) aponta uma redução no consumo de memória em torno de 50% entre o php-5.3 e o php-5.6:
pt.slideshare.net/wimg/the-why-and-how-of-moving-to-php-55-shorter
Por fim, este infográfico apresenta alguns saltos de desempenho entre o php5.6 e o php-7:
www.zend.com/en/resources/php7_infographic
2. Usar cache de arquivos com opcache
Um dos recursos que provocou grande melhoria no desempenho do php foi sem dúvidas o cache de arquivos. No lugar de realizar o carregamento (require, include) dos arquivos à cada requisição, os scripts são mantidos pré-processados e compilados em memória, otimizando muito a execução do programa. Este recurso passou a ser distribuído nativamente a partir do php-5.5:
Para carregar a biblioteca, é necessário definir "zend_extension" no php.ini, ou em
algum arquivo carregado a partir dele:
zend_extension=opcache.so
Obs: Se for compilar o PHP na mão (configure, make, make install), é importante adicionar ao final do configure (--enable-opcache).
O cache de arquivos possui as seguintes configurações específicas no php.ini:
[opcache]
opcache.enable=0
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.enable_cli=1
Maiores informações em:
php.net/manual/pt_BR/opcache.installation.php
O cache de arquivos melhora o desempenho da aplicação de maneira consistente e perceptível, mas é importante percebermos que a requisição de scripts é uma operação ínfima se comparada à outras, como a busca de dados em bases de dados relacionais (SQL), por exemplo. No próximo tópico abordaremos isso.
3. Usar cache de objetos com APC ou Redis
Como vimos no tópico anterior, o cache de arquivos é importante, mas o grande salto de desempenho está no cache de dados (objetos).
O cache de objetos no Adianti funciona por que utilizamos o padrão de projeto Active Record. Assim, cada registro do banco de dados é tratado como um objeto em memória. O framework possui de maneira nativa uma classe chamada TAPCache, que se comunica com o APC do PHP. Para quem não sabe, o APC permite armazenar variáveis em memória RAM. O funcionamento se dá como exposto na figura a seguir. Sempre que um objeto é gravado pelo método store(), ele é gravado tanto no banco de dados (a), quanto no cache (d). Agora, sempre que um objeto for requisitado por meio da instanciação direta (Ex: new Estado(10)), ou indireta ($matricula->aluno->cidade->nome), primeiro o framework verifica se o objeto está na memória RAM (c). Caso encontrado, ele é disponibilizado imediatamente para a aplicação. Caso ele não esteja na memória RAM (c), então ele é carregado do banco de dados (b), e gravado na memória RAM (d), para então ser disponibilizado para a aplicação.
Este recurso vem desabilitado por padrão. Mas para ligar é muito simples. Em primeiro lugar é preciso ter o APC instalado (apt-get install php5-apcu). E em seguida, ligar o cache nas classes que você deseja manter em memória. É importante lembrar que os objetos são carregados no cache na medida em que são acessados pela primeira vez. Veja a linha com a definição da constante CACHECONTROL. Você poderá usar a classe nativa TAPCache, ou indicar ali outra classe para manipular o cache. Você poderá implementar uma classe que armazena os dados em Memcached, por exemplo. A classe indicada deverá implementar a interface AdiantiRegistryInterface.
- <?php
- class Customer extends TRecord
- {
- const TABLENAME = 'customer';
- const PRIMARYKEY = 'id';
- const IDPOLICY = 'max'; // {max, serial}
- const CACHECONTROL = 'TAPCache';
- }
- ?>
Mais referências em: www.adianti.com.br/forum/pt/view_1341?banco-de-dados-em-memoria-nao-
Algumas observações importantes:
- Os dados são carregados para o cache na medida em que são acessados (aos poucos);
- Você pode fazer um script para dar um loop em alguma classe e "forçar" a carga de toda uma tabela de vez;
- Se você mexer no registro do banco de dados, ele não estará atualizado no cache. Nesse caso, é melhor limpar o cache (reinicie o apache);
- Não armazene todas tabelas em RAM, você não terá RAM suficiente para isso. Escolha tabelas com até uma determinada quantidade de registros, faça cálculos.
O APC não é o único mecanismo de cache. Outra boa solução é o Redis (redis.io). Em breve, teremos uma classe para realizar comunicação com Redis.
4. Use sessões em RAM
Sempre que armazenamos dados em sessão ($_SESSION no PHP nativo, TSession::setValue() no Adianti Framework), os dados da sessão por default são armazenadas de maneira serializada em um arquivo no lado do servidor em pastas como /tmp ou /var/lib/php5/sessions, sendo que o caminho pode variar conforme a instalação do sistema operacional. Na dúvida, rode um phpinfo(); e busque pela variável "session.save_path" para descobrir onde os arquivos de sessão estão localizados.
Sempre que precisamos acessar o conteúdo da sessão, é realizada a leitura do arquivo serializado a partir do disco. Entretanto o acesso à disco é lento (claro que isso depende da tecnologia usada), mas em linhas gerais, a memória RAM é sempre mais rápida que o armazenamento em disco. Então neste tópico vamos aprender a armazenar as sessões em um diretório mapeado na memória RAM no Linux.
Os primeiros comandos criam o diretório e montam ele usando tmpfs.
mkdir /var/lib/ramdisk
chmod 777 /var/lib/ramdisk
mount -t tmpfs -o size=512M tmpfs /var/lib/ramdisk/
Adicione isso ao /etc/fstab (para ficar permanente)
tmpfs /var/lib/ramdisk tmpfs size=512M,atime 0 0
Em seguida, modifique a variável "session.save_path" para apontar para o diretório criado:
session.save_path="/tmp/ramdisk"
Pronto. Existem outras soluções usando Memcached ou Redis, mas essa solução é simples e rápida. Além disso, este post (stackoverflow.com/a/6022478) reforça que o acesso local (file/socket/pipe) sempre é mais rápido que o acesso à um socket de rede. Antes de adotar essa solução, não esqueça de verificar se você realmente possui memória RAM disponível para tal.
Obs: Para Windows tem a solução de usar Shared Memory. Mas não ninguém deveria rodar PHP no Windows, não é mesmo? Ele foi feito para rodar em ambientes Unix-like.
Referências:
https://amigotechnotes.wordpress.com/2014/04/06/improve-php-session-performance-by-utilizing-ram/
kvz.io/blog/2011/04/29/faster-php-sessions/
5. Carga direta de arquivos
As classes da aplicação (subdiretório app no Adianti Framework) são localizadas dinamicamente a partir da estrutura de diretórios, o que pode tornar-se oneroso caso a estrutura de diretórios seja muito grande. Geralmente em ambientes Unix-like isso não é um problema, pois estes são bastante performáticos. Mas é possível indicarmos para o framework buscar as classes diretamente nos arquivos, sem precisar localizá-los. Para tal, basta criarmos no diretório raíz da aplicação um arquivo chamado map.php, com o seguinte conteúdo:
map.php
- <?php
- AdiantiCoreLoader::setClassPath('CustomerDataGridView',
- 'app/control/Organization/ComplexViews/CustomerDataGridView.class.php');
- AdiantiCoreLoader::setClassPath('CustomerFormView',
- 'app/control/Organization/ComplexViews/CustomerFormView.class.php');
- ?>
Neste arquivo, indicamos para cada classe sua exata localização. Agora precisamos executar o arquivo map.php logo após o carregamento do init.php dentro do engine.php:
- <?php
- require_once 'init.php';
- require_once 'map.php';
- ?>
Pronto, agora as classes serão localizadas diretamente a partir dos arquivos, não mais localizadas dinamicamente.
Outras técnicas importantes:
Neste artigo abordamos principalmente técnicas relativas ao desempenho na camada da aplicação. Porém, tão importante quanto preocupar-se com o desempenho da aplicação é preocupar-se com o desempenho do banco de dados. Quem nunca deparou-se com uma aplicação lenta quando na verdade só faltavam criar índices de busca?
Pois bem, aqui vão alguns links sobre ajustes de performance em PostgreSQL:
www.revsys.com/writings/postgresql-performance.html
leopard.in.ua/2013/09/05/postgresql-sessting-shared-memory/
https://wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server
www.postgresql.org/docs/9.4/static/runtime-config-resource.html
Dicas de otimização do MYSQL seria muito bem vinda
Pablo,
Ao que se refere ao OPCache, a variável "opcache.enable" não deveria estar em "1" para que o módulo seja habilitado?
Pablo,
Sobre usar sessões em RAM, na variável "session.save_path" você indicou o valor "/tmp/ramdisk", sendo que o correto seria "/var/lib/ramdisk".
Caso a pessoa venha a utilizar o famoso "copy and paste" vai dar errado!