TL; DR
O compilador Just In Time no PHP 8 é implementado como parte da extensão Opcache e foi projetado para compilar o código operacional nas instruções do processador em tempo de execução.
Isso significa que, com o JIT, alguns códigos operacionais não precisam ser interpretados pelo Zend VM; essas instruções serão executadas diretamente como instruções no nível do processador.
JIT no PHP 8
Um dos recursos mais comentados do PHP 8 é o compilador Just In Time (JIT). É ouvido em muitos blogs e comunidades - há muitos rumores sobre isso, mas até agora não encontrei muitos detalhes sobre como o JIT funciona em detalhes.
Depois de muitas tentativas e frustrações para encontrar informações úteis, decidi estudar o código-fonte PHP. Combinando meu pouco conhecimento de C com todas as informações dispersas que consegui reunir até agora, consegui preparar este artigo e espero que ajude a entender melhor o PHP JIT.
Para simplificar: Quando o JIT funciona como esperado, seu código não será executado através da VM do Zend, mas sim diretamente como um conjunto de instruções no nível do processador.
Essa é a ideia toda.
Mas, para entender isso melhor, precisamos pensar em como o php funciona internamente. Não é muito difícil, mas é preciso uma introdução.
Eu já escrevi um artigo com uma rápida visão geral de como o php funciona . Se você acha que este artigo está ficando muito complicado, basta ler o antecessor e voltar. Isso deve facilitar um pouco as coisas.
Como o código PHP é executado?
Todos sabemos que php é uma linguagem interpretada. Mas o que isso realmente significa?
Sempre que você deseja executar o código PHP, seja um trecho de código ou um aplicativo da Web inteiro, você precisa passar pelo interpretador php. Os mais usados são o PHP FPM e o interpretador CLI. O trabalho deles é muito simples: obtenha o código php, interprete-o e retorne o resultado.
Essa é uma imagem comum para todas as línguas interpretadas. Alguns passos podem variar, mas a ideia geral é a mesma. No PHP, funciona assim:
- O código PHP é lido e convertido em um conjunto de palavras-chave conhecidas como Tokens. Esse processo permite que o intérprete entenda em qual parte do programa cada parte do código está gravada. Este primeiro passo é chamado Lexing ou Tokenizing .
- , PHP . (Abstract Syntax Tree — AST) , (parsing). AST , , . , «echo 1 + 1» « 1 + 1» , , « , — 1 + 1».
- AST, , . -, , (Intermediate Representation IR), PHP (Opcode). AST .
- Agora que temos os opcodes, vem o mais interessante: a implementação do código! O PHP tem um mecanismo chamado Zend VM capaz de obter uma lista de códigos de operação e executá-los. Após a execução de todos os códigos, o programa termina.
Para deixar um pouco mais claro, fiz um diagrama:
Um diagrama simplificado do processo de interpretação do PHP.
Bem simples como você pode ver. Mas também há um gargalo aqui: qual é o sentido de lexar e analisar seu código toda vez que você o executa, se seu código php pode nem mesmo mudar com tanta frequência?
Afinal, estamos interessados apenas em opcodes, certo? Certo! É por isso que a extensão Opcache existe .
Extensão Opcache
A extensão Opcache vem com PHP e geralmente não há motivo específico para desativá-la. Se você estiver usando PHP, provavelmente deverá ativar o Opcache.
O que ele faz é adicionar uma camada de cache de código de código compartilhado on-line. Seu trabalho é buscar os códigos de operação gerados recentemente em nosso AST e armazená-los em cache para que execuções posteriores possam ignorar facilmente as fases de análise e lexing.
Aqui está um diagrama do mesmo processo com a extensão Opcache em mente:
fluxo de interpretação do PHP com o Opcache. Se o arquivo já foi analisado, o php extrai o código de operação em cache para ele, em vez de analisá-lo novamente.
É impressionante como as etapas de lexing, análise e compilação são ignoradas.
Nota : É aqui que o recurso de pré-carregamento do PHP 7.4 é útil ! Isso permite que você diga ao PHP FPM para analisar sua base de código, convertê-la em opcodes e armazená-los em cache antes mesmo de fazer qualquer coisa.
Você pode começar a se perguntar onde pode colocar o JIT aqui, certo ?! Pelo menos espero que sim, e é por isso que estou escrevendo este artigo ...
O que o compilador Just In Time faz?
Depois de ouvir a explicação de Ziv em um episódio de podcasts PHP e JIT do PHP Internals News , consegui ter uma idéia do que o JIT realmente deveria fazer ...
Se o Opcache permitir uma busca mais rápida do código de operação para que ele possa pular diretamente para a Zend VM, JIT destinado a fazê-lo funcionar sem o Zend VM.
O Zend VM é um programa C que atua como uma camada entre o código operacional e o próprio processador. O JIT gera o código compilado no tempo de execução, para que o php possa pular a VM do Zend e pular diretamente para o processador . Em teoria, devemos nos beneficiar disso em termos de desempenho.
Pareceu estranho no começo, porque para compilar o código da máquina, você precisa escrever uma implementação muito específica para cada tipo de arquitetura. Mas, de fato, é bem real.
A implementação JIT no PHP usa a biblioteca DynASM (Dynamic Assembler) , que mapeia um conjunto de instruções da CPU em um formato específico para o código de montagem de muitos tipos diferentes de CPUs. Assim, o compilador Just In Time converte o código operacional em código de máquina específico da arquitetura usando o DynASM.
Embora um pensamento ainda me assombrasse ...
Se o pré-carregamento é capaz de analisar o código php como operacional antes da execução, e o DynASM pode compilar o código operacional para o código da máquina (compilação Just In Time), por que diabos não compilamos o PHP no lugar certo usando a compilação Ahead of Time ?!
Um dos pensamentos que tive do episódio do podcast foi que o PHP é digitado de maneira fraca, o que significa que o PHP geralmente não sabe que tipo de variável é até que o Zend VM tente executar um código de operação específico.
Você pode entender isso examinando o tipo de união zend_value , que possui muitos ponteiros para diferentes representações de tipo para uma variável. Sempre que o Zend VM tenta buscar um valor de zend_value, ele usa macros como ZSTR_VALque estão tentando acessar o ponteiro de seqüência de caracteres da concatenação de valor.
Por exemplo, esse manipulador de Zend VM deve manipular a expressão menor ou igual a (<=). Veja como ele se ramifica em muitos caminhos de código diferentes para adivinhar os tipos dos operandos.
Duplicar essa lógica de inferência de tipo com código de máquina não é viável e pode tornar as coisas ainda mais lentas.
A compilação final após a avaliação dos tipos também não é uma boa opção, pois compilar no código da máquina é uma tarefa que exige muita CPU. Portanto, compilar TUDO no tempo de execução é uma má ideia.
Como o compilador Just In Time se comporta?
Agora sabemos que não podemos deduzir tipos para gerar uma pré-compilação suficientemente boa. Também sabemos que a compilação em tempo de execução é cara. Como o JIT pode ser útil para PHP?
Para equilibrar essa equação, o PHP JIT tenta compilar apenas alguns opcodes que acha que valem a pena. Para fazer isso, ele analisa os opcodes executados pela máquina virtual Zend e verifica quais fazem sentido compilar. (dependendo da sua configuração) .
Quando um opcode em particular é compilado, ele delega a execução ao código compilado em vez de delegar na VM do Zend. Parece com o diagrama abaixo:
Fluxo de interpretação do PHP com o JIT. Se eles já estiverem compilados, os códigos de operação não serão executados pela VM do Zend.
Portanto, existem algumas instruções na extensão Opcache que determinam se determinado código operacional deve ser compilado ou não. Nesse caso, o compilador o converte em código de máquina usando o DynASM e executa esse código de máquina recém-gerado.
Curiosamente, como a implementação atual tem um limite de megabytes para código compilado (também configurável), a execução do código deve poder alternar perfeitamente entre o JIT e o código interpretado.
A propósito, essa palestra de Benoit Jacquemont sobre o JIT do php me ajudou MUITO a descobrir isso.
Ainda não sei em que casos específicos a compilação ocorre, mas acho que ainda não quero saber disso.
Portanto, seu ganho de produtividade provavelmente não será colossal
Espero que esteja muito mais claro agora POR QUE todos estão dizendo que a maioria dos aplicativos php não obterá muitos benefícios de desempenho ao usar o compilador Just In Time. E por que a recomendação do Ziv para criar perfis e experimentar diferentes configurações JIT para o seu aplicativo é o melhor caminho a seguir.
Os opcodes compilados geralmente se espalham por várias solicitações se você estiver usando o PHP FPM, mas isso ainda não é uma mudança de jogo.
Isso ocorre porque o JIT otimiza as operações da CPU e hoje em dia a maioria dos aplicativos php é mais centrada em E / S do que qualquer outra coisa. Não importa se as operações de processamento são compiladas se você precisar acessar o disco ou a rede de qualquer maneira. Os horários serão muito semelhantes.
Se apenas...
Você está fazendo algo que não é de E / S, como processamento de imagem ou aprendizado de máquina. Qualquer coisa que não seja E / S se beneficiará do compilador Just In Time. Esta é também a razão pela qual as pessoas agora dizem que se inclinam mais para escrever funções PHP nativas escritas em PHP em vez de C. A sobrecarga não será dramaticamente diferente se essas funções forem compiladas de qualquer maneira.
Um momento interessante de ser um programador PHP ...
Espero que este artigo tenha sido útil para você e você tenha uma melhor compreensão do que é o JIT no PHP 8. Sinta-se à vontade para me twittar se desejar adicionar algo que eu possa ter esquecido aqui, e não se esqueça de compartilhar isso com seus colegas desenvolvedores, certamente agregará um pouco de valor às suas conversas!
-- @nawarian
PHP: