PON-BEAM: um paradigma orientado a notificações dentro da máquina virtual do Erlang
1.
A pergunta que move este projeto Todo programador Elixir ou Erlang já ouviu o mantra: "a BEAM é uma máquina virtual altamente concorrente, com milhões de processos leves, trocas de contexto em microsegundos e escalabilidade quase linear".
E isso é verdade — para o modelo de programação.
Mas quando você abre o código em C da ERTS (Erlang Run-Time System) e olha como a máquina funciona por dentro, encontra uma surpresa: a BEAM é um motor híbrido.
Ela tem notificações para algumas coisas, mas ainda puxa (polling) e faz scans lineares em vários dos subsistemas mais críticos: Subsistema Mecanismo no BEAM stock (OTP 30) Custo seletivo Scan linear da mailbox (cada cláusula contra cada mensagem) $O(N \times M)$ Timers Timer wheel com ticks periódicos de polling O(1) por tick, mas a CPU nunca dorme Scheduler SMP Run-queue pollada / busy-spin quando ocioso 5–30% de um core desperdiçado Coletor de lixo Varredura de heap por raízes $O(\text{heap total})$ ETS Lookups com lock + busca contention na chave quente Esse é exatamente o tipo de problema que o Paradigma Orientado a Notificações (PON / NOP), criado pelo professor Dr.
Jean Marcelo Simão (UTFPR, 2005–2009), se propõe a eliminar: redundância temporal (reavaliações desnecessárias) e redundância estrutural (código de busca repetido).
O PON-BEAM (repositório: matheuscamarques/pon_beam) é uma re-arquitetura completa da BEAM onde todo subsistema interno vira uma entidade reativa PON: mensagens empurram notificações, em vez de a máquina puxar estados.
A tese central: Nenhum trabalho anterior aplicou o PON como princípio fundacional do motor da VM.
A literatura NOP implementa o paradigma em cima de plataformas existentes (C++, Java, FPGA, Erlang).
O PON-BEAM propõe o PON como o próprio design da máquina.
Este artigo explica, um a um, cada componente PON que foi integrado à VMBEAM — o que ele substitui, como foi implementado em C na ERTS, e quais ganhos empíricos o harness de benchmarks mediu.
2.
O paradigma PON em 5 minutos Antes de falar de cada componente, os conceitos básicos do PON: Entidade: qualquer elemento da aplicação (um processo Erlang, um scheduler, um objeto de heap) tratado como reativo e desacoplado.
Uma entidade contém dois tipos de elementos: FAD (Facts / dados): o estado da entidade.
FBE (Fundamental Behavior Element / procedimentos): o comportamento.
Premise: uma condição mínima sobre os fatos de uma entidade.
Ela só é avaliada quando notificada — e não periodicamente.
Ex.: "uma mensagem chegou na mailbox".
Condition: uma conjunção de Premises.
Quando todas as Premises de uma Condition estão satisfeitas, a Condition notifica uma Instigação.
Instigation: dispara a execução de um método (FBE) quando sua condição causal/temporal se torna verdadeira.
No PON-BEAM, os timers são Instigações temporais.
Notificação: o único mecanismo de colaboração entre entidades. É ponto-a-ponto (quem muda sabe quem deve ser avisado), reativa (só dispara quando o fato muda) e sem polling.
A inversão de controle é radical: no modelo imperativo clássico a entidade pergunta "o estado mudou?".
No PON, o estado muda e avisa quem o observa.
3.
A fundação: um overlay compilável sobre o OTP Antes de qualquer subsistema, a Fase 0 estabeleceu como o PON-BEAM existe junto do OTP stock.
As regras de ouro do projeto: Nunca tocar no baseline.
O OTP original vive imutável na branch .
Toda modificação C fica dentro de .
O código original permanece intacto — o PON-BEAM é um overlay compilável, não um fork divergente.
Um artefato por fase: um , um benchmark diferencial e uma telemetria.
Isso garante 100% de compatibilidade retroativa: o formato , a ABI de NIFs e o protocolo de distribuição não mudam.
Um mesmo pool de código compila (stock) ou (PON) dependendo do flag .
A telemetria da prova: Para medir que o comportamento reativo realmente aconteceu (e não só intuir), cada componente PON escreve contadores thread-local por scheduler: Os contadores são expostos ao Erlang via e cada benchmark os lê para validar o mecanismo (ex.: nº de confirma que o receive pulou o scan).
4.
Componente #1 — PON-Receive (Fase 1): Premises na mailbox O problema: O seletivo do Erlang é a fundação da concorrência da BEAM, mas o intérprete do OTP faz o scan: quando você tem e a mailbox tem N mensagens não relacionadas, o intérprete tenta, em ordem de chegada, cada uma das M cláusulas contra cada uma das N mensagens até achar um match.
Um com 10.000 mensagens pendentes custa 4.500µs; com 100.000, 82.000µs.
A solução: Premises Uma Premise PON-BEAM é um slot de pattern match compilado registrado pelo processo no momento do .
Quando uma mensagem compatível chega na mailbox, ela notifica a Premise — em vez de a mailbox ser escaneada mais tarde.
O fluxo tem três momentos:
1.
Classificação rápida por tag de tipo.
Na chegada da mensagem, extrai o 1º elemento da tupla e usa os 8 bits baixos dele para indexar um dos 256 buckets (), mantendo um contador de mensagens por bucket.
Isso isola, em $O(1)$, o subconjunto de mensagens potencialmente relevantes:
2.
Notificação da Premise.
Cada Premise possui um especializado (gerado pelo compilador) ou o fallback .
Se casa, a Premise guarda o termo, a mensagem e uma sequência de chegada global monotônica (), necessária para preservar a semântica do receive seletivo multi-cláusula (a mensagem mais antiga casa primeiro).
3.
Advance O(1) — o pulo do gato.
O hook central é no enqueue (lado do envio): cada mensagem recebe um , que é o endereço do ponteiro da fila que aponta para ela ( ou ): Depois, no do interpretador, reposiciona o save pointer da fila principal diretamente para o endereço da mensagem casada — sem caminhar a lista, sem pattern matching por mensagem: A validação é um gate de segurança: se a mensagem já foi consumida por outra via ou o gambar foi alcançado pelo scan normal, a Premise é marcada obsoleta e o scan linear segue como fallback (correto, apenas mais lento).
Os casos de caminhos não instrumentados (buffers multi-mensagem, flush de , concatenação de cadeias) também registram — todos os caminhos de entrada da mensagem estão cobertos.
Resultado medido N (mensagens na mailbox) Stock PON-BEAM Speedup 100 45.2 µs 8.5 µs 5.3× 1.000 320 µs 9.2 µs 34.8× 10.000 4.500 µs 10 µs 445× 100.000 82.000 µs 12 µs 6.665× A latência do receive PON no plota ~15µs em todo o range de 100 a 50.000 mensagens — o gap cresce monotonicamente com N, confirmando uma inversão assintótica ($O(N \times M) \to O(1)$), e não um ganho de constante.
5.
Componente #2 — PON-Timer (Fase 2): Instigações com O problema: o timer wheel nunca dorme A BEAM usa um timer wheel com resolução de 1ms.
Mesmo com zero timers ativos, o sistema de timers executa ticks periódicos para varrer o wheel em busca de expirações — consumindo ~3% de um core sem fazer nada.
Com 50.000 timers registrados, o custo de checagem chega a 50.000.000 checagens/seg com ~15% de CPU.
A solução: Instigações temporais com o kernel Uma Instigation PON é um evento futuro que, quando satisfeito, notifica um processo-alvo.
No PON-BEAM a expiração de um timer fica a cargo do kernel Linux: cada timer vira um monitorado por .
A VM só acorda no único próximo evento vencido — e nunca varre o wheel.
A entidade guarda o fd, o alvo () e a mensagem a entregar na expiração: faz um não-bloqueante e dispara cada Instigação vencida — drenando as expirações em lote, sem custo proporcional ao número total de timers.
Resultado medido Cenário Stock PON-BEAM CPU ociosa com 0 timers ~3% de 1 core 0.0% 10 timers (1s) ~3.1% 0.001% 50.000 timers (1s) ~15% CPU / 50M checagens/s ~0.1% / 5 checagens/s O custo total de timers registrados cai de "varredura do wheel" para "quase nada" — $10.000.000\times$ em checagens por segundo.
6.
Componente #3 — PON-Spawn (Fase 3): notificação no agendamento O problema Cada percorre o caminho de criação de processo (alocar PCB, inscrever no process table, inserir na run queue) e a latência médi