From eeca5e653d62ba6e9dc47c013ea40782475ec2a2 Mon Sep 17 00:00:00 2001 From: samsonium Date: Thu, 29 Oct 2026 03:25:48 +0300 Subject: [PATCH] feat: add benchmar Make target --- .gitignore | 3 ++- Makefile | 12 +++++++++- README.ru.md | 17 ++++++++++++++ benchmarks/loop.fzs | 8 +++++++ scripts/benchmark.py | 53 ++++++++++++++++++++++++++++++++++++++++++++ src/vm/fzl_vm.c | 2 +- 6 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 benchmarks/loop.fzs create mode 100644 scripts/benchmark.py diff --git a/.gitignore b/.gitignore index 3cc2666..122a8bd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,9 @@ # Dirs !/docs !/exl +!/benchmarks !/include +!/scripts !/src !/tests @@ -15,4 +17,3 @@ !/Makefile !/README.md !/README.ru.md - diff --git a/Makefile b/Makefile index 4e4c8b9..ae2f996 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,9 @@ -.PHONY: build debug-c debug-vm test +.PHONY: build debug-c debug-vm test bench + +BENCH_FILE ?= benchmarks/loop.fzs +RUNS ?= 7 +WARMUP ?= 2 +MAX_ITER ?= 2000000 build: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug @@ -17,3 +22,8 @@ debug-vm: build test: build ctest --output-on-failure --test-dir build +# Compile the benchmark once, then measure only VM process execution. +bench: build-release + ./build/fzc -o build/benchmark.fzb $(BENCH_FILE) + python3 scripts/benchmark.py --runs $(RUNS) --warmup $(WARMUP) \ + --max-iter $(MAX_ITER) --vm ./build/fz --program build/benchmark.fzb diff --git a/README.ru.md b/README.ru.md index d1cc899..3ffbf85 100644 --- a/README.ru.md +++ b/README.ru.md @@ -51,3 +51,20 @@ $ make debug-c FILE=exl/hw.fzs Для проверки новых фич, нужно использовать make-таргет `test`, а тесты писать в `CMakeLists.txt`. +### Бенчмаркинг + +`make bench` собирает Release-версию, компилирует `benchmarks/loop.fzs` один раз и +измеряет время запуска программы в виртуальной машине. По умолчанию выполняются +2 прогрева и 7 замеров; выводятся медиана, среднее, минимум и максимум. Компиляция +исходника в измерения не входит. + +Параметры можно переопределить: + +```shell +make bench RUNS=15 WARMUP=3 +make bench BENCH_FILE=path/to/program.fzs MAX_ITER=2000000 +``` + +Измеряется полное время запуска процесса `fz` (включая загрузку программы), а не +только цикл интерпретации. Для сопоставимых результатов запускайте бенчмарк на +одной машине и не меняйте нагрузку между прогонами. diff --git a/benchmarks/loop.fzs b/benchmarks/loop.fzs new file mode 100644 index 0000000..9a76b7a --- /dev/null +++ b/benchmarks/loop.fzs @@ -0,0 +1,8 @@ +// Integer loop workload for measuring VM execution without I/O. +uint i = 0; +uint sum = 0; + +for (i = 0; i < 1000000; i = i + 1) +{ + sum = sum + i; +} diff --git a/scripts/benchmark.py b/scripts/benchmark.py new file mode 100644 index 0000000..00fad43 --- /dev/null +++ b/scripts/benchmark.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +"""Run an FZL VM benchmark and report wall-clock timing statistics.""" + +import argparse +import statistics +import subprocess +import sys +import time + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--runs", type=int, default=7, help="measured runs") + parser.add_argument("--warmup", type=int, default=2, help="warm-up runs") + parser.add_argument("--max-iter", type=int, default=2_000_000) + parser.add_argument("--vm", required=True, help="path to the fz executable") + parser.add_argument("--program", required=True, help="compiled .fzb program") + args = parser.parse_args() + + if args.runs < 1 or args.warmup < 0 or args.max_iter < 1: + parser.error("runs/max-iter must be positive and warmup must be non-negative") + + command = [args.vm, "--max-iter", str(args.max_iter), args.program] + + for _ in range(args.warmup): + result = subprocess.run(command, capture_output=True, text=True) + if result.returncode != 0: + sys.stderr.write(result.stdout) + sys.stderr.write(result.stderr) + raise SystemExit(result.returncode) + + measurements = [] + for _ in range(args.runs): + start = time.perf_counter() + result = subprocess.run( + command, capture_output=True, text=True + ) + elapsed = time.perf_counter() - start + if result.returncode != 0: + sys.stderr.write(result.stdout) + sys.stderr.write(result.stderr) + raise SystemExit(result.returncode) + measurements.append(elapsed) + + print(f"Runs: {args.runs} (warm-up: {args.warmup})") + print(f"Median: {statistics.median(measurements):.6f} s") + print(f"Mean: {statistics.mean(measurements):.6f} s") + print(f"Min: {min(measurements):.6f} s") + print(f"Max: {max(measurements):.6f} s") + + +if __name__ == "__main__": + main() diff --git a/src/vm/fzl_vm.c b/src/vm/fzl_vm.c index 3842fbb..5bcf0d5 100644 --- a/src/vm/fzl_vm.c +++ b/src/vm/fzl_vm.c @@ -232,7 +232,7 @@ int fzl_vm_run(fzl_vm_t *vm) while (vm->ip < vm->ops_count) { fzl_opcode_t opcode = vm->ops[vm->ip]; - size_t op_group = (opcode >> 16); + size_t op_group = (opcode >> 16) & 0xFF; size_t op_fnnum = (opcode & 0xFF); if (master_tbl[op_group] == NULL || master_tbl[op_group][op_fnnum] == NULL)