Pular para o conteúdo

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::execute e 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() e this.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 profunda JSON.parse(JSON.stringify(o)).

Medir suas próprias cargas de trabalho

Shell
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:

Shell
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_MEMBER mantenha 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 await em instantâneos — a especificação combina a suspensão de geradores com a fila de microtarefas. Quando o corpo de bytecode der suporte tanto a yield quanto a await, 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 com new.
  • 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.