Benchmarks
Phasis é cerca de duas ordens de grandeza mais lento que V8 em JavaScript limitados pela distribuição de chamadas. Esse teto é o custo inerente de um interpretador executado em PHP em comparação com um compilador JIT nativo. Para quem incorpora o mecanismo, a pergunta não é "como ele se compara a V8" — e sim "ele é rápido o bastante para o que estou fazendo?" Para a maioria das cargas de trabalho de incorporação, a resposta é sim.
Quando o teto não importa
- Criação de templates — a renderização SSR de uma página típica leva bem menos de 10 ms.
- Regras de validação — JS fornecidos pelo usuário para validação de formulários são processados em microssegundos.
- Transformações de dados —
array.map(transform)em centenas de itens são concluídas em dezenas de milissegundos. - Executores em sandbox — scripts curtos fornecidos pelo usuário, com um limite de 1 segundo imposto pelo host, ainda têm bastante margem.
Quando o teto importa
- Loops internos com cálculos intensivos — processamento de imagens, reduções de arrays grandes, simulações. Leve os cálculos para uma biblioteca nativa PHP e exponha-a como uma função do host.
- Animação em tempo real — qualquer coisa que precise manter 60 fps. Esse JS deve ficar no cliente (navegador, aplicativo nativo), não no mecanismo incorporado.
- Reimplementações em JS de algoritmos com alto custo computacional — criptografia, compactação, análise sintática. PHP oferece equivalentes nativos 1000× mais rápidos; use-os.
O modelo mental adequado: Phasis é uma camada de integração para lógica no estilo de JS incorporada a aplicações no estilo de PHP. O host PHP cuida dos cálculos; a camada JS cuida da configuração, dos desvios condicionais e das pequenas transformações.
O que torna o mecanismo rápido (em comparação com um interpretador de árvore ingênuo)
- VM de bytecode — a maior parte das chamadas mais frequentes passa pelo switch de opcodes de
VM::execute, não pela AST. - Pilha de chamadas personalizada — cada chamada JS mantém o estado dentro de
VM::executee salva o quadro do chamador em um pool; não cria um novo quadro PHP a cada chamada JS. Consulte VM de bytecode § Pilha de chamadas personalizada. - Caches inline — um cache monomórfico por PC faz com que leituras de métodos de protótipo em instâncias (o padrão
c.method()) ignorem a busca na cadeia de protótipos. - Geradores com instantâneo do quadro — corpos simples de geradores suspendem a execução capturando o estado de distribuição em um objeto na heap, em vez de atravessar uma Fiber PHP.
- Transpiler JsToPhp — corpos com muitos cálculos numéricos ou uso intenso de variáveis locais (incluindo leituras de
obj.method(),new Ctor()ethis.member) são compilados para closures PHP, que o JIT de rastreamento de PHP 8.5 compila para código de máquina nativo. - Cache JSON.stringify —
JSON.stringify(o)armazena o resultado no objeto de entrada, com invalidação transitiva por versão, acelerando o padrão de clonagem profundaJSON.parse(JSON.stringify(o)).
Medir suas próprias cargas de trabalho
php bench/run.php
Isso executa o conjunto de microbenchmarks incluído em bench/microbench.js — um punhado de cargas de trabalho sintéticas (aritmética em loops, funções recursivas, criação de objetos, chamadas a métodos de protótipo, inclusão em arrays, ida e volta de JSON etc.) — e exibe o tempo de execução mediano de cada teste. É útil como verificação básica; os números atuais são registrados em BENCH.md a cada execução de benchmark na CI.
Para executar o seu próprio código, substitua por qualquer script:
php -d xdebug.mode=off bench/run.php path/to/your-script.js
Para criar perfis no nível de PHP, o profiler de Xdebug funciona exatamente com Phasis como com qualquer outra aplicação PHP. Execute um script com xdebug.mode=profile e examine a saída do cachegrind para ver quais classes de Phasis\ dominam.
Roteiro de desenvolvimento
O que reduziria ainda mais a diferença de V8, em ordem aproximada de facilidade de implementação:
- Caches inline polimórficos — permitir que um único ponto de chamada
LOAD_MEMBERmantenha de 2 a 4 formas em cache antes de recorrer à distribuição lenta. Ajuda o código que processa receptores heterogêneos. yield*e try-with-yield no caminho de geradores com instantâneo do quadro — essas estruturas atualmente usam o interpretador de árvore baseado em Fiber. Cada uma exige tratamento explícito por máquina de estados (encaminhamento do iterador delegado, pilha de handlers preservada durante a suspensão) antes de poder usar instantâneos com segurança.- Geradores assíncronos e
awaitem instantâneos — a especificação combina a suspensão de geradores com a fila de microtarefas. Quando o corpo de bytecode der suporte tanto ayieldquanto aawait, os geradores assíncronos também poderão usar instantâneos. - Inferência de tipos de parâmetros em JsToPhp — os parâmetros de função usam o tipo numérico por padrão, então
function (obj) { obj.method() }faz o compilador desistir. Inferir parâmetros como objetos com base no uso deles no corpo permitiria chamadas a métodos em argumentos de função, sem exigir que o receptor fosse construído localmente comnew. - Geração direta de opcodes para opcache — ignorar a etapa
eval()de JsToPhp e gerar os opcodes diretamente no formato do opcache. É o item mais trabalhoso; os demais podem ser implementados na arquitetura atual.
Nada disso é necessário para as cargas de trabalho de incorporação que Phasis visa.