Bem-vindo à aula 78 do nosso curso de PHP! Hoje vamos mergulhar em um dos componentes mais importantes para a performance de aplicações PHP: o OPcache. Se você já se perguntou por que o PHP às vezes parece lento ou como frameworks como Laravel conseguem responder tão rápido, a resposta está, em grande parte, no OPcache. Nesta aula, vamos entender o ciclo de vida de um script PHP, desde o código-fonte até a execução, e como o OPcache otimiza esse processo.

Vamos começar com uma visão geral de como o PHP funciona internamente, depois exploraremos o que é o OPcache, como configurá-lo e, finalmente, os ganhos de performance que você pode esperar. Ao final, você terá um conhecimento sólido para aplicar essas otimizações em seus próprios projetos.

Como o PHP executa

Para entender o OPcache, precisamos primeiro entender o processo de execução de um script PHP. Quando você solicita uma página PHP, o servidor web (como Apache ou Nginx) encaminha a requisição para o interpretador PHP (geralmente o PHP-FPM). O interpretador segue um pipeline de etapas:

  1. Leitura do código-fonte: O PHP lê o arquivo .php do disco.
  2. Análise léxica (tokenização): O código é convertido em tokens, que são unidades significativas como palavras-chave, operadores e identificadores.
  3. Análise sintática (parse): Os tokens são transformados em uma Árvore Sintática Abstrata (AST), que representa a estrutura do código.
  4. Compilação: A AST é convertida em opcodes, que são instruções de baixo nível que a máquina virtual do PHP (Zend Engine) pode executar.
  5. Execução: A Zend Engine executa os opcodes, produzindo a saída.

Esse processo acontece a cada requisição, a menos que exista um mecanismo de cache. Para aplicações simples, isso pode ser aceitável, mas em sistemas com alto tráfego, a repetição dessas etapas para cada requisição consome tempo e CPU. É aqui que o OPcache entra.

Vamos ver um exemplo simples de como o PHP transforma código em opcodes. Podemos usar a função opcache_get_status() para inspecionar o cache, mas para visualizar os opcodes, podemos usar a extensão VLD (Vulcan Logic Disassembler) ou ferramentas online. Aqui está um exemplo de código:

<?php
function soma($a, $b) {
    return $a + $b;
}
echo soma(2, 3);
?>

O PHP tokeniza isso, cria a AST e gera opcodes como ADD e ECHO. O OPcache armazena esses opcodes, evitando que o processo seja refeito.

OPcache

OPcache é uma extensão do PHP que armazena o bytecode compilado (os opcodes) em memória compartilhada. Quando um script é solicitado, o OPcache verifica se o bytecode já existe no cache. Se existir e o arquivo não foi modificado, ele usa o bytecode em cache, pulando as etapas de tokenização, parsing e compilação. Isso reduz drasticamente o tempo de resposta e o uso de CPU.

O OPcache foi introduzido como uma extensão separada (Zend OPcache) e, a partir do PHP 5.5, tornou-se parte integrante do núcleo do PHP. Ele é ativado por padrão em muitas distribuições, mas pode ser necessário habilitá-lo manualmente.

Além de armazenar o bytecode, o OPcache também pode otimizar o código, por exemplo, eliminando opcodes redundantes. No entanto, seu principal benefício é o cache.

Vamos verificar se o OPcache está habilitado no seu ambiente:

<?php
if (extension_loaded('Zend OPcache')) {
    echo "OPcache habilitado\n";
    print_r(opcache_get_status());
} else {
    echo "OPcache não está habilitado\n";
}
?>

Quando você executa esse script, você verá informações detalhadas sobre o cache, como o número de hits, misses e o uso de memória.

Configuração

Para configurar o OPcache, você usa o arquivo php.ini ou arquivos de configuração específicos (como opcache.ini). As configurações mais importantes são:

  • opcache.enable: ativa ou desativa o OPcache (1 ou 0).
  • opcache.memory_consumption: quantidade de memória alocada para o cache (em megabytes). O padrão é 128 MB.
  • opcache.max_accelerated_files: número máximo de arquivos que podem ser armazenados em cache. O padrão é 10000.
  • opcache.validate_timestamps: se ativado (1), o OPcache verifica se o arquivo foi modificado desde o último cache. Isso é útil em desenvolvimento, mas em produção pode ser desativado para evitar overhead.
  • opcache.revalidate_freq: com que frequência (em segundos) o OPcache verifica alterações nos arquivos. O padrão é 2.
  • opcache.enable_cli: ativa o OPcache para scripts CLI (linha de comando). Normalmente desativado, pois scripts CLI são executados uma vez.

Um exemplo de configuração recomendada para produção:

opcache.enable=1
opcache.memory_consumption=128
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0
opcache.revalidate_freq=0
opcache.enable_cli=0
opcache.save_comments=1
opcache.fast_shutdown=1

É importante notar que opcache.validate_timestamps=0 significa que o OPcache não verificará se os arquivos foram alterados, então você precisará recarregar o PHP-FPM sempre que implantar novas versões do código. Em desenvolvimento, você pode manter validate_timestamps=1 e revalidate_freq=2 para que as alterações sejam detectadas rapidamente.

Para aplicar as configurações, você precisa reiniciar o PHP-FPM ou o Apache, dependendo do seu setup. No PHP-FPM, use systemctl restart php-fpm ou service php-fpm restart.

Ganhos

Os ganhos de performance com o OPcache são significativos. Em benchmarks, aplicações PHP com OPcache ativo podem ser de 2 a 3 vezes mais rápidas em termos de throughput (requisições por segundo). Isso porque a etapa de compilação é a mais custosa do ciclo de vida do PHP.

Além disso, o OPcache reduz o uso de CPU, pois as etapas de tokenização e parsing são eliminadas. Isso permite que servidores atendam mais requisições com os mesmos recursos.

Vamos fazer um teste simples para ilustrar. Crie um arquivo teste.php com um loop pesado:

<?php
$inicio = microtime(true);
for ($i = 0; $i < 100000; $i++) {
    $x = sqrt($i);
}
echo "Tempo: " . (microtime(true) - $inicio) . " segundos\n";
?>

Execute esse script várias vezes com OPcache desativado e depois ativado, e compare os tempos. Você verá que a primeira execução pode ser mais lenta (porque o OPcache precisa compilar e armazenar), mas as execuções subsequentes serão muito mais rápidas.

Em aplicações reais, como WordPress ou Laravel, o OPcache é essencial para escalar. Muitos provedores de hospedagem já o habilitam por padrão, mas é importante verificar e ajustar as configurações para seu caso de uso.

Boas práticas

Aqui estão algumas boas práticas ao usar OPcache:

  • Em produção, desative validate_timestamps para evitar a verificação de arquivos a cada requisição.
  • Ajuste memory_consumption e max_accelerated_files de acordo com o tamanho do seu aplicativo. Monitore o uso de memória com opcache_get_status().
  • Use opcache.revalidate_freq em desenvolvimento para que as mudanças sejam detectadas rapidamente.
  • Recarregue o PHP-FPM após implantar novas versões do código quando validate_timestamps=0.
  • Considere o uso de JIT (Just-In-Time) em PHP 8, que pode ser combinado com OPcache para melhorias adicionais.

Exercícios

  1. Explique o pipeline de execução do PHP e onde o OPcache atua.
  2. Qual é a diferença entre opcache.validate_timestamps=1 e 0? Em que cenário cada um é recomendado?
  3. Escreva um script PHP que exiba o status do OPcache, incluindo o número de hits e misses.
  4. Configure o OPcache em um ambiente de teste com as seguintes diretivas: memory_consumption=64, max_accelerated_files=5000, validate_timestamps=0. Descreva como você faria e quais os efeitos esperados.
  5. Dado o código a seguir, quantas vezes ele será compilado se o OPcache estiver ativo e o arquivo não for modificado? Justifique.

✓ Resposta: O pipeline é: leitura do código-fonte, tokenização, parsing (AST), compilação em opcodes e execução. O OPcache atua após a compilação, armazenando os opcodes em memória para reutilização.

✓ Resposta: Com validate_timestamps=1, o OPcache verifica se o arquivo foi modificado a cada revalidação (definida por revalidate_freq). Isso é útil em desenvolvimento. Com 0, ele não verifica, exigindo recarga manual do PHP-FPM após alterações; é recomendado em produção para reduzir overhead.

✓ Resposta:
<?php
if (function_exists('opcache_get_status')) {
    $status = opcache_get_status();
    echo "Hits: " . $status['opcache_statistics']['hits'] . "\n";
    echo "Misses: " . $status['opcache_statistics']['misses'] . "\n";
} else {
    echo "OPcache não disponível\n";
}
?>

✓ Resposta: Edite o php.ini (ou um arquivo opcache.ini) e adicione as diretivas. Depois reinicie o PHP-FPM. Com validate_timestamps=0, o cache nunca expira, então as alterações no código não serão refletidas até que você limpe o cache ou reinicie o PHP-FPM. Os efeitos esperados são: redução do overhead de verificação de arquivos e maior velocidade, mas é necessário reiniciar após deploys.

✓ Resposta: O código será compilado apenas uma vez, na primeira requisição. Nas próximas, o OPcache reutiliza o bytecode, desde que o arquivo não seja modificado. Isso acelera a execução.

Referências