Just in Time Compiler no PHP 8

Diego Brito Sousa
ViaHubtecnologia
Published in
5 min readMay 5, 2020

A versão 1 do PHP foi lançada em 1995. E era um conjunto de pequenas ferramentas escritas em C que faziam inúmeras funções.

O PHP surgiu para resolver os problemas das pessoas que queriam escrever código para WEB. Então o PHP ajudou os programadores web da época a lidarem com requisições HTTP. Era um conjunto de ferramentas CGI.

Atualmente é uma linguagem bem madura e estruturada. É uma linguagem interpretada e executada pela Zend Virtual Machine. O interpretador PHP passa por 4 passos básicos, Análise Léxica, Parseamento, Compilação e execução. São basicamente os mesmos passos de um compilador.

Fluxo básico do processo de compilação
Fluxo inicial de um processo de compilação

Lexing ou Análise Léxica.

O interpretador vai fazer uma análise do código escrito. Ele vai pegar todo o código fonte e transpilar para uma espécie de script contendo unidades semânticas chamadas tokens. Existe um número finito de tokens que possuem um significado bem definido dentro da linguagem e podem significar variáveis, operações, atribuições, etc.

Você pode ver erros de análise léxica quando o interpretador retorna um erro de parse com a mensagem exibindo algum token, como por exemplo T_VARIABLE, T_ECHO, T_PAAMAYIM_NEKUDOTAYIM etc. O prefixo T significa token.

A propósito, os dois primeiros tokens são fáceis de saber o que significam: o primeiro representa variável, o segundo representa o procedimento echo, já o terceito significa dois pontos duplos, (::), por algum motivo alguém escreveu o nome do token em hebraico.

Se você for curioso, há uma função do PHP, token_get_all, que avalia um código fonte fornecido e devolve um vetor com os tokens identificados no código. Vale a pena testar pra entender melhor.

E no próprio código fonte do PHP é possível verificar todos os tokens existentes da linguagem.

Parsing ou Análise da Árvore de Sintaxe Abstrata

Após o processo de análise léxica gerar os tokens é feita uma interpretação dessa sequência. Os tokens são analisados gramaticalmente seguindo cadeia de estruturas sintáticas. Dessa forma tenta-se interpretar o que cada trecho do programa está fazendo seguindo as estruturas conhecidas.

Exemplo de Árvore de Sintaxe Abstrata

https://en.wikipedia.org/wiki/Abstract_syntax_tree#/media/File:Abstract_syntax_tree_for_Euclidean_algorithm.svg

Compilação

Após a intepretação que o código faz, os tokens são compilados para códigos de operação, OpCodes, que são mnemônicos para as operações atômicas que este código deve executar quando iniciado. Mesmo que o PHP seja uma linguagem interpretada isso não significa que ela não passe pela compilação, pois interpretada significa de que ela não é pré-compilada.

O que a Engine do PHP faz é interpretar o código cada vez que é executado ao invés de pegar um código já compilado.

Visto que o processo de interpretação não é muito performático no PHP há uma extensão chamada OPCACHE que armazena Opcodes já compilados para acelerar o processo.

Execução

A execução dos OpCodes acontece dentro da Engine ZendVM que possui funções chamadas de manipuladores. Então os manipuladores de OpCodes mapeam as operações atômicas que cada mnemônico representa. Esse mapeamento da ZendVM depois é executado. Como já mencionado o OPCACHE pode saltar algumas etapas armazenando os mnemônicos já identificados, dessa forma aumentando a performance do processo até aqui.

Mas e o Just In Time Compiler?

O que ele faz é substituir a etapa de execução dentro da ZendVM. A ZendVM é escrita em C e performa várias operações antes de executar cada OpCode, isso a torna menos performática. É aí que entra o JIT: ao invés de executar toda essas operações da VM escrita em C ele executa diretamente no processador. Isso dá um ganho de desempelho muito grande.

O JIT é implementado dentro da extensão OpCache, então a mesma precisa estar instalada para o JIT funcionar. Dentro do php.ini é possível configurar as opções de controle do JIT usando a configuração opcache.jit, que consiste em 4 dígitos decimais na ordem CRTO.

CRTO e seus respectivos valores significam:

C — CPU specific optimization flags — sinalizador de otimização específica de CPU:

  • 0 — nenhum
  • 1 — habilita geração de instrução AVX

R — Register allocation — Alocação de registrador.

  • 0 — não fazer alocação de registrador
  • 1 — use alocador de registro local liner-scan
  • 2 — use alocador de registro global liner-scan

T — JIT trigger — Gatilho

  • 0 — Faz jit em todas as funções no primeiro carregamento do script
  • 1 — Faz jit na primeira execução da função
  • 2 — Perfila na primeira requisição e compila funções chave na segunda.
  • 3 — Perfila instantaneamente e compila funções essenciais
  • 4 — Compila funções com a marcação @jit

O — Optimization leval — Nível de otimização

  • 0 — Não faz jit
  • 1 — Faz o mínimo, chama manipuladores padrão da VM
  • 2 — Otimização seletiva de manipuladores
  • 3 — Faz jit otimizado baseado na inferência de tipo estático das funções individuais
  • 4 — Faz jit otimizada baseada na inferência de tipo estático da árvore de chamada
  • 5 — faz jit otimizada baseada na inferência de tipo e análise de procedimento interno

https://wiki.php.net/rfc/jit

O que esperar e o que não esperar do JIT

O PHP é muito usado para WEB, onde se usa muitas operações de entrada e saída e leitura de arquivos. Para essas operações o ganho não parece ser muito expressivo com o uso de JIT. Então talvez para as aplicações comuns a que estamos habituados como WordPress talvez ainda não vejamos vantagens.

Em contrapartida o ganho de desempenho em funções que fazem uso massivo de funções lógicas e matemáticas trazem a possibilidade de usar o PHP em ambiente que antes não era cogitável, como por exemplo inteligência artifical, marchine learning, tratamento de mídia, etc.

Renderizando um fractal de Mandelbrot com ZendVM à esquerda e JIT à direita:

Demontração da renderização de um Fractal de Mandelbrot em PHP usando ZendVM à esquerda e JIT à direita.

--

--