From 6b49a7063107202349c61f05b3877a729a7954d9 Mon Sep 17 00:00:00 2001 From: "peng.li24" <734991033@qq.com> Date: Sat, 5 Sep 2026 06:50:31 +0800 Subject: [PATCH] =?UTF-8?q?benchmark:=20=E8=B7=A8=E8=AF=AD=E8=A8=80/?= =?UTF-8?q?=E5=90=8E=E7=AB=AF=E6=80=A7=E8=83=BD=E5=9F=BA=E5=87=86=EF=BC=88?= =?UTF-8?q?kvlang=20vs=20py/rs/c=20=C3=97=20shm/fs/redis=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - benchmark/run.py:同一算法 4 份逻辑等价实现({kv,py,rs,c}),kvlang 在三个 kvspace 后端各一列,原生/脚本作基线;规模阶梯冻结、版本化 results.csv。 - cases/:binary_trees / k_nucleotide / prime_sieve / fib / iops / nqueens / binary_search(每 case .kv/.py/.rs/.c 四份)。 - benchmark/README:用途与当前 v0.2.5 常数因子定位。 --- benchmark/README.md | 125 ++++++ benchmark/cases/binary_search/binary_search.c | 42 ++ .../cases/binary_search/binary_search.kv | 50 +++ .../cases/binary_search/binary_search.py | 33 ++ .../cases/binary_search/binary_search.rs | 38 ++ benchmark/cases/binary_trees/binary_trees.c | 38 ++ benchmark/cases/binary_trees/binary_trees.kv | 72 ++++ benchmark/cases/binary_trees/binary_trees.py | 31 ++ benchmark/cases/binary_trees/binary_trees.rs | 34 ++ benchmark/cases/fib/fib.c | 21 + benchmark/cases/fib/fib.kv | 26 ++ benchmark/cases/fib/fib.py | 15 + benchmark/cases/fib/fib.rs | 17 + benchmark/cases/hash_table/hash_table.c | 31 ++ benchmark/cases/hash_table/hash_table.kv | 36 ++ benchmark/cases/hash_table/hash_table.py | 19 + benchmark/cases/hash_table/hash_table.rs | 24 ++ benchmark/cases/iops/iops.c | 22 ++ benchmark/cases/iops/iops.kv | 23 ++ benchmark/cases/iops/iops.py | 18 + benchmark/cases/iops/iops.rs | 20 + benchmark/cases/k_nucleotide/k_nucleotide.c | 26 ++ benchmark/cases/k_nucleotide/k_nucleotide.kv | 39 ++ benchmark/cases/k_nucleotide/k_nucleotide.py | 15 + benchmark/cases/k_nucleotide/k_nucleotide.rs | 20 + benchmark/cases/matmul/matmul.c | 32 ++ benchmark/cases/matmul/matmul.kv | 65 ++++ benchmark/cases/matmul/matmul.py | 23 ++ benchmark/cases/matmul/matmul.rs | 29 ++ benchmark/cases/nqueens/nqueens.c | 29 ++ benchmark/cases/nqueens/nqueens.kv | 40 ++ benchmark/cases/nqueens/nqueens.py | 22 ++ benchmark/cases/nqueens/nqueens.rs | 25 ++ benchmark/cases/prime_sieve/prime_sieve.c | 33 ++ benchmark/cases/prime_sieve/prime_sieve.kv | 41 ++ benchmark/cases/prime_sieve/prime_sieve.py | 27 ++ benchmark/cases/prime_sieve/prime_sieve.rs | 32 ++ benchmark/cases/quicksort/quicksort.c | 44 +++ benchmark/cases/quicksort/quicksort.kv | 66 ++++ benchmark/cases/quicksort/quicksort.py | 32 ++ benchmark/cases/quicksort/quicksort.rs | 35 ++ benchmark/run.py | 362 ++++++++++++++++++ 42 files changed, 1772 insertions(+) create mode 100644 benchmark/README.md create mode 100644 benchmark/cases/binary_search/binary_search.c create mode 100644 benchmark/cases/binary_search/binary_search.kv create mode 100644 benchmark/cases/binary_search/binary_search.py create mode 100644 benchmark/cases/binary_search/binary_search.rs create mode 100644 benchmark/cases/binary_trees/binary_trees.c create mode 100644 benchmark/cases/binary_trees/binary_trees.kv create mode 100644 benchmark/cases/binary_trees/binary_trees.py create mode 100644 benchmark/cases/binary_trees/binary_trees.rs create mode 100644 benchmark/cases/fib/fib.c create mode 100644 benchmark/cases/fib/fib.kv create mode 100644 benchmark/cases/fib/fib.py create mode 100644 benchmark/cases/fib/fib.rs create mode 100644 benchmark/cases/hash_table/hash_table.c create mode 100644 benchmark/cases/hash_table/hash_table.kv create mode 100644 benchmark/cases/hash_table/hash_table.py create mode 100644 benchmark/cases/hash_table/hash_table.rs create mode 100644 benchmark/cases/iops/iops.c create mode 100644 benchmark/cases/iops/iops.kv create mode 100644 benchmark/cases/iops/iops.py create mode 100644 benchmark/cases/iops/iops.rs create mode 100644 benchmark/cases/k_nucleotide/k_nucleotide.c create mode 100644 benchmark/cases/k_nucleotide/k_nucleotide.kv create mode 100644 benchmark/cases/k_nucleotide/k_nucleotide.py create mode 100644 benchmark/cases/k_nucleotide/k_nucleotide.rs create mode 100644 benchmark/cases/matmul/matmul.c create mode 100644 benchmark/cases/matmul/matmul.kv create mode 100644 benchmark/cases/matmul/matmul.py create mode 100644 benchmark/cases/matmul/matmul.rs create mode 100644 benchmark/cases/nqueens/nqueens.c create mode 100644 benchmark/cases/nqueens/nqueens.kv create mode 100644 benchmark/cases/nqueens/nqueens.py create mode 100644 benchmark/cases/nqueens/nqueens.rs create mode 100644 benchmark/cases/prime_sieve/prime_sieve.c create mode 100644 benchmark/cases/prime_sieve/prime_sieve.kv create mode 100644 benchmark/cases/prime_sieve/prime_sieve.py create mode 100644 benchmark/cases/prime_sieve/prime_sieve.rs create mode 100644 benchmark/cases/quicksort/quicksort.c create mode 100644 benchmark/cases/quicksort/quicksort.kv create mode 100644 benchmark/cases/quicksort/quicksort.py create mode 100644 benchmark/cases/quicksort/quicksort.rs create mode 100644 benchmark/run.py diff --git a/benchmark/README.md b/benchmark/README.md new file mode 100644 index 00000000..b2099c50 --- /dev/null +++ b/benchmark/README.md @@ -0,0 +1,125 @@ +# kvlang benchmark + +kvlang 与 Python / Rust / C 的跨语言性能基准。每个 case 是同一算法的**四份逻辑等价实现**, +规模阶梯冻结、跨版本可比,用于量化 kvlang 解释执行模型(每操作一次 KV 往返)相对原生/脚本语言的 +常数因子,并**跨版本追踪 kvlang 自身的性能演进**。 + +当前 v0.2.5 的常数因子仍大,但这是**现阶段的现状、不是 kvlang 的固有属性**:后续版本会持续优化、 +逐步逼近 Python 的水平。这套版本化快照的核心用途,正是把每一版的耗时钉在同一规模点上, +**逐版本量出 kvlang 的加速曲线**——今天与 Python 的差距,是用来被后续版本收窄的基线。 + +kvlang 是被测对象,**分别在三个 kvspace 后端上各跑一遍,占三列**;Python/Rust/C 与后端无关, +作原生/脚本基线各一列。三后端量的是「同一份 kvlang 程序在不同存储介质下每操作往返的真实代价」: + +| 列 | 后端 | DSN | 量什么 | +|----|------|-----|--------| +| `kvlang_shm` | kvspace-c 共享内存 | `shm://` | 纯内存态地板性能(无 I/O) | +| `kvlang_fs` | kvspace-durable 文件 | `fs://` | 每 KV 往返落文件系统的代价 | +| `kvlang_redis` | kvspace-durable redis | `redis://` | 每 KV 往返走 TCP 的代价 | + +每次采样前对应后端都会清空(shm 删文件 / fs 删目录 / redis flushall),杜绝残留污染。 + +> **冻结契约(可比性的前提)**:case 一经纳入,其四语言实现代码与**规模阶梯**即**永久冻结**, +> 后续任何 kvlang 版本迭代都**不得改动** `cases/**/*.{kv,py,rs,c}` 的逻辑,也不得改动 `run.py` +> 里该 case 的 `SWEEP` 规模点——唯有代码不变、规模点不变,`results/` 里跨时间同一 `(case,input)` +> 的耗时序列才真正可比。要压新维度就**加新 case**、要加规模点只**往阶梯尾部追加**(勿动已有点), +> 绝不改旧值。(修 bug 导致输出变化视同新基线,须在提交信息里显式声明并从该版本起断代对比。) + +## 输入规模阶梯(sweep) + +每个 case 不再只跑单一规模,而是沿一串规模点(`run.py` 的 `SWEEP`)依次放大,**逐点落一行** +(`input` 列记该点规模,如 `N=64`、`depth=6`、`rep=5`),从而看清耗时随输入的增长曲线。 +规模经两条通道注入同一份逻辑等价代码,**四语言在每个规模点仍逐字节一致**: + +- **kvlang**:源码里规模写作占位符 `__SCALE__`,`run.py` 按规模点文本替换后落临时文件再跑 + (kvlang 无「脚本读环境变量」的 builtin,`input` 只读 stdin,故走占位符)。 +- **Python / Rust / C**:源码从环境变量 `BENCH_SCALE` 读规模(`os.environ` / `std::env::var` / + `getenv`),`run.py` 每个规模点设一次环境变量、跑同一份未改写的源码(编译与规模无关)。 + +上限刻意放低,确保三后端全量约 **1 小时**内跑完(v0.2.5 每操作一次 KV 往返、常数因子仍大; +这是现阶段现状,后续版本会持续压低,规模点保持冻结才能逐版本量出加速)。 + +## case 集 + +八个经典算法各压一个语言层维度,另加两个 kvlang 架构地板参考。 +每个 case 规模写死、跨版本可比,选让 kvlang 单次 ~2–3s(shm)的规模。 + +| case | 维度 | 关注 | +|------|------|------| +| `nqueens` | 递归 + 整数位运算 + 分支 | 位掩码回溯,`occ ^ all` 求可用列、`0-avail` 取最低位 | +| `fib` | 调用 / 帧寻址深度 | naive 递归,key 长度随深度增长(对齐 #116) | +| `quicksort` | 数组访问 + 递归 | 显式栈迭代 Lomuto 分区,LCG 造数 | +| `binary_search` | 有序表折半 | int 键映射作数组,逐次二分求和 | +| `binary_trees` | 内存分配 + 指针/引用 | L/R 子结点映射建满树,遍历栈跟随指针计数 | +| `hash_table` | 哈希表增删查 | Knuth 乘法散列,插入 + 查找求和 | +| `matmul` | 浮点运算 + 循环优化 | 稠密方阵乘三重循环,float64 校验和 ×1e6 精确对齐 | +| `k_nucleotide` | 字符串 + 哈希表 | 逐字符 `ord` 入哈希表统计碱基频次 | +| `iops` | 最小寻址单元往返地板价 | 单 key 读-改-写 `a<-a+1`,per-op 延迟(对齐 #204,参考基线) | +| `prime_sieve` | 计算 / 控制流密集 | 嵌套 `while` + 取模,O(n²) 内层迭代(参考基线) | + +kvlang 的性能瓶颈是「PC/帧/局部全落 KV 树、每步一次往返」的架构本质(见 kvlang#194 #204 #116), +不是某个热点函数;`iops`/`prime_sieve` 单独隔离出这条地板价,其余八例是跨语言等价算法对照。 + +## 运行 + +```bash +python3 benchmark/run.py # 全部 case × 三后端,min of 3,追加 results.csv +python3 benchmark/run.py -k iops # 只跑名字含 iops 的 case +python3 benchmark/run.py --repeat 1 # 快跑(单次,不取 min) +python3 benchmark/run.py --backends shm,fs # 只跑部分 kvlang 后端 +python3 benchmark/run.py --redis redis://127.0.0.1:6379 # 换 redis 地址 +python3 benchmark/run.py --no-write # 只打印不落 csv +python3 benchmark/run.py --show # 打印 results.csv 历史后退出 +``` + +依赖:`/usr/bin/kvlang`(可用 `--kvlang-bin` 覆盖)、`python3`、`rustc`、`gcc`; +`redis` 列还需本机 redis 与 `redis-cli`(不可用时该列留空,其余照常)。 +shm 走临时文件、fs 走临时目录,均每次采样前清空。 + +## 计时约定 + +每份实现在功能输出之后打印一行 `__bench_ns: <整数纳秒>`,计时区间包住核心计算调用; +kvlang 那份再打印一行 `__bench_input: <规模>`(算法输入,与语言无关,只此一份被采纳,落 csv 的 `input` 列)。 +runner 用正则取这两行,其余 stdout 为功能输出,用于**校验四语言输出逐字节一致** +(`valid` 列)——不一致说明某实现逻辑跑偏,该行数据不可信。 + +- kvlang:`time·now` / `time·sub` / `time/duration·as_nanos` +- Python:`time.perf_counter_ns()` +- Rust:`std::time::Instant` +- C:`clock_gettime(CLOCK_MONOTONIC)` + +**原生编译优化档**(落 csv 的 `native_opt` 列):`gcc -O1` / `rustc -C opt-level=1`;python 解释执行无编译期优化。 +刻意**避开 -O2/-O3**:它们会把无外部副作用的循环闭式折叠/消除——`iops` 的 `a+=1` 在 `gcc -O2` 下 +2000 次循环塌成常量赋值(88ns,纯失真),`-O1` 则保留真实工作量(761ns)。这不是让基线"跑得慢", +而是让原生耗时**真实反映算法工作量**、随输入规模单调增长,从而与 kvlang 同口径可比。 + +## results/ 快照(版本化,每次运行一个文件) + +每次运行写一个独立快照 `results/results--.csv`,文件名带 +**当前 kvlang 版本**(`git describe --tags --abbrev=0`)与**运行时刻**,一目了然属于哪个版本何时跑的。 +`--show` 汇总 `results/` 下全部快照。列: + +| 列 | 含义 | +|----|------| +| `timestamp` | UTC ISO8601 | +| `version` | `git describe --tags --always --dirty`(含 commit/dirty,精确区分同 tag 内迭代) | +| `commit` | short HEAD | +| `cpu` | CPU 型号(取自 `/proc/cpuinfo`,跨机对比须同型号,硬件不同不可比) | +| `case` | case 名 | +| `input` | 算法输入 / 规模(如 `N=6`、`depth=6`、`base×5=325`),取自 kvlang 的 `__bench_input` | +| `samples` | 采样次数(取 min) | +| `kvlang_shm_ns` `kvlang_fs_ns` `kvlang_redis_ns` | kvlang 在三后端各自最优耗时(纳秒),缺失后端留空 | +| `python_ns` `rust_ns` `c_ns` | 原生/脚本基线最优耗时(纳秒),缺失实现留空 | +| `python_ver` `rust_ver` `c_ver` | 各基线的解释器/编译器版本(`3.12.7` / `1.97.1` / `13.3.0`),工具升级也会影响基线 | +| `valid` | 所有已跑列的功能输出是否逐字节一致 | + +**跨版本对比**:`--show` 汇总所有快照,过滤同一 `cpu` + `case`,按 `version` 排序看某一 `kvlang_*_ns` 列趋势。 +例如 #116 三维地址曾把 prime_sieve 从 33.37s 降到 27.32s——这类演进就沉淀在快照序列里。 +三列并排还能看清「后端 I/O 代价」占 kvlang 单操作开销的比重随版本如何变化;`*_ver` 列则保证基线可追溯。 + +## 加新 case + +1. 建 `cases//`,放 `.{kv,py,rs,c}` 四份逻辑等价实现。 +2. 规模写死在代码里(**勿随版本改动**,否则历史不可比),选让 kvlang 单次 ~2–3s(shm)的规模。 +3. 每份实现末尾打印 `__bench_ns:`,kvlang 那份再打印 `__bench_input: <规模>`;功能输出保持四语言逐字节一致。 +4. 缺某语言实现时该语言留空跳过,不影响其余(但 `valid` 需 ≥2 份可比)。 diff --git a/benchmark/cases/binary_search/binary_search.c b/benchmark/cases/binary_search/binary_search.c new file mode 100644 index 00000000..4944fa32 --- /dev/null +++ b/benchmark/cases/binary_search/binary_search.c @@ -0,0 +1,42 @@ +#include +#include +#include + +static long bsearch_idx(const long *arr, long n, long target) { + long lo = 0, hi = n - 1, idx = -1; + while (lo <= hi) { + long mid = (lo + hi) / 2; + long mv = arr[mid]; + if (mv == target) { + idx = mid; + lo = hi + 1; + } else if (mv < target) { + lo = mid + 1; + } else { + hi = mid - 1; + } + } + return idx; +} + +int main(void) { + long n = atol(getenv("BENCH_SCALE")); + long arr[n]; + for (long i = 0; i < n; i++) + arr[i] = i; + struct timespec t0, t1; + clock_gettime(CLOCK_MONOTONIC, &t0); + long sum = 0, found = 0; + for (long q = 0; q < n; q++) { + long idx = bsearch_idx(arr, n, q); + if (idx != -1) { + found++; + sum += idx; + } + } + clock_gettime(CLOCK_MONOTONIC, &t1); + long ns = (t1.tv_sec - t0.tv_sec) * 1000000000L + (t1.tv_nsec - t0.tv_nsec); + printf("bsearch: found = %ld sum = %ld\n", found, sum); + printf("__bench_ns: %ld\n", ns); + return 0; +} diff --git a/benchmark/cases/binary_search/binary_search.kv b/benchmark/cases/binary_search/binary_search.kv new file mode 100644 index 00000000..8c1c1730 --- /dev/null +++ b/benchmark/cases/binary_search/binary_search.kv @@ -0,0 +1,50 @@ +// binary search benchmark — 有序数组二分查找(数组访问 + 循环 + 整除) +// 建 arr[i]=i,对 0..N-1 逐个二分,累加命中下标;N 由 __SCALE__ 占位(勿改逻辑) +// 期望输出(N=32 时): +// bsearch: found = 32 sum = 496 +rwfunc bsearch(arr:[int64]·int64, n:int64, target:int64) -> (idx:int64) { + 0 -> lo + n - 1 -> hi + -1 -> idx + while (lo <= hi) { + lo + hi -> s + s ÷ 2 -> mid + kv·get(arr, mid) -> mv + if (mv == target) { + mid -> idx + hi + 1 -> lo + } else if (mv < target) { + mid + 1 -> lo + } else { + mid - 1 -> hi + } + } +} + +rwfunc test() -> () { + __SCALE__ -> n + arr:[int64]·int64 = {} + 0 -> i + while (i < n) { + i -> arr·*i + i + 1 -> i + } + t0 <- time·now() + 0 -> sum + 0 -> found + 0 -> q + while (q < n) { + bsearch(arr, n, q) -> idx + if (idx != -1) { + found + 1 -> found + sum + idx -> sum + } + q + 1 -> q + } + t1 <- time·now() + println("bsearch: found =", found, "sum =", sum) + delta <- time·sub(t1, t0) + ns <- time/duration·as_nanos(delta) + println("__bench_ns:", ns) + println("__bench_input: N=__SCALE__") +} diff --git a/benchmark/cases/binary_search/binary_search.py b/benchmark/cases/binary_search/binary_search.py new file mode 100644 index 00000000..be5e1475 --- /dev/null +++ b/benchmark/cases/binary_search/binary_search.py @@ -0,0 +1,33 @@ +import os +import time + + +def bsearch(arr, n, target): + lo, hi = 0, n - 1 + idx = -1 + while lo <= hi: + mid = (lo + hi) // 2 + mv = arr[mid] + if mv == target: + idx = mid + lo = hi + 1 + elif mv < target: + lo = mid + 1 + else: + hi = mid - 1 + return idx + + +n = int(os.environ["BENCH_SCALE"]) +arr = [i for i in range(n)] +t0 = time.perf_counter_ns() +total = 0 +found = 0 +for q in range(n): + idx = bsearch(arr, n, q) + if idx != -1: + found += 1 + total += idx +t1 = time.perf_counter_ns() +print("bsearch: found =", found, "sum =", total) +print("__bench_ns:", t1 - t0) diff --git a/benchmark/cases/binary_search/binary_search.rs b/benchmark/cases/binary_search/binary_search.rs new file mode 100644 index 00000000..589acee5 --- /dev/null +++ b/benchmark/cases/binary_search/binary_search.rs @@ -0,0 +1,38 @@ +use std::time::Instant; + +fn bsearch(arr: &[i64], n: i64, target: i64) -> i64 { + let mut lo = 0i64; + let mut hi = n - 1; + let mut idx = -1i64; + while lo <= hi { + let mid = (lo + hi) / 2; + let mv = arr[mid as usize]; + if mv == target { + idx = mid; + lo = hi + 1; + } else if mv < target { + lo = mid + 1; + } else { + hi = mid - 1; + } + } + idx +} + +fn main() { + let n: i64 = std::env::var("BENCH_SCALE").unwrap().parse().unwrap(); + let arr: Vec = (0..n).collect(); + let t0 = Instant::now(); + let mut sum = 0i64; + let mut found = 0i64; + for q in 0..n { + let idx = bsearch(&arr, n, q); + if idx != -1 { + found += 1; + sum += idx; + } + } + let ns = t0.elapsed().as_nanos(); + println!("bsearch: found = {} sum = {}", found, sum); + println!("__bench_ns: {}", ns); +} diff --git a/benchmark/cases/binary_trees/binary_trees.c b/benchmark/cases/binary_trees/binary_trees.c new file mode 100644 index 00000000..c30c2e00 --- /dev/null +++ b/benchmark/cases/binary_trees/binary_trees.c @@ -0,0 +1,38 @@ +#include +#include +#include + +typedef struct Node { + struct Node *l, *r; +} Node; + +Node *make(int d) { + Node *n = malloc(sizeof(Node)); + if (d == 0) { + n->l = NULL; + n->r = NULL; + } else { + n->l = make(d - 1); + n->r = make(d - 1); + } + return n; +} + +long check(Node *n) { + if (n->l == NULL) + return 1; + return 1 + check(n->l) + check(n->r); +} + +int main(void) { + int depth = atoi(getenv("BENCH_SCALE")); + struct timespec t0, t1; + clock_gettime(CLOCK_MONOTONIC, &t0); + Node *root = make(depth); + long count = check(root); + clock_gettime(CLOCK_MONOTONIC, &t1); + long ns = (t1.tv_sec - t0.tv_sec) * 1000000000L + (t1.tv_nsec - t0.tv_nsec); + printf("bintree: nodes = %ld\n", count); + printf("__bench_ns: %ld\n", ns); + return 0; +} diff --git a/benchmark/cases/binary_trees/binary_trees.kv b/benchmark/cases/binary_trees/binary_trees.kv new file mode 100644 index 00000000..ed8b013f --- /dev/null +++ b/benchmark/cases/binary_trees/binary_trees.kv @@ -0,0 +1,72 @@ +// binary trees benchmark — 建满二叉树并遍历计数(内存分配 + 指针/引用) +// 节点用 L/R 子结点 id 映射表示(id 0 = 空),自顶向下显式栈建树, +// 再用遍历栈跟随指针逐结点计数。深度 depth 由 __SCALE__ 占位(勿改逻辑) +// 期望输出(depth=6 时): +// bintree: nodes = 127 +rwfunc test() -> () { + __SCALE__ -> depth + L:[int64]·int64 = {} + R:[int64]·int64 = {} + bid:[int64]·int64 = {} + bdep:[int64]·int64 = {} + 0 -> nid + t0 <- time·now() + nid + 1 -> nid + nid -> root + 0 -> L·*root + 0 -> R·*root + 0 -> top + root -> bid·*top + depth -> bdep·*top + top + 1 -> top + while (top > 0) { + top - 1 -> top + kv·get(bid, top) -> cur + kv·get(bdep, top) -> d + if (d > 0) { + nid + 1 -> nid + nid -> lc + nid + 1 -> nid + nid -> rc + lc -> L·*cur + rc -> R·*cur + 0 -> L·*lc + 0 -> R·*lc + 0 -> L·*rc + 0 -> R·*rc + d - 1 -> nd + lc -> bid·*top + nd -> bdep·*top + top + 1 -> top + rc -> bid·*top + nd -> bdep·*top + top + 1 -> top + } + } + 0 -> count + tid:[int64]·int64 = {} + 0 -> tp + root -> tid·*tp + tp + 1 -> tp + while (tp > 0) { + tp - 1 -> tp + kv·get(tid, tp) -> cur + count + 1 -> count + kv·get(L, cur) -> lc + kv·get(R, cur) -> rc + if (lc != 0) { + lc -> tid·*tp + tp + 1 -> tp + } + if (rc != 0) { + rc -> tid·*tp + tp + 1 -> tp + } + } + t1 <- time·now() + println("bintree: nodes =", count) + delta <- time·sub(t1, t0) + ns <- time/duration·as_nanos(delta) + println("__bench_ns:", ns) + println("__bench_input: depth=__SCALE__") +} diff --git a/benchmark/cases/binary_trees/binary_trees.py b/benchmark/cases/binary_trees/binary_trees.py new file mode 100644 index 00000000..2c849ac5 --- /dev/null +++ b/benchmark/cases/binary_trees/binary_trees.py @@ -0,0 +1,31 @@ +import os +import time + + +class Node: + __slots__ = ("l", "r") + + def __init__(self, l, r): + self.l = l + self.r = r + + +def make(d): + if d == 0: + return Node(None, None) + return Node(make(d - 1), make(d - 1)) + + +def check(n): + if n.l is None: + return 1 + return 1 + check(n.l) + check(n.r) + + +depth = int(os.environ["BENCH_SCALE"]) +t0 = time.perf_counter_ns() +root = make(depth) +count = check(root) +t1 = time.perf_counter_ns() +print("bintree: nodes =", count) +print("__bench_ns:", t1 - t0) diff --git a/benchmark/cases/binary_trees/binary_trees.rs b/benchmark/cases/binary_trees/binary_trees.rs new file mode 100644 index 00000000..c9deaa16 --- /dev/null +++ b/benchmark/cases/binary_trees/binary_trees.rs @@ -0,0 +1,34 @@ +use std::time::Instant; + +struct Node { + l: Option>, + r: Option>, +} + +fn make(d: i32) -> Box { + if d == 0 { + Box::new(Node { l: None, r: None }) + } else { + Box::new(Node { + l: Some(make(d - 1)), + r: Some(make(d - 1)), + }) + } +} + +fn check(n: &Node) -> i64 { + match &n.l { + None => 1, + Some(l) => 1 + check(l) + check(n.r.as_ref().unwrap()), + } +} + +fn main() { + let depth: i32 = std::env::var("BENCH_SCALE").unwrap().parse().unwrap(); + let t0 = Instant::now(); + let root = make(depth); + let count = check(&root); + let ns = t0.elapsed().as_nanos(); + println!("bintree: nodes = {}", count); + println!("__bench_ns: {}", ns); +} diff --git a/benchmark/cases/fib/fib.c b/benchmark/cases/fib/fib.c new file mode 100644 index 00000000..7a39460f --- /dev/null +++ b/benchmark/cases/fib/fib.c @@ -0,0 +1,21 @@ +#include +#include +#include + +static long fib(long n) { + if (n <= 1) + return n; + return fib(n - 1) + fib(n - 2); +} + +int main(void) { + long scale = atol(getenv("BENCH_SCALE")); + struct timespec t0, t1; + clock_gettime(CLOCK_MONOTONIC, &t0); + volatile long ans = fib(scale); + clock_gettime(CLOCK_MONOTONIC, &t1); + long ns = (t1.tv_sec - t0.tv_sec) * 1000000000L + (t1.tv_nsec - t0.tv_nsec); + printf("fib = %ld\n", ans); + printf("__bench_ns: %ld\n", ns); + return 0; +} diff --git a/benchmark/cases/fib/fib.kv b/benchmark/cases/fib/fib.kv new file mode 100644 index 00000000..ac710b51 --- /dev/null +++ b/benchmark/cases/fib/fib.kv @@ -0,0 +1,26 @@ +// fib benchmark — naive 递归斐波那契(压调用/帧寻址深度,对齐 #116) +// 递归深度 n 由 __SCALE__ 占位,run.py 按扫描点替换;naive 递归调用次数指数级 +// 期望输出(n=10 时): +// fib = 55 +rwfunc fib(n:int64) -> (r:int64) { + if (n <= 1) { + n -> r + } else { + a <- n - 1 + b <- n - 2 + fib(a) -> x + fib(b) -> y + r <- x + y + } +} + +rwfunc test() -> () { + t0 <- time·now() + fib(__SCALE__) -> ans + t1 <- time·now() + println("fib =", ans) + delta <- time·sub(t1, t0) + ns <- time/duration·as_nanos(delta) + println("__bench_ns:", ns) + println("__bench_input: n=__SCALE__") +} diff --git a/benchmark/cases/fib/fib.py b/benchmark/cases/fib/fib.py new file mode 100644 index 00000000..cd608d1a --- /dev/null +++ b/benchmark/cases/fib/fib.py @@ -0,0 +1,15 @@ +import os +import time + + +def fib(n: int) -> int: + if n <= 1: + return n + return fib(n - 1) + fib(n - 2) + + +t0 = time.perf_counter_ns() +ans = fib(int(os.environ["BENCH_SCALE"])) +t1 = time.perf_counter_ns() +print("fib =", ans) +print("__bench_ns:", t1 - t0) diff --git a/benchmark/cases/fib/fib.rs b/benchmark/cases/fib/fib.rs new file mode 100644 index 00000000..6d6c7f63 --- /dev/null +++ b/benchmark/cases/fib/fib.rs @@ -0,0 +1,17 @@ +use std::time::Instant; + +fn fib(n: i64) -> i64 { + if n <= 1 { + return n; + } + fib(n - 1) + fib(n - 2) +} + +fn main() { + let scale: i64 = std::env::var("BENCH_SCALE").unwrap().parse().unwrap(); + let t0 = Instant::now(); + let ans = fib(scale); + let ns = t0.elapsed().as_nanos(); + println!("fib = {}", ans); + println!("__bench_ns: {}", ns); +} diff --git a/benchmark/cases/hash_table/hash_table.c b/benchmark/cases/hash_table/hash_table.c new file mode 100644 index 00000000..093bf8f0 --- /dev/null +++ b/benchmark/cases/hash_table/hash_table.c @@ -0,0 +1,31 @@ +#include +#include +#include + +#define M 100003 + +int main(void) { + long n = atol(getenv("BENCH_SCALE")); + static long h[M]; + for (long k = 0; k < M; k++) + h[k] = -1; + struct timespec t0, t1; + clock_gettime(CLOCK_MONOTONIC, &t0); + for (long i = 0; i < n; i++) { + long key = (i * 2654435761L) % M; + h[key] = i; + } + long sum = 0, hits = 0; + for (long i = 0; i < n; i++) { + long key = (i * 2654435761L) % M; + if (h[key] != -1) { + hits++; + sum += h[key]; + } + } + clock_gettime(CLOCK_MONOTONIC, &t1); + long ns = (t1.tv_sec - t0.tv_sec) * 1000000000L + (t1.tv_nsec - t0.tv_nsec); + printf("hash: hits = %ld sum = %ld\n", hits, sum); + printf("__bench_ns: %ld\n", ns); + return 0; +} diff --git a/benchmark/cases/hash_table/hash_table.kv b/benchmark/cases/hash_table/hash_table.kv new file mode 100644 index 00000000..d6f6dc5f --- /dev/null +++ b/benchmark/cases/hash_table/hash_table.kv @@ -0,0 +1,36 @@ +// hash table benchmark — 散列插入 + 查找(哈希表增删查) +// 键由 Knuth 乘法散列生成,规模 N 由 __SCALE__ 占位(勿改逻辑) +// 期望输出(N=100 时): +// hash: hits = 100 sum = 4950 +rwfunc test() -> () { + __SCALE__ -> n + h:[int64]·int64 = {} + t0 <- time·now() + 0 -> i + while (i < n) { + i × 2654435761 -> k0 + k0 % 100003 -> key + i -> h·*key + i + 1 -> i + } + 0 -> sum + 0 -> hits + 0 -> i + while (i < n) { + i × 2654435761 -> k0 + k0 % 100003 -> key + kv·get(h, key) != None -> ex + if (ex) { + hits + 1 -> hits + kv·get(h, key) -> v + sum + v -> sum + } + i + 1 -> i + } + t1 <- time·now() + println("hash: hits =", hits, "sum =", sum) + delta <- time·sub(t1, t0) + ns <- time/duration·as_nanos(delta) + println("__bench_ns:", ns) + println("__bench_input: N=__SCALE__") +} diff --git a/benchmark/cases/hash_table/hash_table.py b/benchmark/cases/hash_table/hash_table.py new file mode 100644 index 00000000..ce10878b --- /dev/null +++ b/benchmark/cases/hash_table/hash_table.py @@ -0,0 +1,19 @@ +import os +import time + +n = int(os.environ["BENCH_SCALE"]) +h = {} +t0 = time.perf_counter_ns() +for i in range(n): + key = (i * 2654435761) % 100003 + h[key] = i +total = 0 +hits = 0 +for i in range(n): + key = (i * 2654435761) % 100003 + if key in h: + hits += 1 + total += h[key] +t1 = time.perf_counter_ns() +print("hash: hits =", hits, "sum =", total) +print("__bench_ns:", t1 - t0) diff --git a/benchmark/cases/hash_table/hash_table.rs b/benchmark/cases/hash_table/hash_table.rs new file mode 100644 index 00000000..3def5df4 --- /dev/null +++ b/benchmark/cases/hash_table/hash_table.rs @@ -0,0 +1,24 @@ +use std::collections::HashMap; +use std::time::Instant; + +fn main() { + let n: i64 = std::env::var("BENCH_SCALE").unwrap().parse().unwrap(); + let mut h: HashMap = HashMap::new(); + let t0 = Instant::now(); + for i in 0..n { + let key = (i * 2654435761) % 100003; + h.insert(key, i); + } + let mut sum = 0i64; + let mut hits = 0i64; + for i in 0..n { + let key = (i * 2654435761) % 100003; + if let Some(v) = h.get(&key) { + hits += 1; + sum += *v; + } + } + let ns = t0.elapsed().as_nanos(); + println!("hash: hits = {} sum = {}", hits, sum); + println!("__bench_ns: {}", ns); +} diff --git a/benchmark/cases/iops/iops.c b/benchmark/cases/iops/iops.c new file mode 100644 index 00000000..923642b9 --- /dev/null +++ b/benchmark/cases/iops/iops.c @@ -0,0 +1,22 @@ +#include +#include +#include + +static long iops(long n) { + long a = 0; + for (long i = 1; i <= n; ++i) + a = a + 1; + return a; +} + +int main(void) { + long scale = atol(getenv("BENCH_SCALE")); + struct timespec t0, t1; + clock_gettime(CLOCK_MONOTONIC, &t0); + volatile long ans = iops(scale); + clock_gettime(CLOCK_MONOTONIC, &t1); + long ns = (t1.tv_sec - t0.tv_sec) * 1000000000L + (t1.tv_nsec - t0.tv_nsec); + printf("iops a = %ld\n", ans); + printf("__bench_ns: %ld\n", ns); + return 0; +} diff --git a/benchmark/cases/iops/iops.kv b/benchmark/cases/iops/iops.kv new file mode 100644 index 00000000..00bdf2c9 --- /dev/null +++ b/benchmark/cases/iops/iops.kv @@ -0,0 +1,23 @@ +// iops benchmark — 单变量反复读改写 a<-a+1(最小寻址单元的每操作 KV 往返地板价,对齐 #204) +// 循环次数 N 由 __SCALE__ 占位(勿改逻辑) +// 期望输出(N=2000 时): +// iops a = 2000 +rwfunc iops(n:int64) -> (a:int64) { + a = 0 + 1 -> i + while (i <= n) { + a <- a + 1 + i = i + 1 + } +} + +rwfunc test() -> () { + t0 <- time·now() + iops(__SCALE__) -> ans + t1 <- time·now() + println("iops a =", ans) + delta <- time·sub(t1, t0) + ns <- time/duration·as_nanos(delta) + println("__bench_ns:", ns) + println("__bench_input: N=__SCALE__") +} diff --git a/benchmark/cases/iops/iops.py b/benchmark/cases/iops/iops.py new file mode 100644 index 00000000..300099ea --- /dev/null +++ b/benchmark/cases/iops/iops.py @@ -0,0 +1,18 @@ +import os +import time + + +def iops(n: int) -> int: + a = 0 + i = 1 + while i <= n: + a = a + 1 + i = i + 1 + return a + + +t0 = time.perf_counter_ns() +ans = iops(int(os.environ["BENCH_SCALE"])) +t1 = time.perf_counter_ns() +print("iops a =", ans) +print("__bench_ns:", t1 - t0) diff --git a/benchmark/cases/iops/iops.rs b/benchmark/cases/iops/iops.rs new file mode 100644 index 00000000..d483cf75 --- /dev/null +++ b/benchmark/cases/iops/iops.rs @@ -0,0 +1,20 @@ +use std::time::Instant; + +fn iops(n: i64) -> i64 { + let mut a = 0; + let mut i = 1; + while i <= n { + a = a + 1; + i = i + 1; + } + a +} + +fn main() { + let scale: i64 = std::env::var("BENCH_SCALE").unwrap().parse().unwrap(); + let t0 = Instant::now(); + let ans = iops(scale); + let ns = t0.elapsed().as_nanos(); + println!("iops a = {}", ans); + println!("__bench_ns: {}", ns); +} diff --git a/benchmark/cases/k_nucleotide/k_nucleotide.c b/benchmark/cases/k_nucleotide/k_nucleotide.c new file mode 100644 index 00000000..4dfe941c --- /dev/null +++ b/benchmark/cases/k_nucleotide/k_nucleotide.c @@ -0,0 +1,26 @@ +#include +#include +#include +#include + +int main(void) { + const char *base = + "GGTATTGAGCACTGGCAATTGACGTCAGGTATCCGAATTGCACGTTAGCATGCATGCATGCACGT"; + int rep = atoi(getenv("BENCH_SCALE")); + char s[strlen(base) * rep + 1]; + s[0] = '\0'; + for (int r = 0; r < rep; r++) + strcat(s, base); + long h[128] = {0}; + long n = strlen(s); + struct timespec t0, t1; + clock_gettime(CLOCK_MONOTONIC, &t0); + for (long i = 0; i < n; i++) + h[(int)s[i]]++; + clock_gettime(CLOCK_MONOTONIC, &t1); + long ns = (t1.tv_sec - t0.tv_sec) * 1000000000L + (t1.tv_nsec - t0.tv_nsec); + printf("knuc: A = %ld C = %ld G = %ld T = %ld\n", h[65], h[67], h[71], + h[84]); + printf("__bench_ns: %ld\n", ns); + return 0; +} diff --git a/benchmark/cases/k_nucleotide/k_nucleotide.kv b/benchmark/cases/k_nucleotide/k_nucleotide.kv new file mode 100644 index 00000000..d6aa99c6 --- /dev/null +++ b/benchmark/cases/k_nucleotide/k_nucleotide.kv @@ -0,0 +1,39 @@ +// k-nucleotide benchmark — 统计 DNA 序列碱基频次(字符串 + 哈希表) +// 65 字符基串重复 rep 次得定长序列,逐字符 ord 入 int 键哈希表计数。 +// 重复次数 rep 由 __SCALE__ 占位(勿改逻辑) +// 期望输出(rep=5 时): +// knuc: A = 80 C = 70 G = 90 T = 85 +rwfunc test() -> () { + "GGTATTGAGCACTGGCAATTGACGTCAGGTATCCGAATTGCACGTTAGCATGCATGCATGCACGT" -> base + "" -> s + 0 -> r + while (r < __SCALE__) { + s + base -> s + r + 1 -> r + } + string·len(s) -> n + h:[int64]·int64 = {} + t0 <- time·now() + 0 -> i + while (i < n) { + string·char(s, i) -> ch + string·ord(ch) -> c + kv·get(h, c) -> cur + if (cur == None) { + 0 -> cur + } + cur + 1 -> cnt + cnt -> h·*c + i + 1 -> i + } + t1 <- time·now() + kv·get(h, 65) -> a + kv·get(h, 67) -> cc + kv·get(h, 71) -> g + kv·get(h, 84) -> t + println("knuc: A =", a, "C =", cc, "G =", g, "T =", t) + delta <- time·sub(t1, t0) + ns <- time/duration·as_nanos(delta) + println("__bench_ns:", ns) + println("__bench_input: rep=__SCALE__") +} diff --git a/benchmark/cases/k_nucleotide/k_nucleotide.py b/benchmark/cases/k_nucleotide/k_nucleotide.py new file mode 100644 index 00000000..4c65044c --- /dev/null +++ b/benchmark/cases/k_nucleotide/k_nucleotide.py @@ -0,0 +1,15 @@ +import os +import time + +base = "GGTATTGAGCACTGGCAATTGACGTCAGGTATCCGAATTGCACGTTAGCATGCATGCATGCACGT" +s = base * int(os.environ["BENCH_SCALE"]) +h = {} +t0 = time.perf_counter_ns() +for ch in s: + c = ord(ch) + h[c] = h.get(c, 0) + 1 +t1 = time.perf_counter_ns() +print( + "knuc: A =", h[65], "C =", h[67], "G =", h[71], "T =", h[84] +) +print("__bench_ns:", t1 - t0) diff --git a/benchmark/cases/k_nucleotide/k_nucleotide.rs b/benchmark/cases/k_nucleotide/k_nucleotide.rs new file mode 100644 index 00000000..aed77408 --- /dev/null +++ b/benchmark/cases/k_nucleotide/k_nucleotide.rs @@ -0,0 +1,20 @@ +use std::collections::HashMap; +use std::time::Instant; + +fn main() { + let base = "GGTATTGAGCACTGGCAATTGACGTCAGGTATCCGAATTGCACGTTAGCATGCATGCATGCACGT"; + let scale: usize = std::env::var("BENCH_SCALE").unwrap().parse().unwrap(); + let s = base.repeat(scale); + let mut h: HashMap = HashMap::new(); + let t0 = Instant::now(); + for ch in s.chars() { + let c = ch as i64; + *h.entry(c).or_insert(0) += 1; + } + let ns = t0.elapsed().as_nanos(); + println!( + "knuc: A = {} C = {} G = {} T = {}", + h[&65], h[&67], h[&71], h[&84] + ); + println!("__bench_ns: {}", ns); +} diff --git a/benchmark/cases/matmul/matmul.c b/benchmark/cases/matmul/matmul.c new file mode 100644 index 00000000..aec4e37c --- /dev/null +++ b/benchmark/cases/matmul/matmul.c @@ -0,0 +1,32 @@ +#include +#include +#include + +int main(void) { + int n = atoi(getenv("BENCH_SCALE")); + double A[n * n], B[n * n]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + int idx = i * n + j; + A[idx] = (double)((i + j) % 4) * 0.25 + 0.25; + B[idx] = (double)((i * 2 + j) % 4) * 0.25 + 0.25; + } + } + struct timespec t0, t1; + clock_gettime(CLOCK_MONOTONIC, &t0); + double checksum = 0.0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + double acc = 0.0; + for (int k = 0; k < n; k++) + acc += A[i * n + k] * B[k * n + j]; + checksum += acc; + } + } + clock_gettime(CLOCK_MONOTONIC, &t1); + long ns = (t1.tv_sec - t0.tv_sec) * 1000000000L + (t1.tv_nsec - t0.tv_nsec); + long out = (long)(checksum * 1000000.0); + printf("matmul: check = %ld\n", out); + printf("__bench_ns: %ld\n", ns); + return 0; +} diff --git a/benchmark/cases/matmul/matmul.kv b/benchmark/cases/matmul/matmul.kv new file mode 100644 index 00000000..759561fe --- /dev/null +++ b/benchmark/cases/matmul/matmul.kv @@ -0,0 +1,65 @@ +// matrix multiplication benchmark — 稠密方阵乘(浮点运算 + 三重循环) +// A/B 元素取 0.25 倍数(二进制精确),C 校验和 × 1e6 恒为精确整数、跨语言逐字节一致 +// 方阵阶 N 由 __SCALE__ 占位(O(N^3),勿改逻辑) +// 期望输出(N=6 时): +// matmul: check = 82750000 +rwfunc test() -> () { + __SCALE__ -> n + A:[int64]·float64 = {} + B:[int64]·float64 = {} + 0 -> i + while (i < n) { + 0 -> j + while (j < n) { + i × n -> base + base + j -> idx + i + j -> ta + ta % 4 -> qa + float64(qa) -> qaf + qaf × 0.25 -> pa + pa + 0.25 -> av + av -> A·*idx + i × 2 -> i2 + i2 + j -> tb + tb % 4 -> qb + float64(qb) -> qbf + qbf × 0.25 -> pb + pb + 0.25 -> bv + bv -> B·*idx + j + 1 -> j + } + i + 1 -> i + } + t0 <- time·now() + 0.0 -> checksum + 0 -> i + while (i < n) { + 0 -> j + while (j < n) { + 0.0 -> acc + 0 -> k + while (k < n) { + i × n -> ba + ba + k -> ia + kv·get(A, ia) -> av + k × n -> bb + bb + j -> ib + kv·get(B, ib) -> bv + av × bv -> pr + acc + pr -> acc + k + 1 -> k + } + checksum + acc -> checksum + j + 1 -> j + } + i + 1 -> i + } + t1 <- time·now() + checksum × 1000000.0 -> scaled + int64(scaled) -> out + println("matmul: check =", out) + delta <- time·sub(t1, t0) + ns <- time/duration·as_nanos(delta) + println("__bench_ns:", ns) + println("__bench_input: N=__SCALE__") +} diff --git a/benchmark/cases/matmul/matmul.py b/benchmark/cases/matmul/matmul.py new file mode 100644 index 00000000..f8aa7aea --- /dev/null +++ b/benchmark/cases/matmul/matmul.py @@ -0,0 +1,23 @@ +import os +import time + +n = int(os.environ["BENCH_SCALE"]) +A = [0.0] * (n * n) +B = [0.0] * (n * n) +for i in range(n): + for j in range(n): + idx = i * n + j + A[idx] = float((i + j) % 4) * 0.25 + 0.25 + B[idx] = float((i * 2 + j) % 4) * 0.25 + 0.25 +t0 = time.perf_counter_ns() +checksum = 0.0 +for i in range(n): + for j in range(n): + acc = 0.0 + for k in range(n): + acc += A[i * n + k] * B[k * n + j] + checksum += acc +t1 = time.perf_counter_ns() +out = int(checksum * 1000000.0) +print("matmul: check =", out) +print("__bench_ns:", t1 - t0) diff --git a/benchmark/cases/matmul/matmul.rs b/benchmark/cases/matmul/matmul.rs new file mode 100644 index 00000000..0bc39954 --- /dev/null +++ b/benchmark/cases/matmul/matmul.rs @@ -0,0 +1,29 @@ +use std::time::Instant; + +fn main() { + let n: usize = std::env::var("BENCH_SCALE").unwrap().parse().unwrap(); + let mut a = vec![0.0f64; n * n]; + let mut b = vec![0.0f64; n * n]; + for i in 0..n { + for j in 0..n { + let idx = i * n + j; + a[idx] = ((i + j) % 4) as f64 * 0.25 + 0.25; + b[idx] = ((i * 2 + j) % 4) as f64 * 0.25 + 0.25; + } + } + let t0 = Instant::now(); + let mut checksum = 0.0f64; + for i in 0..n { + for j in 0..n { + let mut acc = 0.0f64; + for k in 0..n { + acc += a[i * n + k] * b[k * n + j]; + } + checksum += acc; + } + } + let ns = t0.elapsed().as_nanos(); + let out = (checksum * 1000000.0) as i64; + println!("matmul: check = {}", out); + println!("__bench_ns: {}", ns); +} diff --git a/benchmark/cases/nqueens/nqueens.c b/benchmark/cases/nqueens/nqueens.c new file mode 100644 index 00000000..0e20b134 --- /dev/null +++ b/benchmark/cases/nqueens/nqueens.c @@ -0,0 +1,29 @@ +#include +#include +#include + +static long nq(long cols, long d1, long d2, long all) { + if (cols == all) + return 1; + long cnt = 0; + long avail = ((cols | d1 | d2) & all) ^ all; + while (avail != 0) { + long p = avail & (-avail); + avail ^= p; + cnt += nq(cols | p, (d1 | p) << 1, (d2 | p) >> 1, all); + } + return cnt; +} + +int main(void) { + long scale = atol(getenv("BENCH_SCALE")); + struct timespec t0, t1; + clock_gettime(CLOCK_MONOTONIC, &t0); + long all = (1L << scale) - 1; + long ans = nq(0, 0, 0, all); + clock_gettime(CLOCK_MONOTONIC, &t1); + long ns = (t1.tv_sec - t0.tv_sec) * 1000000000L + (t1.tv_nsec - t0.tv_nsec); + printf("queens = %ld\n", ans); + printf("__bench_ns: %ld\n", ns); + return 0; +} diff --git a/benchmark/cases/nqueens/nqueens.kv b/benchmark/cases/nqueens/nqueens.kv new file mode 100644 index 00000000..16ce4459 --- /dev/null +++ b/benchmark/cases/nqueens/nqueens.kv @@ -0,0 +1,40 @@ +// n-queens benchmark — 位运算回溯数解(递归 + 整数位运算 + 分支) +// 棋盘规模 N 由 __SCALE__ 占位,run.py 按扫描点替换(勿改逻辑) +// 期望输出(N=5 时): +// queens = 10 +rwfunc nq(cols:int64, d1:int64, d2:int64, all:int64) -> (cnt:int64) { + if (cols == all) { + 1 -> cnt + } else { + 0 -> cnt + cols | d1 -> occ0 + occ0 | d2 -> occ1 + occ1 & all -> occ + occ ^ all -> avail + while (avail != 0) { + 0 - avail -> neg + avail & neg -> p + avail ^ p -> avail + cols | p -> nc + d1 | p -> u1 + u1 << 1 -> nd1 + d2 | p -> u2 + u2 >> 1 -> nd2 + nq(nc, nd1, nd2, all) -> sub + cnt <- cnt + sub + } + } +} + +rwfunc test() -> () { + t0 <- time·now() + 1 << __SCALE__ -> sh + sh - 1 -> all + nq(0, 0, 0, all) -> ans + t1 <- time·now() + println("queens =", ans) + delta <- time·sub(t1, t0) + ns <- time/duration·as_nanos(delta) + println("__bench_ns:", ns) + println("__bench_input: N=__SCALE__") +} diff --git a/benchmark/cases/nqueens/nqueens.py b/benchmark/cases/nqueens/nqueens.py new file mode 100644 index 00000000..ade8bd2d --- /dev/null +++ b/benchmark/cases/nqueens/nqueens.py @@ -0,0 +1,22 @@ +import os +import time + + +def nq(cols, d1, d2, all): + if cols == all: + return 1 + cnt = 0 + avail = ((cols | d1 | d2) & all) ^ all + while avail != 0: + p = avail & (-avail) + avail ^= p + cnt += nq(cols | p, (d1 | p) << 1, (d2 | p) >> 1, all) + return cnt + + +t0 = time.perf_counter_ns() +all = (1 << int(os.environ["BENCH_SCALE"])) - 1 +ans = nq(0, 0, 0, all) +t1 = time.perf_counter_ns() +print("queens =", ans) +print("__bench_ns:", t1 - t0) diff --git a/benchmark/cases/nqueens/nqueens.rs b/benchmark/cases/nqueens/nqueens.rs new file mode 100644 index 00000000..ad425ded --- /dev/null +++ b/benchmark/cases/nqueens/nqueens.rs @@ -0,0 +1,25 @@ +use std::time::Instant; + +fn nq(cols: i64, d1: i64, d2: i64, all: i64) -> i64 { + if cols == all { + return 1; + } + let mut cnt = 0; + let mut avail = ((cols | d1 | d2) & all) ^ all; + while avail != 0 { + let p = avail & (-avail); + avail ^= p; + cnt += nq(cols | p, (d1 | p) << 1, (d2 | p) >> 1, all); + } + cnt +} + +fn main() { + let scale: i64 = std::env::var("BENCH_SCALE").unwrap().parse().unwrap(); + let t0 = Instant::now(); + let all = (1i64 << scale) - 1; + let ans = nq(0, 0, 0, all); + let ns = t0.elapsed().as_nanos(); + println!("queens = {}", ans); + println!("__bench_ns: {}", ns); +} diff --git a/benchmark/cases/prime_sieve/prime_sieve.c b/benchmark/cases/prime_sieve/prime_sieve.c new file mode 100644 index 00000000..05346246 --- /dev/null +++ b/benchmark/cases/prime_sieve/prime_sieve.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +static void prime_sieve(long limit) { + printf("primes up to %ld\n", limit); + long count = 0; + for (long n = 2; n <= limit; ++n) { + bool is_prime = true; + for (long d = 2; d < n; ++d) + if (n % d == 0) { + is_prime = false; + break; + } + if (is_prime) { + printf(" prime: %ld\n", n); + ++count; + } + } + printf("total primes up to %ld = %ld\n", limit, count); +} + +int main(void) { + long scale = atol(getenv("BENCH_SCALE")); + struct timespec t0, t1; + clock_gettime(CLOCK_MONOTONIC, &t0); + prime_sieve(scale); + clock_gettime(CLOCK_MONOTONIC, &t1); + long ns = (t1.tv_sec - t0.tv_sec) * 1000000000L + (t1.tv_nsec - t0.tv_nsec); + printf("__bench_ns: %ld\n", ns); + return 0; +} diff --git a/benchmark/cases/prime_sieve/prime_sieve.kv b/benchmark/cases/prime_sieve/prime_sieve.kv new file mode 100644 index 00000000..9f7211c3 --- /dev/null +++ b/benchmark/cases/prime_sieve/prime_sieve.kv @@ -0,0 +1,41 @@ +// prime_sieve benchmark — 试除法求 <=N 的质数(嵌套 while + 取模,计算/控制流密集) +// 上界 N 由 __SCALE__ 占位(勿改逻辑) +// 期望输出(N=100 时): +// primes up to 100 +// ... +// total primes up to 100 = 25 +rwfunc prime_sieve(limit:int64) -> () { + println("primes up to", limit) + count = 0 + 2 -> n + while (n <= limit) { + is_prime <- true + d = 2 + while (d < n) { + n % d -> rem + divisible <- rem == 0 + if (divisible) { + is_prime = false + break + } else { + d <- d + 1 + } + } + if (is_prime) { + println(" prime:", n) + count = count + 1 + } + n + 1 -> n + } + println("total primes up to", limit, "=", count) +} + +rwfunc test() -> () { + t0 <- time·now() + prime_sieve(__SCALE__) + t1 <- time·now() + delta <- time·sub(t1, t0) + ns <- time/duration·as_nanos(delta) + println("__bench_ns:", ns) + println("__bench_input: N=__SCALE__") +} diff --git a/benchmark/cases/prime_sieve/prime_sieve.py b/benchmark/cases/prime_sieve/prime_sieve.py new file mode 100644 index 00000000..3c11bbcd --- /dev/null +++ b/benchmark/cases/prime_sieve/prime_sieve.py @@ -0,0 +1,27 @@ +import os +import time + + +def prime_sieve(limit: int) -> None: + print("primes up to", limit) + count = 0 + n = 2 + while n <= limit: + is_prime = True + d = 2 + while d < n: + if n % d == 0: + is_prime = False + break + d += 1 + if is_prime: + print(" prime:", n) + count += 1 + n += 1 + print("total primes up to", limit, "=", count) + + +t0 = time.perf_counter_ns() +prime_sieve(int(os.environ["BENCH_SCALE"])) +t1 = time.perf_counter_ns() +print("__bench_ns:", t1 - t0) diff --git a/benchmark/cases/prime_sieve/prime_sieve.rs b/benchmark/cases/prime_sieve/prime_sieve.rs new file mode 100644 index 00000000..0f7a9364 --- /dev/null +++ b/benchmark/cases/prime_sieve/prime_sieve.rs @@ -0,0 +1,32 @@ +use std::time::Instant; + +fn prime_sieve(limit: i64) { + println!("primes up to {}", limit); + let mut count = 0; + let mut n = 2; + while n <= limit { + let mut is_prime = true; + let mut d = 2; + while d < n { + if n % d == 0 { + is_prime = false; + break; + } + d += 1; + } + if is_prime { + println!(" prime: {}", n); + count += 1; + } + n += 1; + } + println!("total primes up to {} = {}", limit, count); +} + +fn main() { + let scale: i64 = std::env::var("BENCH_SCALE").unwrap().parse().unwrap(); + let t0 = Instant::now(); + prime_sieve(scale); + let ns = t0.elapsed().as_nanos(); + println!("__bench_ns: {}", ns); +} diff --git a/benchmark/cases/quicksort/quicksort.c b/benchmark/cases/quicksort/quicksort.c new file mode 100644 index 00000000..a1a5717e --- /dev/null +++ b/benchmark/cases/quicksort/quicksort.c @@ -0,0 +1,44 @@ +#include +#include +#include + +static void swap(long *a, long i, long j) { + long t = a[i]; + a[i] = a[j]; + a[j] = t; +} + +static void qsort_r(long *a, long lo, long hi) { + if (lo < hi) { + long pivot = a[hi]; + long i = lo - 1; + for (long j = lo; j < hi; j++) { + if (a[j] <= pivot) { + i++; + swap(a, i, j); + } + } + swap(a, i + 1, hi); + long p = i + 1; + qsort_r(a, lo, p - 1); + qsort_r(a, p + 1, hi); + } +} + +int main(void) { + long n = atol(getenv("BENCH_SCALE")); + long arr[n]; + long seed = 1; + for (long i = 0; i < n; i++) { + seed = (seed * 1103515245 + 12345) % 2147483648; + arr[i] = seed % 100; + } + struct timespec t0, t1; + clock_gettime(CLOCK_MONOTONIC, &t0); + qsort_r(arr, 0, n - 1); + clock_gettime(CLOCK_MONOTONIC, &t1); + long ns = (t1.tv_sec - t0.tv_sec) * 1000000000L + (t1.tv_nsec - t0.tv_nsec); + printf("qsort: a0 = %ld amid = %ld alast = %ld\n", arr[0], arr[n / 2], arr[n - 1]); + printf("__bench_ns: %ld\n", ns); + return 0; +} diff --git a/benchmark/cases/quicksort/quicksort.kv b/benchmark/cases/quicksort/quicksort.kv new file mode 100644 index 00000000..21c113d1 --- /dev/null +++ b/benchmark/cases/quicksort/quicksort.kv @@ -0,0 +1,66 @@ +// quicksort benchmark — 迭代 Lomuto 分区就地排序(数组访问 + 显式栈递归) +// 数据由 LCG 确定生成,规模 N 由 __SCALE__ 占位,run.py 按扫描点替换(勿改逻辑) +// 期望输出(N=64 时): +// qsort: a0 = 1 amid = 52 alast = 99 +rwfunc test() -> () { + __SCALE__ -> n + arr:[int64]·int64 = {} + 1 -> seed + 0 -> i + while (i < n) { + seed × 1103515245 -> s1 + s1 + 12345 -> s2 + s2 % 2147483648 -> seed + seed % 100 -> v + v -> arr·*i + i + 1 -> i + } + t0 <- time·now() + st_lo:[int64]·int64 = {} + st_hi:[int64]·int64 = {} + 0 -> top + 0 -> st_lo·*top + n - 1 -> st_hi·*top + top + 1 -> top + while (top > 0) { + top - 1 -> top + kv·get(st_lo, top) -> lo + kv·get(st_hi, top) -> hi + if (lo < hi) { + kv·get(arr, hi) -> pivot + lo - 1 -> ii + lo -> jj + while (jj < hi) { + kv·get(arr, jj) -> aj + if (aj <= pivot) { + ii + 1 -> ii + kv·get(arr, ii) -> ai + aj -> arr·*ii + ai -> arr·*jj + } + jj + 1 -> jj + } + ii + 1 -> pp + kv·get(arr, pp) -> app + pivot -> arr·*pp + app -> arr·*hi + lo -> st_lo·*top + pp - 1 -> st_hi·*top + top + 1 -> top + pp + 1 -> st_lo·*top + hi -> st_hi·*top + top + 1 -> top + } + } + t1 <- time·now() + n ÷ 2 -> mid + n - 1 -> last + kv·get(arr, 0) -> a0 + kv·get(arr, mid) -> am + kv·get(arr, last) -> al + println("qsort: a0 =", a0, "amid =", am, "alast =", al) + delta <- time·sub(t1, t0) + ns <- time/duration·as_nanos(delta) + println("__bench_ns:", ns) + println("__bench_input: N=__SCALE__,seed=1") +} diff --git a/benchmark/cases/quicksort/quicksort.py b/benchmark/cases/quicksort/quicksort.py new file mode 100644 index 00000000..3e866cde --- /dev/null +++ b/benchmark/cases/quicksort/quicksort.py @@ -0,0 +1,32 @@ +import os +import time +import sys + +sys.setrecursionlimit(10000) + + +def qsort(a, lo, hi): + if lo < hi: + pivot = a[hi] + i = lo - 1 + for j in range(lo, hi): + if a[j] <= pivot: + i += 1 + a[i], a[j] = a[j], a[i] + a[i + 1], a[hi] = a[hi], a[i + 1] + p = i + 1 + qsort(a, lo, p - 1) + qsort(a, p + 1, hi) + + +n = int(os.environ["BENCH_SCALE"]) +arr = [] +seed = 1 +for i in range(n): + seed = (seed * 1103515245 + 12345) % 2147483648 + arr.append(seed % 100) +t0 = time.perf_counter_ns() +qsort(arr, 0, n - 1) +t1 = time.perf_counter_ns() +print("qsort: a0 =", arr[0], "amid =", arr[n // 2], "alast =", arr[n - 1]) +print("__bench_ns:", t1 - t0) diff --git a/benchmark/cases/quicksort/quicksort.rs b/benchmark/cases/quicksort/quicksort.rs new file mode 100644 index 00000000..98baef52 --- /dev/null +++ b/benchmark/cases/quicksort/quicksort.rs @@ -0,0 +1,35 @@ +use std::time::Instant; + +fn qsort(a: &mut [i64], lo: i64, hi: i64) { + if lo < hi { + let pivot = a[hi as usize]; + let mut i = lo - 1; + let mut j = lo; + while j < hi { + if a[j as usize] <= pivot { + i += 1; + a.swap(i as usize, j as usize); + } + j += 1; + } + a.swap((i + 1) as usize, hi as usize); + let p = i + 1; + qsort(a, lo, p - 1); + qsort(a, p + 1, hi); + } +} + +fn main() { + let n: i64 = std::env::var("BENCH_SCALE").unwrap().parse().unwrap(); + let mut arr: Vec = Vec::new(); + let mut seed: i64 = 1; + for _ in 0..n { + seed = (seed * 1103515245 + 12345) % 2147483648; + arr.push(seed % 100); + } + let t0 = Instant::now(); + qsort(&mut arr, 0, n - 1); + let ns = t0.elapsed().as_nanos(); + println!("qsort: a0 = {} amid = {} alast = {}", arr[0], arr[(n / 2) as usize], arr[(n - 1) as usize]); + println!("__bench_ns: {}", ns); +} diff --git a/benchmark/run.py b/benchmark/run.py new file mode 100644 index 00000000..16d3239c --- /dev/null +++ b/benchmark/run.py @@ -0,0 +1,362 @@ +#!/usr/bin/env python3 +"""kvlang 跨语言 / 跨后端性能基准。 + +同一算法四份逻辑等价实现(cases//.{kv,py,rs,c})。 +kvlang 是被测对象,分别在三个 kvspace 后端上跑,各占一列: + kvlang_shm —— kvspace-c 共享内存(shm://,内存态地板) + kvlang_fs —— kvspace-durable 文件后端(fs://) + kvlang_redis —— kvspace-durable redis 后端(redis://) +python / rust / c 与后端无关,作原生/脚本基线各一列。 + +设计目标:跨版本长期可比。 +- 例子规模写死在各实现里,勿随版本改动;每份实现打印 `__bench_input: <规模>` + (仅 kvlang 那份被采纳,与语言无关),落 csv 的 input 列。 +- 计时约定稳定:每份实现打印一行 `__bench_ns: <整数纳秒>`,功能输出在前。 +- 每次运行落一个独立快照文件 results/results--.csv, + 文件名带 kvlang 版本与运行时刻;行内另记 python/rust/c 工具链版本。 + +用法: + python3 benchmark/run.py # 全部 case,min of 3,写快照 + python3 benchmark/run.py -k prime_sieve # 只跑某 case + python3 benchmark/run.py --repeat 1 # 快跑(不取 min) + python3 benchmark/run.py --backends shm,fs # 只跑部分后端 + python3 benchmark/run.py --no-write # 只打印不落 csv + python3 benchmark/run.py --show # 汇总 results/ 历史,不跑 +""" +import argparse +import csv +import os +import platform +import re +import shutil +import subprocess +import sys +import tempfile +from datetime import datetime, timezone +from pathlib import Path + +ROOT = Path(__file__).resolve().parent +KVLANG_ROOT = ROOT.parent +CASES_DIR = ROOT / "cases" +RESULTS_DIR = ROOT / "results" +BENCH_RE = re.compile(r"__bench_ns:\s*(\d+)") +INPUT_RE = re.compile(r"__bench_input:\s*(.+)") +KV_BACKENDS = ("shm", "fs", "redis") # kvlang 三后端列顺序 +NATIVE = ("python", "rust", "c") +EXT = {"kvlang": ".kv", "python": ".py", "rust": ".rs", "c": ".c"} + +# 输入规模阶梯:每个 case 在这一串规模上各跑一遍,逐点落一行 csv。 +# 规模点亦属冻结契约——一经确定勿改,跨版本才可按同一规模对比。 +# scale 传入方式:kvlang 走源码 __SCALE__ 占位替换;python/rust/c 走环境变量 BENCH_SCALE。 +# 上限刻意放低,确保三后端全量约 1 小时内跑完(v0.2.5 每操作一次 KV 往返、常数因子仍大, +# 后续版本会持续优化、逐步逼近 python,这套快照正是用来逐版本追踪那条加速曲线的)。 +SWEEP = { + "iops": [500, 1000, 2000], + "prime_sieve": [50, 75, 100], + "fib": [8, 9, 10], + "nqueens": [4, 5, 6], + "quicksort": [32, 64, 128], + "binary_search": [16, 32, 64], + "binary_trees": [4, 5, 6], + "hash_table": [50, 100, 200], + "matmul": [4, 6, 8], + "k_nucleotide": [3, 5, 8], +} +SCALE_ENV = "BENCH_SCALE" # python/rust/c 从此环境变量读规模 +SCALE_TOKEN = "__SCALE__" # kvlang 源码里的规模占位符 +FIELDS = ["timestamp", "version", "commit", "cpu", "case", "input", "samples", + "kvlang_shm_ns", "kvlang_fs_ns", "kvlang_redis_ns", + "python_ns", "rust_ns", "c_ns", + "python_ver", "rust_ver", "c_ver", "native_opt", "valid"] + + +def sh(cmd, timeout=None, env=None): + return subprocess.run(cmd, capture_output=True, text=True, + timeout=timeout, env=env) + + +def git_version(): + v = sh(["git", "-C", str(KVLANG_ROOT), "describe", "--tags", "--always", + "--dirty"]).stdout.strip() or "unknown" + c = sh(["git", "-C", str(KVLANG_ROOT), "rev-parse", "--short", + "HEAD"]).stdout.strip() or "unknown" + return v, c + + +def git_tag(): + return sh(["git", "-C", str(KVLANG_ROOT), "describe", "--tags", + "--abbrev=0"]).stdout.strip() or "v0" + + +def tool_versions(): + """python / rust / c 工具链版本,随 csv 落盘——基线也会随工具升级而变。""" + py = platform.python_version() + rm = re.search(r"rustc (\S+)", sh(["rustc", "--version"]).stdout) + g = sh(["gcc", "--version"]).stdout.splitlines() + gm = re.search(r"(\d+\.\d+\.\d+)", g[0]) if g else None + return py, (rm.group(1) if rm else "unknown"), (gm.group(1) if gm else "unknown") + + +def cpu_model(): + """CPU 型号——跨机对比的前提。""" + try: + for ln in Path("/proc/cpuinfo").read_text().splitlines(): + if ln.startswith("model name"): + return ln.split(":", 1)[1].strip() + except OSError: + pass + return platform.processor() or "unknown" + + +def parse(out): + """(bench_ns|None, input|None, 功能输出) —— 剥掉两种标记行,其余为功能输出。""" + ns, inp, func = None, None, [] + for ln in out.splitlines(): + m, mi = BENCH_RE.search(ln), INPUT_RE.search(ln) + if m: + ns = int(m.group(1)) + elif mi: + inp = mi.group(1).strip() + else: + func.append(ln) + return ns, inp, "\n".join(func).rstrip() + + +# 禁用 -O2/-O3 的过度优化:那些优化会把无外部副作用的循环闭式折叠/消除 +# (iops 的 a+=1 在 gcc -O2 下 2000 次循环塌成常量,88ns 纯失真), +# -O1 保留真实工作量又不做激进变换。python 解释执行无编译期优化,不涉及。 +NATIVE_OPT = "gcc -O1 / rustc opt-level=1 / cpython" + + +def compile_native(lang, src, workdir): + exe = workdir / "bench.out" + if lang == "rust": + cc = sh(["rustc", "-C", "opt-level=1", "-o", str(exe), str(src)]) + else: + cc = sh(["gcc", "-O1", "-o", str(exe), str(src)]) + if cc.returncode != 0: + raise RuntimeError(f"{lang} 编译失败:\n{cc.stderr}") + return [str(exe)] + + +def kv_dsn(label, workdir, redis_dsn): + if label == "shm": + return f"shm://{workdir}/bench.shm" + if label == "fs": + return f"fs://{workdir}/bench.fs" + return redis_dsn + + +def kv_reset(label, workdir, redis_dsn): + """每次采样前清空该后端,杜绝残留污染。""" + if label == "shm": + Path(f"{workdir}/bench.shm").unlink(missing_ok=True) + elif label == "fs": + shutil.rmtree(f"{workdir}/bench.fs", ignore_errors=True) + else: + addr = redis_dsn.split("://", 1)[-1] + host, _, port = addr.partition(":") + sh(["redis-cli", "-h", host or "127.0.0.1", "-p", port or "6379", + "flushall"], timeout=10) + + +def run_kvlang(case_dir, label, args, workdir, scale): + # kvlang 无从脚本读环境变量的 builtin,故按规模把源码里的 __SCALE__ 占位替换后落临时文件再跑。 + text = (case_dir / (case_dir.name + ".kv")).read_text() + src = workdir / (case_dir.name + ".kv") + src.write_text(text.replace(SCALE_TOKEN, str(scale))) + dsn = kv_dsn(label, workdir, args.redis) + + def _run(): + kv_reset(label, workdir, args.redis) + env = {**os.environ, "KVSPACE": dsn} + return sh([args.kvlang_bin, str(src)], timeout=args.timeout, + env=env).stdout + return _run + + +def run_native(lang, case_dir, workdir, scale): + # python/rust/c 源码不含占位符,规模经环境变量 BENCH_SCALE 传入(编译与规模无关,只运行时读取)。 + src = case_dir / (case_dir.name + EXT[lang]) + if lang == "python": + cmd = [sys.executable, str(src)] + else: + cmd = compile_native(lang, src, workdir) + env = {**os.environ, SCALE_ENV: str(scale)} + + def _run(): + return sh(cmd, timeout=300, env=env).stdout + return _run + + +def sample(runner, repeat, func_sink, key): + """返回 (min_ns|None, input|None)。""" + best, inp = None, None + for _ in range(repeat): + ns, i, f = parse(runner()) + func_sink[key] = f + if i is not None: + inp = i + if ns is not None and (best is None or ns < best): + best = ns + return best, inp + + +def bench_case(case_dir, args, scale): + best, func, case_input = {}, {}, None + for label in args.backends: + key = f"kvlang_{label}" + with tempfile.TemporaryDirectory(prefix=f"bench-kv-{label}-") as tmp: + try: + b, inp = sample( + run_kvlang(case_dir, label, args, Path(tmp), scale), + args.repeat, func, key) + best[key] = b + if inp is not None: + case_input = inp + except (subprocess.TimeoutExpired, RuntimeError) as e: + print(f" ! kvlang/{label} 失败: {str(e).splitlines()[0]}", + file=sys.stderr) + best[key] = None + for lang in NATIVE: + if not (case_dir / (case_dir.name + EXT[lang])).exists(): + best[lang] = None + continue + with tempfile.TemporaryDirectory(prefix=f"bench-{lang}-") as tmp: + try: + b, _ = sample(run_native(lang, case_dir, Path(tmp), scale), + args.repeat, func, lang) + best[lang] = b + except (subprocess.TimeoutExpired, RuntimeError) as e: + print(f" ! {lang} 失败: {str(e).splitlines()[0]}", + file=sys.stderr) + best[lang] = None + present = [func[k] for k in func if best.get(k) is not None] + valid = len(present) >= 2 and all(x == present[0] for x in present) + return best, valid, case_input + + +def fmt_ns(ns): + if ns is None: + return "-" + if ns >= 1e9: + return f"{ns/1e9:.3f}s" + if ns >= 1e6: + return f"{ns/1e6:.3f}ms" + if ns >= 1e3: + return f"{ns/1e3:.3f}µs" + return f"{ns}ns" + + +NS_HDR = ("kv-shm", "kv-fs", "kv-redis", "python", "rust", "c") +COLS = ("kvlang_shm", "kvlang_fs", "kvlang_redis", "python", "rust", "c") + + +def print_header(): + print(f"{'case':<14}{'input':<14}" + "".join(f"{h:>11}" for h in NS_HDR) + + " valid") + + +def print_row(name, inp, best, valid): + cells = "".join(f"{fmt_ns(best.get(c)):>11}" for c in COLS) + print(f"{name:<14}{(inp or '-'):<14}{cells} {valid}") + + +def show_history(): + files = sorted(RESULTS_DIR.glob("results-*.csv")) + if not files: + print("无历史(results/ 无快照)") + return + rows = [] + for fp in files: + with fp.open() as f: + rows.extend(csv.DictReader(f)) + if not rows: + print("results/ 快照为空") + return + w = max(len(r["version"]) for r in rows) + 1 + iw = max([len(r.get("input", "") or "-") for r in rows] + [5]) + 1 + print(f"{'version':<{w}}{'case':<14}{'input':<{iw}}" + + "".join(f"{h:>11}" for h in NS_HDR) + " valid") + for r in rows: + def g(c): + v = r.get(c + "_ns", "") + return fmt_ns(int(v)) if v not in ("", "-") else "-" + cells = "".join(f"{g(c):>11}" for c in COLS) + print(f"{r['version']:<{w}}{r['case']:<14}" + f"{(r.get('input') or '-'):<{iw}}{cells} {r['valid']}") + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("-k", "--filter", default="", help="只跑名字含此串的 case") + ap.add_argument("--repeat", type=int, default=3, help="每列采样次数,取 min") + ap.add_argument("--backends", default="shm,fs,redis", + help="kvlang 后端子集,逗号分隔(shm,fs,redis)") + ap.add_argument("--redis", default="redis://127.0.0.1:6379", + help="redis 后端 DSN") + ap.add_argument("--kvlang-bin", default="/usr/bin/kvlang") + ap.add_argument("--timeout", type=int, default=300) + ap.add_argument("--no-write", action="store_true", help="不写快照") + ap.add_argument("--show", action="store_true", help="汇总历史后退出") + args = ap.parse_args() + + if args.show: + show_history() + return + + args.backends = [b for b in args.backends.split(",") if b in KV_BACKENDS] + if not args.backends: + print("无有效后端", file=sys.stderr) + sys.exit(1) + + version, commit = git_version() + py_ver, rust_ver, c_ver = tool_versions() + cpu = cpu_model() + now = datetime.now(timezone.utc) + ts = now.strftime("%Y-%m-%dT%H:%M:%SZ") + cases = sorted(d for d in CASES_DIR.iterdir() + if d.is_dir() and args.filter in d.name) + if not cases: + print("无匹配 case", file=sys.stderr) + sys.exit(1) + + print(f"version={version} commit={commit} cpu=[{cpu}]\n" + f"python={py_ver} rust={rust_ver} gcc={c_ver} opt=[{NATIVE_OPT}] " + f"backends={','.join(args.backends)} repeat={args.repeat}\n") + print_header() + rows = [] + for cd in cases: + scales = SWEEP.get(cd.name, [1]) + for scale in scales: + best, valid, cinp = bench_case(cd, args, scale) + cinp = cinp or str(scale) + print_row(cd.name, cinp, best, valid) + rows.append({ + "timestamp": ts, "version": version, "commit": commit, + "cpu": cpu, "case": cd.name, "input": cinp, + "samples": args.repeat, + "kvlang_shm_ns": best.get("kvlang_shm") or "", + "kvlang_fs_ns": best.get("kvlang_fs") or "", + "kvlang_redis_ns": best.get("kvlang_redis") or "", + "python_ns": best.get("python") or "", + "rust_ns": best.get("rust") or "", + "c_ns": best.get("c") or "", + "python_ver": py_ver, "rust_ver": rust_ver, "c_ver": c_ver, + "native_opt": NATIVE_OPT, "valid": valid, + }) + + if args.no_write: + return + RESULTS_DIR.mkdir(exist_ok=True) + fname = f"results-{git_tag()}-{now.strftime('%Y%m%dT%H%M%SZ')}.csv" + out_path = RESULTS_DIR / fname + with out_path.open("w", newline="") as f: + wr = csv.DictWriter(f, fieldnames=FIELDS) + wr.writeheader() + wr.writerows(rows) + print(f"\n→ 写入 {len(rows)} 行到 {out_path.relative_to(KVLANG_ROOT)}") + + +if __name__ == "__main__": + main()