Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ lugar errado foi tocado; não afrouxe o teste:

Benchmarks em `benchmarks/`: `pwsh -NoProfile -File interleaved_compare.ps1`
(dois binários intercalados — a única comparação que vale), máquina ociosa,
delta só na mesma sessão; `compare_examples.ps1` captura a saída dos exemplos
delta só na mesma sessão; em Linux/macOS os mesmos runners existem em bash
(`interleaved_compare.sh`, `run_benchmarks.sh`, `cross_runtime/run_cross_runtime.sh`); `compare_examples.ps1` captura a saída dos exemplos
antes/depois ao mexer no runtime. CI (`.github/workflows/network-deadlines.yml`):
testes Go em ubuntu+windows, `-race` em `internal/vm`, o runner, cross-build
com `CGO_ENABLED=0`.
Expand Down
11 changes: 11 additions & 0 deletions benchmarks/RESULTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1495,6 +1495,17 @@ powershell -File benchmarks/cross_runtime/run_cross_runtime.ps1 -Noxy <exe> `
-NoxyBaseline <exe-antigo> -BaselineLabel v060
```

Em Linux/macOS, os mesmos três runners em bash (mesmo protocolo, mesma
saída; `compare_examples` continua só em PowerShell):

```bash
benchmarks/run_benchmarks.sh --binary <bin> --label <label>
benchmarks/interleaved_compare.sh --baseline <bin> --candidate <bin> \
--baseline-label v060 --candidate-label v0141 --runs 9
benchmarks/cross_runtime/run_cross_runtime.sh --noxy <bin> \
--noxy-baseline <bin-antigo> --baseline-label v060
```

Os binários têm de estar em **disco local**: este repo vive em OneDrive e medir
de lá infla os tempos em ~2x (filtro de sync + antivírus no read). Benches que
não rodam nos dois binários são pulados e listados no relatório, não medidos.
18 changes: 12 additions & 6 deletions benchmarks/bench_bst_owned.nx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
// `ref TreeNode` nos filhos e nao passa por esse caminho.
struct TreeNode
valor: int
esquerda: TreeNode
direita: TreeNode
esquerda: TreeNode? // posse; null onde a arvore termina (spec §2.4, §3)
direita: TreeNode?
end
func insert(node: ref TreeNode, v: int) -> void
func insert(node: ref (TreeNode?), v: int) -> void
if *node == null then
*node = TreeNode(v, null, null)
return
Expand All @@ -19,14 +19,20 @@ func insert(node: ref TreeNode, v: int) -> void
insert(ref node.direita, v)
end
end
func count(node: ref TreeNode) -> int
func count(node: ref (TreeNode?)) -> int
if *node == null then
return 0
end
return 1 + count(ref node.esquerda) + count(ref node.direita)
// O narrowing de `*node` nao sobrevive a uma chamada cuja raiz e `ref`
// (spec §3, "Self-Reference"): um emprestimo por chamada, testado de novo.
let e: int = count(ref node.esquerda)
if *node == null then
return 0
end
return 1 + e + count(ref node.direita)
end
func main() -> void
let raiz: TreeNode = null
let raiz: TreeNode? = null
let seed: int = 12345
let i: int = 0
while i < 20000 do
Expand Down
9 changes: 5 additions & 4 deletions benchmarks/bench_bst_ref.nx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
// promovida a heap, e o acesso nao caminha o caminho do emprestimo (issue #93).
struct TreeNode
valor: int
esquerda: ref TreeNode
direita: ref TreeNode
esquerda: ref TreeNode? // compartilhado; null onde a arvore termina (spec §2.3 R8)
direita: ref TreeNode?
end
func insert(node: ref TreeNode, v: int) -> void
if v < node.valor then
Expand All @@ -22,11 +22,12 @@ func insert(node: ref TreeNode, v: int) -> void
end
end
end
func count(node: ref TreeNode) -> int
func count(node: ref TreeNode?) -> int
if node == null then
return 0
end
return 1 + count(node.esquerda) + count(node.direita)
let n: ref TreeNode = node // spec §2.4: o narrowing nao sobrevive a chamada; vincula uma vez
return 1 + count(n.esquerda) + count(n.direita)
end
func main() -> void
let raiz: TreeNode = TreeNode(500000, null, null)
Expand Down
1 change: 1 addition & 0 deletions benchmarks/bench_hash31_bytes.nx
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ while i < n do
end
let t1: int = time_now_ms()
print("bytes=" + to_str(n) + " hash31=" + to_str(h) + " ms=" + to_str(t1 - t0))
print(f"CHECKSUM:{h}")
14 changes: 13 additions & 1 deletion benchmarks/cross_runtime/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,20 @@ diferentes do VM, mais um piso de processo.
-NoxyBaseline C:\local\noxy_antigo.exe -BaselineLabel v060
```

Em Linux/macOS, o mesmo protocolo em bash (mesma saída, mesmas colunas):

```bash
./run_cross_runtime.sh --noxy /caminho/local/noxy

# comparando duas versões do próprio Noxy na mesma janela intercalada
./run_cross_runtime.sh --noxy /local/noxy_novo \
--noxy-baseline /local/noxy_antigo --baseline-label v060
```

Saída em `results/cross_runtime.md`. `lua` e `go` são opcionais — se não
estiverem no `PATH`, viram `-` na tabela em vez de erro.
estiverem no `PATH`, viram `-` na tabela em vez de erro. Os dois runners
gravam números com ponto decimal fixo (`LC_NUMERIC=C` no bash), independente
do locale da máquina.

`-NoxyBaseline` adiciona uma segunda versão do Noxy como coluna extra,
intercalada junto com os outros runtimes. É a **única forma válida de comparar
Expand Down
83 changes: 83 additions & 0 deletions benchmarks/cross_runtime/results/2026-09-05-v0250-ubuntu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Cross-runtime: Noxy x CPython x Go — v0.25.0, Ubuntu 26.04 (2026-09-05)

- noxy: build local de e4496f8 (Noxy v0.25.0), Go 1.27.1
- python: Python 3.14.4
- go: go version go1.27.1 linux/amd64
- lua: ausente
- Sistema: Ubuntu 26.04.1 LTS, Intel Core i7-1165G7 (8 threads), instalacao limpa, load ~0.5
- python: pacote do Ubuntu (gcc, computed gotos, JIT compilado mas desligado, GIL ativo)
- Runner: `run_cross_runtime.sh` (port bash, mesmo protocolo do `.ps1`)
- Data: 2026-09-05T17:49
- Runs por bench: 9, intercalados; **minimo** reportado

## Tempo total (ms)

| bench | noxy | python | go |
|---|---|---|---|
| `bubblesort` | 88.8 | 57.1 | - |
| `fib` | 128.4 | 79.8 | 5.6 |
| `loop_arith` | 180.8 | 149.1 | 10.8 |
| `mandelbrot` | 122.8 | 68.2 | - |
| `map_churn` | 126.0 | 61.7 | - |
| `startup` | 3.0 | 10.1 | 1.8 |
| `string_ops` | 81.3 | 35.6 | - |

## Tempo de execucao, descontado o piso de `startup` (ms)

| bench | noxy | python | go |
|---|---|---|---|
| `bubblesort` | 85.8 | 47.0 | - |
| `fib` | 125.4 | 69.7 | ~0 |
| `loop_arith` | 177.8 | 139.0 | 9.0 |
| `mandelbrot` | 119.8 | 58.1 | - |
| `map_churn` | 123.0 | 51.6 | - |
| `string_ops` | 78.3 | 25.5 | - |

`~0` = o trabalho cabe dentro do ruido do piso de processo do runtime.

## Razoes sobre o tempo liquido (noxy / outro)

| bench | / python | / go |
|---|---|---|
| `bubblesort` | 1.83x | - |
| `fib` | 1.80x | - |
| `loop_arith` | 1.28x | 19.76x |
| `mandelbrot` | 2.06x | - |
| `map_churn` | 2.38x | - |
| `string_ops` | 3.07x | - |

## Checksums

- bubblesort: CHECKSUM:376520193
- fib: CHECKSUM:832040
- loop_arith: CHECKSUM:135
- mandelbrot: CHECKSUM:692665
- map_churn: CHECKSUM:764868
- startup: CHECKSUM:0
- string_ops: CHECKSUM:2200001

## Segunda rodada (mesma sessao, confirmacao)

| bench | noxy | python | go |
|---|---|---|---|
| `bubblesort` | 90.9 | 57.0 | - |
| `fib` | 130.4 | 80.2 | 5.6 |
| `loop_arith` | 185.3 | 150.7 | 10.6 |
| `mandelbrot` | 125.0 | 68.4 | - |
| `map_churn` | 126.8 | 62.7 | - |
| `startup` | 3.0 | 10.3 | 1.8 |
| `string_ops` | 82.7 | 37.2 | - |

## Leitura

- Primeira medicao do cross-runtime em Linux. As razoes noxy/python sao
piores que as publicadas no Windows (`fib` 1,16x -> 1,80x, `loop_arith`
1,06x -> 1,28x, `string_ops` 2,17x -> 3,07x). O que mudou de lado foi o
CPython: o build gcc + PGO/LTO do pacote Debian/Ubuntu e consistentemente
mais rapido que o build MSVC do Windows, enquanto o Go rende parecido nas
duas plataformas. Os tres binarios noxy disponiveis (build do HEAD, `./noxy`
do repo, `~/go/bin/noxy` via `go install`) deram o mesmo `fib`, entao nao e
regressao de build.
- Piso de processo: noxy 3 ms, python 10 ms — o Noxy sobe 3x mais rapido.
- Maquina diferente da dos resultados anteriores: os ms absolutos nao se
comparam com as rodadas do Windows, so as razoes dentro desta janela.
186 changes: 186 additions & 0 deletions benchmarks/cross_runtime/run_cross_runtime.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
#!/usr/bin/env bash
# Port bash de run_cross_runtime.ps1 — mesmo protocolo, mesma saida.
#
# Compara o VM do Noxy com outros runtimes na mesma carga. Noxy e CPython
# cobrem os sete benches; Lua 5.4 e Go nativo cobrem tres (startup,
# loop_arith, fib) como calibracao. Runtime ausente vira "-" na tabela.
#
# Metodologia (ver README.md): intercalado, MINIMO de N amostras, fontes
# copiados para disco local, versao antiga medida na mesma janela
# (--noxy-baseline). Cada implementacao imprime a mesma linha CHECKSUM: e o
# script aborta se divergirem — nao seria a mesma carga.
#
# Uso:
# ./run_cross_runtime.sh --noxy <bin> [--noxy-baseline <bin> --baseline-label v060]
# [--python python3] [--lua lua] [--runs 9]
set -euo pipefail
export LC_NUMERIC=C

HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
NOXY="$HERE/../../noxy"
NOXY_BASE=""
BASE_LABEL="noxy_base"
PYTHON="python3"
LUA="lua"
RUNS=9

while [ $# -gt 0 ]; do
case "$1" in
--noxy) NOXY="$2"; shift 2 ;;
--noxy-baseline) NOXY_BASE="$2"; shift 2 ;;
--baseline-label) BASE_LABEL="$2"; shift 2 ;;
--python) PYTHON="$2"; shift 2 ;;
--lua) LUA="$2"; shift 2 ;;
--runs) RUNS="$2"; shift 2 ;;
-h|--help) sed -n '2,15p' "$0"; exit 0 ;;
*) echo "argumento desconhecido: $1" >&2; exit 2 ;;
esac
done

have() { command -v "$1" >/dev/null 2>&1; }
[ -x "$NOXY" ] || have "$NOXY" || { echo "noxy nao encontrado: $NOXY" >&2; exit 2; }
have "$PYTHON" || { echo "python nao encontrado: $PYTHON" >&2; exit 2; }
HAS_LUA=0; have "$LUA" && HAS_LUA=1
HAS_GO=0; have go && HAS_GO=1

WORK="$(mktemp -d "${TMPDIR:-/tmp}/noxy_cross_XXXXXXXX")"
trap 'rm -rf "$WORK"' EXIT
cp "$HERE"/*.nx "$HERE"/*.py "$WORK"/
cp "$HERE"/*.lua "$WORK"/ 2>/dev/null || true

# Go compila antes de medir: o benchmark e do binario, nao do compilador.
if [ "$HAS_GO" = 1 ]; then
for d in "$HERE"/go/*/; do
b="$(basename "$d")"
go build -o "$WORK/go_$b" "$d/main.go" || { echo "go build falhou para $b" >&2; exit 1; }
done
fi

# Ordem fixa: define a ordem das colunas e a ordem do intercalamento.
ORDER=(noxy)
[ -n "$NOXY_BASE" ] && ORDER+=("$BASE_LABEL")
ORDER+=(python lua go)

now_ms() { awk -v t="$EPOCHREALTIME" 'BEGIN{printf "%.3f", t*1000}'; }

run_one() { # $1=runtime $2=bench
case "$1" in
noxy) "$NOXY" "$WORK/$2.nx" ;;
"$BASE_LABEL") "$NOXY_BASE" "$WORK/$2.nx" ;;
python) "$PYTHON" "$WORK/$2.py" ;;
lua) "$LUA" "$WORK/$2.lua" ;;
go) "$WORK/go_$2" ;;
esac
}

declare -A MIN CHK
BENCHES=()
for f in "$WORK"/*.nx; do BENCHES+=("$(basename "$f" .nx)"); done
IFS=$'\n' BENCHES=($(sort <<<"${BENCHES[*]}")); unset IFS

for b in "${BENCHES[@]}"; do
# Monta so os runtimes que existem para este bench.
rts=(noxy)
[ -n "$NOXY_BASE" ] && rts+=("$BASE_LABEL")
[ -f "$WORK/$b.py" ] && rts+=(python)
[ "$HAS_LUA" = 1 ] && [ -f "$WORK/$b.lua" ] && rts+=(lua)
[ -x "$WORK/go_$b" ] && rts+=(go)

# Warmup (aquece o cache de arquivo) + equivalencia entre runtimes.
chk=""
for r in "${rts[@]}"; do
c="$(run_one "$r" "$b" 2>&1 | grep -m1 '^CHECKSUM:' || true)"
[ -n "$c" ] || { echo "$b/$r : sem linha CHECKSUM" >&2; exit 1; }
[ -n "$chk" ] || chk="$c"
[ "$c" = "$chk" ] || { echo "$b : checksum divergente ($r=$c, esperado $chk)" >&2; exit 1; }
done
CHK[$b]="$chk"

declare -A samples=()
for i in $(seq 1 "$RUNS"); do
for r in "${rts[@]}"; do
t0="$(now_ms)"; run_one "$r" "$b" >/dev/null 2>&1; t1="$(now_ms)"
samples[$r]+=" $(awk -v a="$t0" -v b="$t1" 'BEGIN{printf "%.1f", b-a}')"
done
done
shown=""
for r in "${rts[@]}"; do
m="$(tr ' ' '\n' <<<"${samples[$r]}" | sed '/^$/d' | sort -n | head -1)"
MIN[$b,$r]="$m"; shown+=" $r=${m}ms"
done
printf '%-12s%s\n' "$b" "$shown"
unset samples
done

# ---- relatorio ----
PRESENT=()
for r in "${ORDER[@]}"; do
for b in "${BENCHES[@]}"; do
if [ -n "${MIN[$b,$r]:-}" ]; then PRESENT+=("$r"); break; fi
done
done

cell() { [ -n "${1:-}" ] && echo "$1" || echo "-"; }
# Liquido: total menos o piso de processo do proprio runtime. Para runtimes
# rapidos o trabalho cabe no ruido do piso e a subtracao vai a zero ou fica
# negativa — reportamos "~0" em vez de fingir precisao.
net() { [ -n "${1:-}" ] && [ -n "${2:-}" ] || { echo "-"; return; }
awk -v v="$1" -v f="$2" 'BEGIN{n=v-f; if (n<=5) print "~0"; else printf "%.1f", n}'; }
ratio() { case "$1$2" in *~0*|*-*) echo "-"; return ;; esac
awk -v a="$1" -v b="$2" 'BEGIN{printf "%.2fx", a/b}'; }
header() { local s="| bench |"; for r in "$@"; do s+=" $r |"; done; echo "$s"
s="|---|"; for r in "$@"; do s+="---|"; done; echo "$s"; }

OUT="$HERE/results/cross_runtime.md"
mkdir -p "$HERE/results"
{
echo "# Cross-runtime: Noxy x CPython x Lua x Go"
echo
echo "- noxy: \`$NOXY\` ($("$NOXY" --version 2>&1 | tr -d '\n'))"
[ -n "$NOXY_BASE" ] && echo "- $BASE_LABEL: \`$NOXY_BASE\` ($("$NOXY_BASE" --version 2>&1 | tr -d '\n'))"
echo "- python: $("$PYTHON" --version 2>&1 | tr -d '\n')"
if [ "$HAS_LUA" = 1 ]; then echo "- lua: $("$LUA" -v 2>&1 | tr -d '\n')"; else echo "- lua: ausente"; fi
if [ "$HAS_GO" = 1 ]; then echo "- go: $(go version)"; else echo "- go: ausente"; fi
echo "- Sistema: $(uname -srm)"
echo "- Data: $(date +%Y-%m-%dT%H:%M:%S)"
echo "- Runs por bench: $RUNS, intercalados; **minimo** reportado"
echo
echo "## Tempo total (ms)"
echo
header "${PRESENT[@]}"
for b in "${BENCHES[@]}"; do
s="| \`$b\` |"; for r in "${PRESENT[@]}"; do s+=" $(cell "${MIN[$b,$r]:-}") |"; done; echo "$s"
done
echo
echo "## Tempo de execucao, descontado o piso de \`startup\` (ms)"
echo
header "${PRESENT[@]}"
for b in "${BENCHES[@]}"; do
[ "$b" = startup ] && continue
s="| \`$b\` |"; for r in "${PRESENT[@]}"; do s+=" $(net "${MIN[$b,$r]:-}" "${MIN[startup,$r]:-}") |"; done; echo "$s"
done
echo
echo "\`~0\` = o trabalho cabe dentro do ruido do piso de processo do runtime."
# Razoes sobre o liquido: os ms absolutos dependem da carga da maquina na
# hora; a razao contra um runtime medido na MESMA janela, sim, se compara.
OTHERS=(); for r in "${PRESENT[@]}"; do [ "$r" != noxy ] && OTHERS+=("$r"); done
if [ "${#OTHERS[@]}" -gt 0 ]; then
echo
echo "## Razoes sobre o tempo liquido (noxy / outro)"
echo
labels=(); for r in "${OTHERS[@]}"; do labels+=("/ $r"); done
header "${labels[@]}"
for b in "${BENCHES[@]}"; do
[ "$b" = startup ] && continue
n="$(net "${MIN[$b,noxy]:-}" "${MIN[startup,noxy]:-}")"
s="| \`$b\` |"
for r in "${OTHERS[@]}"; do s+=" $(ratio "$n" "$(net "${MIN[$b,$r]:-}" "${MIN[startup,$r]:-}")") |"; done
echo "$s"
done
fi
echo
echo "## Checksums"
echo
for b in "${BENCHES[@]}"; do echo "- \`$b\`: ${CHK[$b]}"; done
} > "$OUT"
echo "wrote results/cross_runtime.md"
Loading