Opcache: cache de bytecode

1. O que é o Opcache e por que ele é importante?

O Opcache é uma extensão do PHP que implementa cache de bytecode. Para entender sua importância, precisamos primeiro compreender como o PHP executa scripts tradicionalmente.

Quando um script PHP é executado sem Opcache, o interpretador realiza três etapas a cada requisição:

  1. Parsing (análise léxica e sintática): o código-fonte é lido do disco e convertido em tokens
  2. Compilação: os tokens são transformados em opcodes (bytecode)
  3. Execução: os opcodes são executados pela engine Zend

Esse processo consome recursos significativos de CPU e I/O de disco. O Opcache elimina as duas primeiras etapas após a primeira execução, armazenando o bytecode compilado em memória compartilhada. O resultado é uma redução drástica no tempo de resposta e no consumo de CPU, especialmente em aplicações com muitos arquivos PHP.

// Exemplo: medindo o impacto do Opcache
$start = microtime(true);
for ($i = 0; $i < 1000; $i++) {
    include 'arquivo_pesado.php';
}
$end = microtime(true);
echo "Tempo sem Opcache: " . ($end - $start) . " segundos";

2. Como o Opcache funciona internamente

Ciclo de vida com Opcache

Quando o Opcache está ativo, o fluxo de execução muda:

  1. O PHP verifica se o bytecode do script já está em cache
  2. Se estiver presente e válido, carrega diretamente da memória compartilhada
  3. Se não estiver, compila o script e armazena o resultado

Armazenamento em memória compartilhada (SHM)

O Opcache utiliza memória compartilhada (SHM) para armazenar:
- Bytecode compilado
- Metadados dos arquivos (timestamps, checksums)
- Tabelas de hash para busca rápida

Mecanismo de invalidação

O Opcache usa dois mecanismos principais para invalidar cache:

  • Timestamps: verifica a data de modificação do arquivo
  • Checksums: calcula um hash do conteúdo do arquivo (mais seguro, mas mais lento)
// Exemplo: verificando status do cache
$status = opcache_get_status();
if ($status && isset($status['opcache_statistics'])) {
    echo "Cache hits: " . $status['opcache_statistics']['hits'] . "\n";
    echo "Cache misses: " . $status['opcache_statistics']['misses'] . "\n";
}

3. Configuração básica do Opcache no php.ini

As diretivas mais importantes para configurar o Opcache são:

; Ativar Opcache
opcache.enable=1

; Memória total disponível para cache (em megabytes)
opcache.memory_consumption=128

; Número máximo de arquivos em cache
opcache.max_accelerated_files=4000

; Verificar timestamps dos arquivos
opcache.validate_timestamps=1

; Intervalo de revalidação (em segundos)
opcache.revalidate_freq=2

; Buffer de strings internas
opcache.interned_strings_buffer=8

; Máximo de memória desperdiçada antes de reiniciar
opcache.max_wasted_percentage=5

Configuração para produção vs. desenvolvimento

Produção:

opcache.validate_timestamps=0
opcache.revalidate_freq=0

Desenvolvimento:

opcache.validate_timestamps=1
opcache.revalidate_freq=0

4. Otimizando o uso de memória do Opcache

Calculando o tamanho ideal

O tamanho ideal de opcache.memory_consumption depende do tamanho total dos seus scripts PHP. Uma regra prática:

// Script para estimar o tamanho total dos scripts
$total_size = 0;
$iterator = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator('/caminho/para/seu/projeto')
);
foreach ($iterator as $file) {
    if ($file->getExtension() === 'php') {
        $total_size += $file->getSize();
    }
}
echo "Tamanho total dos arquivos PHP: " . round($total_size / 1024 / 1024, 2) . " MB";

Monitoramento de fragmentação

// Verificando uso de memória e fragmentação
$status = opcache_get_status(false);
if ($status) {
    $memory = $status['memory_usage'];
    $used_memory = $memory['used_memory'];
    $free_memory = $memory['free_memory'];
    $wasted_memory = $memory['wasted_memory'];
    $wasted_percentage = ($wasted_memory / ($used_memory + $free_memory + $wasted_memory)) * 100;

    echo "Memória usada: " . round($used_memory / 1024 / 1024, 2) . " MB\n";
    echo "Memória livre: " . round($free_memory / 1024 / 1024, 2) . " MB\n";
    echo "Memória desperdiçada: " . round($wasted_percentage, 2) . "%\n";
}

5. Estratégias de invalidação e recarga de cache

Em deploys

// Script de deploy: limpar cache do Opcache
function clear_opcache() {
    if (function_exists('opcache_reset')) {
        $result = opcache_reset();
        if ($result) {
            echo "Cache do Opcache limpo com sucesso!\n";
        } else {
            echo "Falha ao limpar cache do Opcache.\n";
        }
    }
}

// Invalidar arquivo específico
function invalidate_file($file_path) {
    if (function_exists('opcache_invalidate')) {
        if (opcache_invalidate($file_path, true)) {
            echo "Arquivo $file_path invalidado.\n";
        }
    }
}

Proteção contra arquivos parciais

; Esperar 2 segundos antes de cachear arquivos recém-modificados
opcache.file_update_protection=2

6. Opcache em ambientes de desenvolvimento

Configuração ideal para desenvolvimento

; Desabilitar cache (ou usar revalidação frequente)
opcache.enable=1
opcache.validate_timestamps=1
opcache.revalidate_freq=0

; Habilitar logs de cache
opcache.log_verbosity_level=1

; Não usar cache de arquivos em disco
opcache.file_cache=0

Ferramentas complementares

Para desenvolvimento, considere usar:
- Symfony VarDumper: fornece informações detalhadas sobre o Opcache
- Laravel Debugbar: exibe estatísticas do Opcache no painel de debug

7. Monitoramento e depuração do Opcache

Funções nativas de monitoramento

// Informações completas de configuração
$config = opcache_get_configuration();
echo "Versão do Opcache: " . $config['version']['version'] . "\n";
echo "Opcache habilitado: " . ($config['directives']['opcache.enable'] ? 'Sim' : 'Não') . "\n";

// Estatísticas detalhadas
$status = opcache_get_status(true);
if ($status) {
    $stats = $status['opcache_statistics'];
    echo "Taxa de acerto: " . round(
        ($stats['hits'] / ($stats['hits'] + $stats['misses'])) * 100, 2
    ) . "%\n";
    echo "Arquivos em cache: " . $stats['num_cached_scripts'] . "\n";
    echo "Cache cheio: " . ($stats['oom_restarts'] > 0 ? 'Sim' : 'Não') . "\n";
}

Identificando problemas comuns

// Verificar se o cache está cheio
$status = opcache_get_status();
if ($status['opcache_statistics']['oom_restarts'] > 0) {
    echo "ALERTA: O cache do Opcache está ficando sem memória!\n";
    echo "Considere aumentar opcache.memory_consumption\n";
}

// Verificar arquivos não cacheados
$cached_files = $status['scripts'] ?? [];
echo "Total de arquivos cacheados: " . count($cached_files) . "\n";

8. Boas práticas e armadilhas comuns

Cuidados com arquivos dinâmicos

// Evite isso - arquivos gerados dinamicamente podem causar problemas
$dynamic_file = '/tmp/cache_' . time() . '.php';
file_put_contents($dynamic_file, '<?php return ' . time() . ';');

// Prefira isso - use cache em memória
$cache_key = 'dynamic_data_' . date('Y-m-d');
$data = apcu_fetch($cache_key);
if ($data === false) {
    $data = generateExpensiveData();
    apcu_store($cache_key, $data, 3600);
}

Cache em disco (file_cache)

; Habilitar cache em disco como fallback
opcache.file_cache=/tmp/opcache
opcache.file_cache_only=0

Limitações importantes

  1. Ambientes compartilhados: Opcache não funciona bem em hosts compartilhados
  2. CLI: Por padrão, Opcache está desabilitado na CLI
  3. Includes dinâmicos: include $variavel pode não ser cacheado
; Habilitar Opcache para CLI (não recomendado em produção)
opcache.enable_cli=1

Referências