PHP DO BÁSICO AO AVANÇADO – AULA 22 – JIT e OPCache, melhorando a performance do código

Tempo de Leitura: 29 Minutos

Nesta aula, vamos falar sobre o JIT (Just In Time) do PHP8, e das funções de controle de avançados para cache e otimização do código, o que pode ampliar ainda mais o leque de opções para serem usadas com o PHP, e também o número de projetos que possuam um padrão onde utilizar o PHP seja a principal opção. Essas linhas abordadas na aula de hoje, são muito importantes principalmente quando nossa aplicação passa a ganhar mais funcionalidades e um grande número de requisições passa a ser necessários, usar esses mecanismos permitem um maior escalonamento e uma maior abrangência e integração.

JIT (Just In Time) – Pré Compilador e Sua Utilização

O JIT é um compilador, que serve como um acelerador, tornando parte do código em instruções de máquina pré-compiladas e para que você possa entender melhor vamos falar um pouco sobre ele os primeiro os esforços para implementá-lo se iniciaram em meados de 2011 na Zend (principalmente pelo Dmitry Stogov) e o grande salto de desempenho do PHP 7 foi originalmente iniciado graças a essas tentativas de implementação.

Ele só não foi lançado em nenhuma das versões anteriores ao PHP8, por três motivos principais: não resultaram em ganhos substanciais de desempenho para aplicativos Web típicos, eram complexos de desenvolver e manter, e ainda tinham orientações adicionais que deveriam ser exploradas para melhorar o desempenho sem precisar propriamente introduzir o JIT. Mas em janeiro de 2019 o JIT já era parte do futuro iminente do PHP e as estruturas sem bloqueio de E/S (I/O) estavam sendo adotadas com mais frequência na produção do código.

O JIT é implementado como uma parte quase independente do Opcache (um cache acelerador de scripts, que falaremos mais abaixo). Ele pode ser habilitado e desabilitado em tempo de compilação e em tempo de execução no PHP. Quando habilitado, o código nativo dos arquivos PHP são armazenado em uma região adicional da memória compartilhada do Opcache. Mesmo que os opcodes (códigos de operação armazenados em cache pelo Opcache) estejam na forma de representação intermediária de baixo nível, eles ainda têm que ser compilados em código de máquina dai a melhoria.

O JIT não introduz nenhuma forma adicional de Representação Intermediária, mas utiliza DynASM (Dynamic Assembler for code generation engines) para gerar código nativo diretamente do byte-code em PHP. Com o JIT o código não seria mais executado pela VM da Zend, e sim pela própria CPU, o melhoraria consideravelmente a velocidade nos cálculos, e beneficiaria em partes as aplicações Web, pois estas também dependem de outros fatores como otimização de banco de dados, solicitações HTTP, otimização de recursos e acessos.

Em resumo, o JIT traduz as partes mais matemáticas (quentes) do código PHP diretamente em códigos de máquina, e ao contornar a compilação na hora da execução, ele seria capaz de trazer melhorias consideráveis na performance e uso de memória pelo próprio script, o que, gera benefícios e abre um leque de opções para o programador.

A partir destes fatos, gostaria de salientar que os benefícios do JIT no PHP não são apenas performance. Usar JIT pode abrir a porta para que o PHP seja utilizado com mais frequência em outros cenários além da Web e com uso intensivo de CPU (onde os benefícios de desempenho serão realmente muito substanciais) e para os quais o PHP provavelmente nem está sendo considerado hoje, como analise de dados, Inteligência Artificial, Machine Learning ou até mesmo Desenvolvimento de jogos e análises matemáticas ou probabilística.

Quando falamos em COMPILAR, passamos a ideia de converter um objeto em outro, podemos dizer que o processo de compilação envolve converter o nosso código para linguagem de máquina através de um programa chamado compilador. O que pode significar também condensar um vasto numero de objetos ou arquivos em um resultado final, o que nos ajuda a entender outro processo que o compilador faz, juntando todos os arquivos e blocos de código em um arquivo binário e guarda o resultado em um único “pacote”, como se fosse um “arquivo zipado”, que é enviado para o nosso cliente final já pronto para ser executado.

Por outro lado, até o PHP7 todo o código era somente INTERPRETADO, ou seja, pense nisso como um interprete de portugues/inglês, esse processo envolve receber uma informação de uma linguagem fonte e transmiti-la de maneira traduzida para uma linguagem alvo. Em computação, uma linguagem interpretada executa um processo similar, nosso código escrito será lido pelo interpretador, que faz toda a verificação básica como analisar lexicalmente (entender cada palavra ou expressão), analise de Parse ou Balanceamento (verifica se todos os parenteses, chaves, colchetes, aspas, e outros) estão balanceados corretamente, se todas as instruções estão corretamente feitas e terminadas (por exemplo se o ; está em todos os fins de instruções) e uma série de outras avaliações e irá então converter nosso código escrito para linguagem de máquina. Esse processo irá ocorrendo aos poucos, pois o interpretador somente vai fazer a analise de parser de cada arquivo quando ele for necessário, e irá realizar a conversão de cada instrução analisando linha por linha, e não o programa inteiro, diferentemente do que ocorre com o compilador.

Conseguimos perceber que o processo de interpretação será mais lento, que o processo compilado pois será executado de maneira repetida (a cada execução do programa), já no compilado essa primeira analise que é mais demorada que a conversão em si é feita uma única vez, e isso diminui a performance nas linguagens interpretadas. Sabemos que nosso usuário final provavelmente não irá perceber essa diferença de velocidade, pois isso ocorre por “debaixo dos panos”. Entretanto, sabemos que esse é um fator muito importante em qualquer programa, e um dos fatores mais determinantes na escolha de uma ou de outra linguagem de programação.

Mesmo possuindo a desvantagem da performance, as linguagens interpretadas possuem a vantagem de ser mais simples de “debugar”. Como a interpretação ocorre aos poucos, podemos visualizar com mais facilidade os erros que podem aparecer, e se constatarmos algum problema, poderemos prontamente resolvê-los. Isso é uma vantagem do código interpretado em relação ao compilado.

Entretanto, ainda existe uma terceira alternativa, que consegue mesclar os dois conceitos e pode nos trazer muitos benefícios, que é justamente o JIT que significa “Just in Time” (no momento exato, em livre tradução). E seu objetivo é otimizar a produção, é uma técnica que envolve mesclar conceitos de códigos compilados e interpretados, com o JIT nós continuamos a interpretar os códigos, entretanto, nós também iremos pré-compilar gerando um código conhecido como ByteCode onde já temos as etapas de parse e analise léxica já estão pré executadas, e algumas das instruções já estão pré-convertidas em linguagem de máquina, mas isso ocorre apenas com a parte do código que iremos usar no momento da execução, e não com todo o código como em um compilador. Isso representa um aumento muito grande de performance. No JIT iremos usar o processo de compilação, que é rápido, e ele será executado em partes isoladas do código, apenas na hora certa (“just in time”) assim, nós temos à nossa disposição “o melhor dos dois mundos”, unindo a velocidade da compilação com a simplicidade de debugar nosso código pelo fato de continuar sendo interpretado.

Esperamos que essa explicação possa ter nos ajudado a entender um pouco melhor o processo necessário para um programa ser executado, e com esses conceitos em mente também estamos mais preparados para decidir quais linguagens utilizar levando em conta suas arquiteturas e também as circunstâncias e requisitos de cada projeto que formos trabalhar.

Como um código PHP é executado?

Sabemos que o php é uma linguagem interpretada. Mas o que isso realmente quer dizer? Sempre que você quiser executar um código PHP, sendo este um snippet de código ou uma aplicação web inteira, você precisará passar pelo interpretador do php, que são os softwares utilizados para converter seu script em linguagem de máquina, e os mais comumente utilizados são o PHP CGI que usa uma interface comum dos servidores web chamada de CGI (Commom Gateway Interface) ou o PHP-FPM (Full Process Manager) que usa a FastCGI que é uma implementação mais nova e que permite o controle de threads e o próprio interpretador de linha de comando, que nada mais é que um interpretador que vai fazer a tradução de um código php e que pode ser executado na própria console do computador.

O trabalho destes interpretadores é bem direto: receber um código php, traduzir e interpretar este código e cuspir o resultado, seja ele no browser do usuário ou na tela ou em um arquivo.

Isto normalmente acontece em toda linguagem interpretada, algumas podem remover alguns passos, mas a ideia geral é a mesma, e no caso do PHP funciona assim:

  1. O código PHP é lido e transformado em uma série de palavras chave conhecidas como Tokens. Este processo permite que o interpretador possa entender que parte de código está escrito em qual parte do programa. Este primeiro passo é chamado de Lexing ou Tokenizing.
  2. Com os tokens em mãos, o interpretador PHP analisa esta coleção de tokens e tenta encontrar algum sentido neles, o que gera como resultado uma Árvore de Sintaxe Abstrata (Abstract Syntax Tree, ou AST) é gerada através de um processo chamado parsing. Esta AST é uma série de nós (ou nodos) indicando quais operações deverão ser executadas em nível de uma linguagem de montagem (asembler).
  3. Em posse do AST ele passa a entender as operações e suas precedências e transforma esta árvore em algo que possa ser executado diretamente pela CPU o que requer uma representação intermediária (Intermediate Representation, IR também conhecida como ByteCode) que em PHP é chamado de OpCode. O processo de transformar a AST em Opcodes é chamada de pre-compilação.
  4. Agora, com os Opcodes em mãos vem a parte massiva, a execução do código. O PHP tem um motor chamado Zend VM, que é capaz de receber uma lista de Opcodes e executá-las, e após executar todos os Opcodes, a Zend VM encerra a execução e o seu programa é terminado, porém a Zend VM continua em execução, principalmente em implementações baseadas em PHP-FPM e este é o motivo que ela foi o primeiro passo a otimizar a velocidade de execução dos códigos em PHP e apareceu na versão 7.

Como você pode reparar, o gargalo no tempo é justamente ter que fazer o lexing e o parsing do código a cada vez que for executar um script se o próprio código PHP não mudou, é dispendioso ter que refazer isso com frequência! O que importa mesmo são os Opcodes e é por isso que a extensão Opcache existe, ela guarda a pré-execução do código já convertido na arvore de OPCODES e compara o HASH do arquivo existente e pré-compilado com o atual (chamado pelo interpretador) e simplesmente se não houve mudança, ela executa o que já existem pronto.

A extensão Opcache

A extensão Opcache é compilada junto com o PHP e normalmente não há motivos pra desativá-la, o que essa extensão faz é adicionar uma camada de cache em memória para os Opcodes. Sua função é pegar os Opcodes recém gerados através da AST e jogá-los num cache para que as próximas execuções possam facilmente pular as fases de Lexing e Parsing.

Nesta segunda imagem, viu como caso o arquivo tenha sido pré-compilado simplesmente pulamos as etapas mais demoradas. Ela permite que você diga ao PHP-FPM pra fazer o parsing do seu código fonte, transformá-lo em Opcodes e jogar no cache antes mesmo de executar qualquer código escrito por você.

O Opcache faz com que a obtenção de Opcodes seja mais rápida para que possam ir direto para a Zend VM, isso é diferente do JIT pois este faz com que eles executem sem necessidade da Zend VM. A Zend VM é um programa escrito em C que age como uma camada entre Opcodes e a CPU. O que o JIT faz é gerar código compilado em tempo de execução para que o php possa pular a Zend VM e executar diretamente na CPU.

A implementação do JIT em PHP usa uma biblioteca chamada DynASM (Dynamic Assembler), que mapeia uma série de instruções de CPU de um formato específico em código assembly para vários tipos diferentes de CPU. Então o Just In Time compiler transforma Opcodes em código de máquina específico da arquitetura da CPU usando DynASM.

Neste momento você pode estar se perguntando porque então não compilamos TODO o código em PHP como fazemos com um código em JAVA ou em C/C++? A resposta é simples, porque o PHP é uma linguagem fracamente tipada, e criar analises para toda e qualquer possibilidade de tipagem existente para compilar o código em instruções nativas seria uma tarefa extra de comparações e o ganho de performance não seria obtido, além de que duplicar esta lógica de inferência de tipos com código de máquina não é uma tarefa trivial e potencialmente tornaria a execução mais lenta ainda.

Portanto um balanceamento de quanto de código deve ser compilado, e quanto deve ser executado diretamente pela ZendVM é o que está sendo realizado no JIT que faz um profiling dos Opcodes executados pela Zend VM e verifica quais fazem sentido ou não compilar baseado em suas configurações.

Então na extensão Opcache existem algumas instruções tentando detectar se determinados Opcodes deveriam ser compilados ou não. Caso sim, o compilador então transforma este Opcode em código de máquina utilizando DynASM e executa este código de máquina recém gerado.

A coisa interessante nisso tudo é que existe um limite em megabytes para o código compilado nesta implementação (também configurável), e a execução de código deve ser capaz de alternar entre JIT e código interpretado sem diferença alguma. O que nos leva a questões de como o JIT se comporta de maneira diferente de acordo com a nossa implementação de código, e é função do programador ou do DevOps fazer vários (dezenas) de testes com diferentes configurações do JIT e do OPCACHE para identificar quais serão melhor implementadas para a sua específica aplicação. Note, neste momento, os ganhos de performance estão sendo altamente notados em operações que fazem uso intenso de CPU porém, uma grande maioria das implementações atuais que usam o PHP fazem largo uso de operações de E/S (entrada e saida de dados) que não são diretamente impactadas pela otimização de um compilador.

Você esteja fazendo algo que não envolve E/S, como processamento de imagens ou machine learning, ou calculos probabilisticos ou intensos, ou qualquer outra coisa que não toque em I/O irá se beneficiar do Just In Time compiler. E esta também é a razão de algumas pessoas citarem que agora estamos mais próximos de poder escrever funções PHP nativas, para qualquer nível de código que precisemos, mesmo para uso com outras linguagens ou criação de pacotes, isso ainda são cenas para os próximos capitulos e o rumo que a linguagem vai tomar a partir desse ponto é só especulação.

Configurando o OPCACHE

O OPcache só pode ser compilado como uma extensão compartilhada. E caso esteja usando o XDebug ou outra extensão de depuração, essas devem ser carregadas somente depois da extensão OPCACHE, na realidade é uma boa prática que ela seja uma das primeiras coisas a serem carregadas, e para habilita-la você deve ter no seu arquivo php.ini a seguinte linha:

zend_extension=/full/path/to/opcache.so -> Nas versões LINUX e POSIX

zend_extension=C:\path\to\php_opcache.dll -> Nas versões para Windows

As seguintes diretrizes de configuração também devem ser ajustadas, para que a extensão funcione de maneira considerável, e os valores padrão são indicados para máquinas de produção e balanceiam performance e consumo de recursos, você deve fazer alguns testes de performance específicos para a sua aplicação, como já foi dito anteriormente, e verificar diversas possibilidade e os impactos em performance, consumo de memória, uso de disco e uso de CPU. As diretrizes estão citadas e com as configurações padrões do PHP em máquinas 64 Bits e Linux baseadas em Debian:

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
;opcache.save_comments = 0
;opcache.enable_file_override = 1

Note que as duas ultimas instruções você só pode habilitar se tiver realizado testes com sua aplicação, pois alguns códigos podem deixar de funcionar ou se tornarem instáveis quando essas configurações são modificadas, por este motivo, elas devem ser bem verificadas antes de serem colocadas em ambientes de produção. Além dessas configurações, outras diretrizes do php.ini podem ser ajustadas, e cada uma delas será explicada abaixo:

opcache.enable – Ativa o cache de opcode. Quando desativado, o código não é otimizado (0) ou armazenado em cache. A configuração opcache.enable não pode ser habilitada em tempo de execução através de ini_set(), só pode ser desabilitada, tentar habilitá-lo em um script gerará um aviso (warning).

opcache.enable_cli = Habilita ou desabilita a extensão para a interface CLI.

opcache.memory_consumption – Define o tamanho da memória compartilhada máxima que será usada pelo OPCache, em megabytes. O valor mínimo permitido é “8”, que é aplicado se um valor menor for definido.

opcache.interned_strings_buffer – A quantidade máxima de memória usada para armazenar strings internadas, em megabytes.

opcache.max_accelerated_files – O número máximo de chaves e/ou scripts armazenados na tabela hash do OPcache. O valor real usado será o primeiro número no conjunto de números primos {223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987, 262237, 524521, 1048793} que é maior ou igual ao valor configurado. O valor mínimo é 200. O valor máximo é 1000000. Valores fora dessa faixa são fixados na faixa permissível.

opcache.max_wasted_percentage – A porcentagem máxima de memória desperdiçada permitida antes de uma reinicialização ser agendada, se não houver memória livre suficiente. O valor máximo permitido é “50”, que é aplicado se um valor maior for definido.

opcache.use_cwd – Se ativado, o OPcache anexa o diretório de trabalho atual à chave do script, eliminando assim possíveis colisões entre arquivos com o mesmo nome de base. Desativar esta diretiva melhora o desempenho, mas pode interromper os aplicativos existentes, caso existam colisões de nomes em diretórios diferentes.

opcache.validate_timestamps – Se ativado, o OPcache verificará se há scripts atualizados a cada opcache.revalidate_freq segundos, quando esta diretiva está desabilitada, você deve redefinir o OPcache manualmente via opcache_reset(), opcache_invalidate() ou reiniciando o servidor Web para que as alterações no sistema de arquivos tenham efeito.

opcache.revalidate_freq – A frequência com que os carimbos de data/hora do script são verificados em busca de atualizações, em segundos. 0 resultará na verificação do OPcache por atualizações em cada solicitação. Esta diretiva de configuração é ignorada se opcache.validate_timestamps estiver desabilitado.

opcache.revalidate_path – Se desativado, os arquivos em cache existentes usando o mesmo include_path serão reutilizados. Portanto, se um arquivo com o mesmo nome estiver em outro lugar no include_path, ele não será encontrado, o que pode quebrar algumas aplicações ou causar comportamentos inesperados.

opcache.save_comments – Se desativado, todos os comentários da documentação serão descartados do cache opcode para reduzir o tamanho do código otimizado. Desabilitar esta diretiva de configuração pode quebrar aplicativos e estruturas que dependem da análise de comentários para anotações, incluindo Doctrine, Zend Framework 2 e PHPUnit.

opcache.fast_shutdown – Se habilitado, uma sequência de desligamento rápido é usada que não libera cada bloco alocado, mas depende do gerenciador de memória Zend Engine para desalocar todo o conjunto de variáveis ​​de solicitação em massa. Esta diretiva foi removida no PHP 7.2.0. Uma variante da sequência de desligamento rápido foi integrada ao PHP e será usada automaticamente, se possível.

opcache.enable_file_override – Quando habilitado, o opcode cache será verificado para ver se um arquivo já foi armazenado em cache quando file_exists(), is_file() e is_readable() são chamados. Isso pode aumentar o desempenho em aplicativos que verificam a existência e legibilidade de scripts PHP, mas corre o risco de retornar dados desatualizados se opcache.validate_timestamps estiver desabilitado.

opcache.optimization_level – Uma máscara de bits que controla quais etapas de otimização são executadas.

opcache.inherited_hack – Esta diretiva de configuração é ignorada, na verdade foi removida do código antes de ser enviado para as versões de testes públicos, só estando presente em algumas versões de desenvolvimento do código fonte.

opcache.dups_fix – Este hack deve ser habilitado apenas para solucionar os erros “Não é possível redeclarar a classe”.

opcache.blacklist_filename – A localização do arquivo de lista negra do OPcache. Um arquivo de lista negra é um arquivo de texto que contém os nomes dos arquivos que não devem ser acelerados, um por linha. Curingas são permitidos e prefixos também podem ser fornecidos. As linhas que começam com um ponto e vírgula são ignoradas como comentários. Um arquivo de lista negra simples pode ter a seguinte aparência:

; Corresponde a um arquivo específico.
/var/www/broken.php
; Um prefixo que corresponde a todos os arquivos que começam com x.
/var/www/x
; Uma correspondência de curinga.
/var/www/*

broken.php opcache.max_file_size – O tamanho máximo do arquivo que será armazenado em cache, em bytes. Se for 0, todos os arquivos serão armazenados em cache.

opcache.consistency_checks – Se diferente de zero, o OPcache verificará a soma de verificação do cache a cada N pedidos, onde N é o valor desta diretiva de configuração. Isso só deve ser habilitado durante a depuração, pois prejudicará o desempenho.

opcache.force_restart_timeout – O tempo de espera para que uma reinicialização agendada comece se o cache não estiver ativo, em segundos. Se o tempo limite for atingido, o OPcache assumirá que algo está errado e eliminará os processos que mantêm bloqueios no cache para permitir uma reinicialização. Se opcache.log_verbosity_level for definido como 2 ou acima, um aviso será registrado no log de erros quando isso ocorrer. Esta diretiva não é suportada no Windows.

opcache.error_log – O log de erros para erros do OPcache. Uma string vazia é tratada da mesma forma que stderr e resultará no envio de logs para erro padrão que será o log de erros do servidor Web na maioria dos casos, se outra forma não estiver explicitamente configurada.

opcache.log_verbosity_level – O nível de detalhamento do log. Por padrão, apenas erros fatais (nível 0) e erros (nível 1) são registrados. Outros níveis disponíveis são avisos (nível 2), mensagens de informação (nível 3) e mensagens de depuração (nível 4).

opcache.preferred_memory_model – O modelo de memória preferencial para uso do OPcache. Se deixado em branco, o OPcache escolherá o modelo mais apropriado, que é o comportamento correto em praticamente todos os casos. Os valores possíveis incluem mmap, shm, posix e win32.

opcache.protect_memory – Protege a memória compartilhada de gravações inesperadas durante a execução de scripts. Isso é útil apenas para depuração interna.

opcache.mmap_base – A base usada para segmentos de memória compartilhada no Windows. Todos os processos PHP precisam mapear a memória compartilhada no mesmo espaço de endereço. O uso desta diretiva permite que os erros “Não foi possível reconectar ao endereço de base” sejam corrigidos. Veja as notas para maiores detalhes.

opcache.restrict_api – Permite chamar funções da API OPcache apenas a partir de scripts PHP cujo caminho é iniciado a partir da string especificada. O padrão “” significa sem restrição.

opcache.file_update_protection – Impede o armazenamento em cache de arquivos com menos de N segundos. Ele protege contra o armazenamento em cache de arquivos atualizados de forma incompleta. No caso de todas as atualizações de arquivo em seu site serem atômicas, você pode aumentar o desempenho definindo-o como “0”.

opcache.huge_code_pages – Habilita ou desabilita a cópia do código PHP (segmento de texto) em PÁGINAS ENORMES. Isso deve melhorar o desempenho, mas requer uma configuração de sistema operacional apropriada. Disponível no Linux a partir do PHP 7.0.0 e no FreeBSD a partir do PHP 7.4.0.

opcache.lockfile_path – Caminho absoluto usado para armazenar arquivos de bloqueio compartilhados, só funciona em sistemas baseados em POSIX, colo linux ou free BSD

opcache.opt_debug_level – Produz despejos de opcode para depurar diferentes estágios de otimizações. 0x10000 produzirá opcodes conforme o compilador os produziu antes que ocorra qualquer otimização, enquanto 0x20000 produzirá códigos otimizados.

opcache.file_cache – Ativa e define o diretório de cache de segundo nível. Deve melhorar o desempenho quando a memória SHM está cheia, na reinicialização do servidor ou na reinicialização do SHM. O padrão “” desativa o cache baseado em arquivo.

opcache.file_cache_only – Habilita ou desabilita o cache de opcode na memória compartilhada. Nota: Antes do PHP 8.1.0, desabilitar esta diretiva com um cache de arquivo já populado tornava necessário limpar manualmente o cache de arquivo.

opcache.file_cache_consistency_checks – Habilita ou desabilita a validação da soma de verificação quando o script é carregado do cache de arquivo.

opcache.file_cache_fallback – Implica que opcache.file_cache_only = 1 para um determinado processo que falhou ao se reconectar à memória compartilhada (apenas para Windows). O cache de arquivo explicitamente habilitado é necessário. Cuidado Desativar esta opção de configuração pode impedir o início de processos e, portanto, não é recomendado, pois pode tornar o sistema do PHP inoperante.

opcache.validate_permission – Valida as permissões do arquivo em cache em relação ao usuário atual.

opcache.validate_root – Previne colisões de nomes em ambientes chroot. Isso deve ser habilitado em todos os ambientes chroot para prevenir o acesso a arquivos fora do chroot.

opcache.preload – Especifica um script PHP que será compilado e executado na inicialização do servidor, e que pode pré-carregar outros arquivos, incluindo-os ou usando a função opcache_compile_file(). Todas as entidades (por exemplo, funções e classes) definidas nesses arquivos estarão disponíveis para solicitações prontas para uso, até que o servidor seja encerrado. Nota: O pré-carregamento não é compatível com o Windows.

opcache.preload_user – O pré-carregamento do código como root não é permitido por razões de segurança. Esta diretiva permite que o pré-carregamento seja executado como outro usuário, definido seu nome.

opcache.cache_id – No Windows, todos os processos que executam o mesmo PHP SAPI sob a mesma conta de usuário com o mesmo ID de cache compartilham uma única instância OPcache. O valor do ID do cache pode ser escolhido livremente. Dica Para IIS, diferentes pools de aplicativos podem ter sua própria instância OPcache usando a variável de ambiente APP_POOL_ID como opcache.cache_id.

opcache.jit – Para uso típico, esta opção aceita um dos quatro valores de string:
disable Completamente desabilitado, não pode ser habilitado em tempo de execução.
Off Desativado, mas pode ser ativado em tempo de execução.
tracing / on Use rastreio JIT. Ativado por padrão e recomendado para a maioria dos usuários.
function Use a função JIT.

Para uso avançado, esta opção aceita um CRTO inteiro de 4 dígitos, em que os dígitos significam:
C (sinalizadores de otimização específicos da CPU) – 0: Desativa a otimização específica da CPU. 1: Habilite o uso de AVX, se a CPU suportar.
R (alocação de registro) – 0: Não executa alocação de registro. 1: Execute a alocação de registro local de bloco. 2: Execute a alocação de registro global.
T (gatilho) 0: Compila todas as funções no carregamento do script. 1: Compila funções na primeira execução. 2: Funções de perfil na primeira solicitação e compilar as funções mais usadas depois. 3: Crie perfis dinâmicos e compile funções importantes. 4: Atualmente não utilizado. 5: Use o JIT de rastreamento. Crie perfis dinâmicos e compile rastreios para segmentos de código importantes.
O (nível de otimização) 0: Sem JIT. 1: JIT mínimo (chamar manipuladores VM padrão). 2: Manipuladores de VM em linha. 3: Use inferência de tipo. 4: Use o gráfico de chamadas. 5: Otimize o script inteiro. O modo “rastreio” corresponde a CRTO = 1254, o modo “função” corresponde a CRTO = 1205.

opcache.jit_buffer_size – A quantidade de memória compartilhada a ser reservada para o código JIT compilado. Um valor zero desativa o JIT. Quando um inteiro é utilizado, o valor é medido em bytes. A notação resumida pode também ser usada.

opcache.jit_debug – Uma máscara de bits que especifica qual saída de depuração JIT habilitar. Para valores possíveis, consulte zend_jit.h na documentação.

opcache.jit_bisect_limit – Opção de depuração que desativa a compilação JIT após compilar um certo número de funções. Isso pode ser útil para dividir a fonte de um erro de compilação JIT.

opcache.jit_prof_threshold – Ao usar o modo de disparo “perfil na primeira solicitação”, esse limite determina se uma função é considerada quente. O número de chamadas para a função dividido pelo número de chamadas para todas as funções deve estar acima do limite. Por exemplo, um limite de 0,005 significa que as funções que representaram mais de 0,5% de todas as chamadas serão compiladas JIT.

opcache.jit_max_root_traces – Número máximo de rastreamentos de raiz.

opcache.jit_max_side_traces – Número máximo de traços laterais que um traço de raiz pode ter.

opcache.jit_max_exit_counters – Número máximo de contadores de saída de rastreamento lateral. Isso limita o número total de rastreamentos laterais que podem existir em todos os rastreamentos de raiz.

opcache.jit_hot_loop – Após quantas iterações, um loop é considerado quente.

opcache.jit_hot_func – Depois de quantas chamadas uma função é considerada quente.

opcache.jit_hot_return – Após quantas devoluções uma devolução é considerada quente.

opcache.jit_hot_side_exit – Após quantas saídas uma saída lateral é considerada quente.

opcache.jit_blacklist_root_trace – Número máximo de vezes que a compilação de um rastreamento de raiz é tentada antes de ser colocado na lista negra.

opcache.jit_blacklist_side_trace – Número máximo de vezes que a compilação de um rastreamento lateral é tentada antes de ser colocado na lista negra.

opcache.jit_max_loop_unrolls – Número máximo de tentativas de desenrolar um loop em um traço lateral, tentando alcançar o traço raiz e fechar o loop externo.

opcache.jit_max_recursive_calls – Número máximo de loops de chamadas recursivas desenroladas.

opcache.jit_max_recursive_returns – Número máximo de loops de retorno recursivos desenrolados.

opcache.jit_max_polymorphic_calls – Número máximo de tentativas de chamadas polimórficas sequenciais (dinâmicas ou de método). As chamadas acima desse limite são tratadas como megamórficas e não são sequenciadas.

NOTAS: Ao usar o PHP em uma plataforma Windows e habilitar o opcache, você pode se deparar com mensagens de ERRO 500 (erro interno do servidor) ocasionais. Eles parecerão aparecer inteiramente aleatórios. Quando isso acontecer, o log de eventos do Windows (Windows Logs / Application) mostrará várias entradas do Zend OPcache com Event ID. Mais informações exibirão a seguinte mensagem de erro: “O endereço básico marca a região da memória inutilizável”. Este problema pode ser resolvido adicionando o seguinte ao seu php.ini: opcache.mmap_base = 0x20000000 . Infelizmente, vários relatos públicos informam isso ter resolvido o problema, mais nenhum explicou extamente porque esse endereço de memória específico, que é basicamente acima de 512Mb.

Deve-se notar que de acordo com o RFC original opcache.preload armazena em cache arquivos pré-carregados eternamente para todas as instâncias do processo PHP subjacente. Isso significa que hospedar vários sistemas diferentes em um mesmo servidor pode resultar em algum comportamento inesperado. Se você trabalha com FPM, definindo um pool para cada aplicativo. A fim de otimizar o consumo de memória, você também pode usar um FPM Pool comum para todos os aplicativos e pré-carregar todo o framework lá e simplesmente não pré-carregar classes de espaço do usuário que podem ser armazenados em cache pelo opcache de qualquer maneira, mas é mais lento, pois será verificado se o arquivo foi alterado em cada solicitação.

Se você tentar alocar mais memória que está disponível usando opcache.memory_consumption, o PHP para de funcionar sem nenhum registro o que dificulta a depuração.

Funções Da Extensão OPCache

opcache_compile_file(string $filename) – Compila e armazena no cache o arquivo indicado em $filename, isso pode ser usado para preparar o cache após a reinicialização do servidor Web, pré-armazenando arquivos em cache que serão incluídos em solicitações posteriores. Esteja ciente de que o opcache apenas compilará e armazenará em cache arquivos mais antigos do que o início da execução do script. Se o nome do arquivo não puder ser carregado ou compilado, um erro de nível E_WARNING será gerado. Você pode usar @ para suprimir este aviso.

opcache_get_configuration() – Obtém as informações de configuração do cache atual. O retorno é um array, que pode ser impresso para a verificação dos dados atuais do cache, ou usado para um monitoramento do cache, se opcache.restrict_api estiver em uso e o caminho atual violar a regra, um E_WARNING será gerado; nenhuma informação de status será retornada. Um exemplo da saída desta função (retorno) se parece com:

array(3) {
  ["directives"]=>
  array(25) {
    ["opcache.enable"]=>
    bool(true)
    ["opcache.enable_cli"]=>
    bool(true)
    ["opcache.use_cwd"]=>
    bool(true)
    ["opcache.validate_timestamps"]=>
    bool(true)
    ["opcache.inherited_hack"]=>
    bool(true)
    ["opcache.dups_fix"]=>
    bool(false)
    ["opcache.revalidate_path"]=>
    bool(false)
    ["opcache.log_verbosity_level"]=>
    int(1)
    ["opcache.memory_consumption"]=>
    int(134217728)
    ["opcache.interned_strings_buffer"]=>
    int(8)
    ["opcache.max_accelerated_files"]=>
    int(4000)
    ["opcache.max_wasted_percentage"]=>
    float(0.05)
    ["opcache.consistency_checks"]=>
    int(0)
    ["opcache.force_restart_timeout"]=>
    int(180)
    ["opcache.revalidate_freq"]=>
    int(60)
    ["opcache.preferred_memory_model"]=>
    string(0) ""
    ["opcache.blacklist_filename"]=>
    string(0) ""
    ["opcache.max_file_size"]=>
    int(0)
    ["opcache.error_log"]=>
    string(0) ""
    ["opcache.protect_memory"]=>
    bool(false)
    ["opcache.save_comments"]=>
    bool(true)
    ["opcache.load_comments"]=>
    bool(true)
    ["opcache.fast_shutdown"]=>
    bool(true)
    ["opcache.enable_file_override"]=>
    bool(false)
    ["opcache.optimization_level"]=>
    int(4294967295)
  }
  ["version"]=>
  array(2) {
    ["version"]=>
    string(9) "7.0.4-dev"
    ["opcache_product_name"]=>
    string(12) "Zend OPcache"
  }
  ["blacklist"]=>
  array(0) {
  }
}

opcache_get_status(TRUE/FALSE) – Retorna um array com o status atual do cache, ou false se o cache está desabilitado. Esta função retorna informações de estado sobre a instância do cache na memória. Ele não retornará nenhuma informação sobre o cache do arquivo. Caso o valor TRUE seja fornecido como argumento, informações específicas do scrip atual serão incluídas, caso contrário será o status geral do cache. O retorno típico da função, caso seja passado o valor TRUE, se parece com:

array(8) {
  ["opcache_enabled"]=>
  bool(true)
  ["cache_full"]=>
  bool(false)
  ["restart_pending"]=>
  bool(false)
  ["restart_in_progress"]=>
  bool(false)
  ["memory_usage"]=>
  array(4) {
    ["used_memory"]=>
    int(10936144)
    ["free_memory"]=>
    int(123281584)
    ["wasted_memory"]=>
    int(0)
    ["current_wasted_percentage"]=>
    float(0)
  }
  ["interned_strings_usage"]=>
  array(4) {
    ["buffer_size"]=>
    int(8388608)
    ["used_memory"]=>
    int(458480)
    ["free_memory"]=>
    int(7930128)
    ["number_of_strings"]=>
    int(5056)
  }
  ["opcache_statistics"]=>
  array(13) {
    ["num_cached_scripts"]=>
    int(1)
    ["num_cached_keys"]=>
    int(2)
    ["max_cached_keys"]=>
    int(7963)
    ["hits"]=>
    int(0)
    ["start_time"]=>
    int(1410858101)
    ["last_restart_time"]=>
    int(0)
    ["oom_restarts"]=>
    int(0)
    ["hash_restarts"]=>
    int(0)
    ["manual_restarts"]=>
    int(0)
    ["misses"]=>
    int(1)
    ["blacklist_misses"]=>
    int(0)
    ["blacklist_miss_ratio"]=>
    float(0)
    ["opcache_hit_rate"]=>
    float(0)
  }
  ["scripts"]=>
  array(1) {
    ["/var/www/opcache.php"]=>
    array(6) {
      ["full_path"]=>
      string(17) "/var/www/opcache.php"
      ["hits"]=>
      int(0)
      ["memory_consumption"]=>
      int(1064)
      ["last_used"]=>
      string(24) "Tue Sep 16 09:01:41 2014"
      ["last_used_timestamp"]=>
      int(1410858101)
      ["timestamp"]=>
      int(1410858099)
    }
  }
}

opcache_is_script_cached(string $filename) – Retorna verdadeiro caso o script informado esteja em cache, falso caso não. Isso pode ser usado para detectar mais facilmente o “aquecimento” do cache para um script específico. Esta função verifica apenas o cache na memória, não o cache de arquivos. Caso o retorno tenha sido FALSE, você pode adicionar uma chamada a opcache_compile_file() para pré-preparar o cache.

opcache_invalidate(string $filename, bool $force = false) – Invalida o cache para $filename, ee force não for definido ou for falso, o script só será invalidado se a hora de modificação do script for mais recente do que os opcodes em cache. Esta função invalida apenas o cache na memória e não o cache de arquivos. Se for TRUE o cache será invalidado compulsivamente. Retorna verdadeiro se o cache de opcode para nome de arquivo foi invalidado ou se não havia nada para invalidar, ou falso se o cache de opcode está desabilitado. Esteja ciente de que apenas os arquivos existentes podem ser invalidados. Em vez de remover um arquivo do opcache que você excluiu, você precisa chamar opcache_invalidate antes de excluí-lo. Observe que a invalidação não remove nada do cache, apenas força uma recompilação.

opcache_reset() – Redefine o conteúdo do cache de opcode. Depois de chamar opcache_reset(), todos os scripts serão recarregados e refeitos na próxima vez que forem acessados. Essa função redefine apenas o cache na memória, não o cache de arquivos. Esta função não funciona para limpar o cache, caso tenha sido chamada pela interface CLI pois a CLI do PHP tem um cache opcode separado daquele usado pelo servidor web ou processo PHP-FPM.

Funcionamento da PRELOADING do OPCACHE

ATENÇÃO! O Preload não funciona em sistemas windows.

O PHP (desde sua versão 7.4) pode ser configurado para pré-carregar scripts no opcache quando o servidor ou serviço de FPM é iniciado, portanto, qualquer funções, classes, interfaces ou características nesses arquivos se tornarão globalmente disponíveis para todas as solicitações sem a necessidade de serem incluídas explicitamente, ou seja, estão pr-e carregadas e pré-compiladas, isso é um meio termo entre conveniência e desempenho porque o código está sempre disponível para uso na memória base (RAM) do sistema. Também requer reiniciar o processo de PHP para limpar scripts pré-carregados, o que significa que esse recurso só é prático para uso em produção, não em ambiente de desenvolvimento.

Observe que a compensação ideal entre desempenho e memória pode variar com o aplicativo. “Pré-carregar tudo” pode ser a estratégia mais fácil, mas não necessariamente a melhor estratégia. Além disso, o pré-carregamento só é útil quando há um processo persistente de uma solicitação para outra. Isso significa que, embora possa funcionar em um script CLI se o opcache estiver habilitado, geralmente é inútil. A exceção é ao usar o pré-carregamento em bibliotecas FFI (que estudaremos na proxima aula).

Para configurar o preload de um arquivo (e seus subjacentes) basta incluir a linha opcache.preload = /path/to/filename.php e o arquivo será executado uma vez na inicialização do servidor (PHP-FPM, mod_php, etc.) e carregará o código na memória já pre-compilado (JIT). Se o PHP for executado como root o que não é recomendado, e não permitido em algumas implementações, o valor opcache.preload_user pode especificar um usuário do sistema para executar o pré-carregamento. No script qualquer arquivo referenciado por include, include_once, require, require_once ou opcache_compile_file () será analisado e colocado na memória também.

Ambos include e opcache_compile_file() funcionam para pre-compilar um arquivo, mas têm implicações diferentes em como o código é tratado, include irá executar o código no arquivo, enquanto opcache_compile_file () não, o que significa que apenas o include suporta declaração condicional (funções declaradas dentro de um bloco if). Como a inclusão executará o código, os arquivos incluídos aninhados também serão analisados ​​e suas declarações pré-carregadas. Já opcache_compile_file() pode carregar arquivos em qualquer ordem, ou seja, se a.php define a classe A e b.php define a classe B que estende A, então opcache_compile_file() pode carregar esses dois arquivos em qualquer ordem.

Ao usar include, entretanto, a.php deve ser incluído primeiro. Em ambos os casos, se um script posterior incluir um arquivo que já foi pré-carregado, seu conteúdo ainda será executado, mas quaisquer símbolos que ele definir não serão redefinidos. Usar include_once não impedirá que o arquivo seja incluído uma segunda vez. Pode ser necessário carregar um arquivo novamente para incluir constantes globais definidas nele, já que elas não são tratadas por pré-carregamento. Qual abordagem é melhor, portanto, depende do comportamento desejado. Com código que usaria um autoloader, opcache_compile_file() permite maior flexibilidade. Com o código que de outra forma seria carregado manualmente, o include será mais robusto.

Conclusão

Com esta aula, você deve ter entendido melhor, como o PHP trata cada um dos seus arquivos, e será capaz de projetar a forma como seu software vai fazer uso de cada uma das funções e declarações, permitindo uma grande eficiência para a otimização do cache e o pre-compile pode ser otimizado para incluir código que é reusado em muitas partes do sistema. Uma ótima implementação de código pré-compilado é criar sistemas de autoload, onde todo o conteúdo de terceiros já esteja disponível e pre carregado, principalmente para servidores que rodam aplicações específicas.

Cabe ao programados, e a equipe de DevOps juntas, definirem quais as melhores políticas para uso de cache e de pre-compilação para um perfeito equilibrio entre a velocidade de processamento e uso da CPU, e também o consumo de memória, bem como as políticas para o carregamento e deploy de suas aplicações. Na proxima aula, vamos falar sobre o uso da FFI e o controle de fluxo de saída, outras duas ferramentas que são muito importantes para o desenvolvimento de aplicações.