From bda28be3e4aec38b93816b0fc8932982086a1d2e Mon Sep 17 00:00:00 2001 From: AmuroEita <1071307515@qq.com> Date: Tue, 22 Sep 2026 10:58:30 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E8=AF=BB=E6=94=B9=E5=86=99=E8=80=97?= =?UTF-8?q?=E6=97=B6=E5=9F=BA=E5=87=86=EF=BC=8C=E4=BB=A5=E5=8F=8A=20kvspac?= =?UTF-8?q?e=C2=B7cp=20/=20cpdir=20=E6=95=99=E7=A8=8B=EF=BC=88#330?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bench/iops 用同一段 a = a + 1,分别量 Rust、Python 和 kvspace 一次读改写要多久。 kv.c 按当前 kvspace 接口写:Get 之后用 WriteInPlace,写不进去再 WriteNewPlace。 另外给已经有的 kvspace·cp、kvspace·cpdir 补了教程。 --- .gitignore | 1 + bench/baseline.md | 31 +++++++++ bench/iops/README.md | 13 ++++ bench/iops/kv.c | 118 +++++++++++++++++++++++++++++++++ bench/iops/loop.kv | 15 +++++ bench/iops/loop.py | 15 +++++ bench/iops/loop.rs | 24 +++++++ bench/iops/loop_2d.kv | 37 +++++++++++ bench/iops/loop_2d.py | 27 ++++++++ bench/iops/loop_array.kv | 41 ++++++++++++ bench/iops/loop_array.py | 29 ++++++++ bench/iops/loop_f64.kv | 16 +++++ bench/iops/loop_f64.py | 11 +++ bench/iops/loop_forin.kv | 22 ++++++ bench/iops/loop_forin.py | 16 +++++ bench/iops/loop_forin_map.kv | 22 ++++++ bench/iops/loop_forin_map.py | 16 +++++ bench/iops/loop_nested.kv | 23 +++++++ bench/iops/loop_nested.py | 16 +++++ bench/iops/loop_odds.kv | 23 +++++++ bench/iops/loop_odds.py | 17 +++++ bench/iops/loop_sieve.kv | 32 +++++++++ bench/iops/loop_sieve.py | 24 +++++++ bench/iops/loop_sum.kv | 18 +++++ bench/iops/loop_sum.py | 13 ++++ bench/iops/loop_trial.kv | 31 +++++++++ bench/iops/loop_trial.py | 23 +++++++ bench/iops/run.sh | 39 +++++++++++ tutorial/13-stdlib/kv/cp.kv | 17 +++++ tutorial/13-stdlib/kv/cpdir.kv | 14 ++++ 30 files changed, 744 insertions(+) create mode 100644 bench/baseline.md create mode 100644 bench/iops/README.md create mode 100644 bench/iops/kv.c create mode 100644 bench/iops/loop.kv create mode 100644 bench/iops/loop.py create mode 100644 bench/iops/loop.rs create mode 100644 bench/iops/loop_2d.kv create mode 100644 bench/iops/loop_2d.py create mode 100644 bench/iops/loop_array.kv create mode 100644 bench/iops/loop_array.py create mode 100644 bench/iops/loop_f64.kv create mode 100644 bench/iops/loop_f64.py create mode 100644 bench/iops/loop_forin.kv create mode 100644 bench/iops/loop_forin.py create mode 100644 bench/iops/loop_forin_map.kv create mode 100644 bench/iops/loop_forin_map.py create mode 100644 bench/iops/loop_nested.kv create mode 100644 bench/iops/loop_nested.py create mode 100644 bench/iops/loop_odds.kv create mode 100644 bench/iops/loop_odds.py create mode 100644 bench/iops/loop_sieve.kv create mode 100644 bench/iops/loop_sieve.py create mode 100644 bench/iops/loop_sum.kv create mode 100644 bench/iops/loop_sum.py create mode 100644 bench/iops/loop_trial.kv create mode 100644 bench/iops/loop_trial.py create mode 100755 bench/iops/run.sh create mode 100644 tutorial/13-stdlib/kv/cp.kv create mode 100644 tutorial/13-stdlib/kv/cpdir.kv diff --git a/.gitignore b/.gitignore index 2e595ac0..bb4440c4 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ build/ *.o *.a *.lib +bench/iops/.run/ # ── IDE / Editor ───────────────────────────────────── .idea/ diff --git a/bench/baseline.md b/bench/baseline.md new file mode 100644 index 00000000..696f9323 --- /dev/null +++ b/bench/baseline.md @@ -0,0 +1,31 @@ +# Baseline (do not treat as an improvement) + +Frozen **before** v0.3 A-line work. Later numbers must use the same commands on the same machine. + +- date: 2026-09-16 +- host: linux x86_64, 96 nproc +- DSN: `shm://` + +## IOPS (#204) `IOPS_N=100000000 ./bench/iops/run.sh` + +| impl | ns/iter | total | +|------|---------|-------| +| Rust `rustc -O` + `black_box` | 1.361 | 0.136 s | +| Python 3 `while a < n: a = a+1` | 49.319 | 4.932 s | +| kvspace Get+decode / +1 / NewInt64+Set | **168.938** | 16.894 s | + +N=1e6 smoke: rust 1.907 / python 47.079 / kvspace 172.857 ns/iter. + +Issue #204 quoted 695.8 ns/iter on another machine and older shm; that figure is **not** this baseline. + +## prime_sieve(200) `python3 tutorial/test.py --no-build --bench --kvspace shm://…` + +| impl | ms | +|------|----| +| kvlang `bin/kvlang` | **6178.832** | +| Python | 0.150 | +| C `-O3` | 0.034 | + +kvlang / Python ≈ 41200×. Issue #194 quoted 33.37 s kvlang on another machine; that figure is **not** this baseline. + +PC remains a kvspace path (`·pc`); this freeze does not add a process-private PC. diff --git a/bench/iops/README.md b/bench/iops/README.md new file mode 100644 index 00000000..39782866 --- /dev/null +++ b/bench/iops/README.md @@ -0,0 +1,13 @@ +# IOPS floor (#204) + +Same `a = a + 1` loop, three implementations, shm kvspace for the C path. + +``` +IOPS_N=1000000 ./bench/iops/run.sh # default +IOPS_N=100000000 ./bench/iops/run.sh # issue-sized +``` + +kvspace path is Get → DecodeHead → int64 +1 → NewInt64 → WriteInPlace(同长)/ WriteNewPlace on key `/a`. +That is the KV round-trip floor, not the kvlang interpreter. + +Frozen before numbers: `bench/baseline.md`. diff --git a/bench/iops/kv.c b/bench/iops/kv.c new file mode 100644 index 00000000..d46834fc --- /dev/null +++ b/bench/iops/kv.c @@ -0,0 +1,118 @@ +/* #204 floor: shm Get+decode / +1 / NewInt64+Set. Same loop as kvlang a=a+1. + * v0.2.18 无 kvspaceSet / kvspaceBytesFree:Get 是借用(不得 free);Set 等价于 + * DecodeHead 后 WriteInPlace,失败再 WriteNewPlace,把 NewInt64 的 body 拷进去。 */ +#include "kvspace/kvspace.h" +#include +#include +#include +#include +#include +#include + +static int64_t rd64(const uint8_t *p) { + uint64_t u = (uint64_t)p[0] | ((uint64_t)p[1] << 8) | ((uint64_t)p[2] << 16) | + ((uint64_t)p[3] << 24) | ((uint64_t)p[4] << 32) | + ((uint64_t)p[5] << 40) | ((uint64_t)p[6] << 48) | + ((uint64_t)p[7] << 56); + return (int64_t)u; +} + +static uint64_t now_ns(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec; +} + +/* NewInt64 产出的完整 TLV → 后端。codec 缓冲由调用方 free。 */ +static int set_tlv(void *h, const char *key, uint8_t *tlv, uint32_t tlen, + char *err, uint32_t err_cap) { + kvspaceHead_t hd; + uint8_t *dst = NULL; + uint32_t body_len; + if (kvspaceDecodeHead(tlv, tlen, &hd) != 0) + return 1; + body_len = hd.body_len < 0 ? 0 : (uint32_t)hd.body_len; + if (kvspaceWriteInPlace(h, key, 0, body_len, &dst, err, err_cap) != 0) { + if (kvspaceWriteNewPlace(h, key, hd.ref, hd.storetype, hd.ro, hd.vid, + hd.langtype, body_len, &dst, err, err_cap) != 0) + return 1; + } + if (body_len && dst) + memcpy(dst, tlv + hd.body_offset, body_len); + return 0; +} + +int main(void) { + const char *dsn = getenv("KVSPACE"); + if (!dsn || !dsn[0]) + dsn = "shm:///tmp/kvlang_iops.shm"; + int64_t n = 1000000; + const char *ns = getenv("IOPS_N"); + if (ns && ns[0]) + n = strtoll(ns, NULL, 10); + + void *h = kvspaceConnect(dsn); + if (!h) { + fprintf(stderr, "kvspaceConnect failed: %s\n", dsn); + return 1; + } + + uint8_t *tlv = NULL; + uint32_t tlen = 0; + if (kvspaceNewInt64(0, &tlv, &tlen) != 0 || !tlv) { + fprintf(stderr, "NewInt64 0 failed\n"); + return 1; + } + const char *key = "/a"; + char err[128] = {0}; + if (set_tlv(h, key, tlv, tlen, err, sizeof err) != 0) { + fprintf(stderr, "seed set: %s\n", err); + return 1; + } + free(tlv); + + uint64_t t0 = now_ns(); + int64_t a = 0; + for (;;) { + uint8_t *d = NULL; + uint32_t len = 0; + if (kvspaceGet(h, key, 0, &d, &len) != 0 || !d) { + fprintf(stderr, "get failed\n"); + return 1; + } + kvspaceHead_t hd; + memset(&hd, 0, sizeof hd); + if (kvspaceDecodeHead(d, len, &hd) != 0 || hd.body_len < 8 || + hd.body_offset + 8 > (int32_t)len) { + fprintf(stderr, "decode failed len=%u\n", len); + kvspaceReadReset(h); + return 1; + } + a = rd64(d + hd.body_offset); + kvspaceReadReset(h); + if (a >= n) + break; + a += 1; + tlv = NULL; + tlen = 0; + if (kvspaceNewInt64(a, &tlv, &tlen) != 0) { + fprintf(stderr, "NewInt64 failed\n"); + return 1; + } + if (set_tlv(h, key, tlv, tlen, err, sizeof err) != 0) { + fprintf(stderr, "set: %s\n", err); + free(tlv); + return 1; + } + free(tlv); + } + uint64_t elapsed = now_ns() - t0; + kvspaceClose(h); + if (a != n) { + fprintf(stderr, "kv: a=%" PRId64 " want %" PRId64 "\n", a, n); + return 1; + } + printf("kvspace-c n=%" PRId64 " ns=%" PRIu64 " ns/iter=%.3f a=%" PRId64 "\n", + n, elapsed, (double)elapsed / (double)n, a); + return 0; +} diff --git a/bench/iops/loop.kv b/bench/iops/loop.kv new file mode 100644 index 00000000..1f185d68 --- /dev/null +++ b/bench/iops/loop.kv @@ -0,0 +1,15 @@ +// Same a=a+1 loop as loop.py. N from the inner literal (keep in sync with IOPS_N when running). +rwfunc test() -> () { + 1000000 -> n + 0 -> a + t0 <- time·now() + while (a < n) { + a = a + 1 + } + t1 <- time·now() + delta <- time·sub(t1, t0) + ns <- time/duration·as_nanos(delta) + println("kvlang n=", n) + println("ns=", ns) + println("a=", a) +} diff --git a/bench/iops/loop.py b/bench/iops/loop.py new file mode 100644 index 00000000..d7b9faea --- /dev/null +++ b/bench/iops/loop.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +"""#204 floor: native while a=a+1. IOPS_N default 1e6 (CI); 1e8 for the issue number.""" +import os +import time + +n = int(os.environ.get("IOPS_N", "1000000")) +a = 0 +t0 = time.perf_counter_ns() +while a < n: + a = a + 1 +t1 = time.perf_counter_ns() +if a != n: + raise SystemExit(f"python: a={a} want {n}") +ns = t1 - t0 +print(f"python n={n} ns={ns} ns/iter={ns / n:.3f} a={a}") diff --git a/bench/iops/loop.rs b/bench/iops/loop.rs new file mode 100644 index 00000000..876cca75 --- /dev/null +++ b/bench/iops/loop.rs @@ -0,0 +1,24 @@ +// #204 floor: rustc -O while black_box(a) < black_box(n) { a = black_box(a)+1 } +use std::hint::black_box; +use std::time::Instant; + +fn main() { + let n: i64 = std::env::var("IOPS_N") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(1_000_000); + let mut a: i64 = 0; + let t0 = Instant::now(); + while black_box(a) < black_box(n) { + a = black_box(a) + 1; + } + let ns = t0.elapsed().as_nanos(); + if a != n { + eprintln!("rust: a={a} want {n}"); + std::process::exit(1); + } + println!( + "rust n={n} ns={ns} ns/iter={:.3} a={a}", + ns as f64 / n as f64 + ); +} diff --git a/bench/iops/loop_2d.kv b/bench/iops/loop_2d.kv new file mode 100644 index 00000000..048d21d5 --- /dev/null +++ b/bench/iops/loop_2d.kv @@ -0,0 +1,37 @@ +// 2-d compact ARRAYND: m[i,j]=1 then checksum. Hits xv.set/at nidx=2. +rwfunc test() -> () { + m <- xv·reshape([0, 0, 0, 0, 0, 0, 0, 0], 2, 4) + 20000 -> n + 0 -> k + t0 <- time·now() + while (k < n) { + 0 -> i + while (i < 2) { + 0 -> j + while (j < 4) { + 1 -> m[i, j] + j <- j + 1 + } + i <- i + 1 + } + k <- k + 1 + } + t1 <- time·now() + delta <- time·sub(t1, t0) + ns <- time/duration·as_nanos(delta) + 0 -> acc + 0 -> i + while (i < 2) { + 0 -> j + while (j < 4) { + x <- m[i, j] + acc <- acc + x + j <- j + 1 + } + i <- i + 1 + } + println("shape=arraynd-2d") + println("kvlang-2d n=", n) + println("ns=", ns) + println("acc=", acc) +} diff --git a/bench/iops/loop_2d.py b/bench/iops/loop_2d.py new file mode 100644 index 00000000..b40f4691 --- /dev/null +++ b/bench/iops/loop_2d.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +import time + +n = 20000 +m = [[0, 0, 0, 0], [0, 0, 0, 0]] +t0 = time.perf_counter_ns() +k = 0 +while k < n: + i = 0 + while i < 2: + j = 0 + while j < 4: + m[i][j] = 1 + j = j + 1 + i = i + 1 + k = k + 1 +t1 = time.perf_counter_ns() +acc = 0 +i = 0 +while i < 2: + j = 0 + while j < 4: + acc = acc + m[i][j] + j = j + 1 + i = i + 1 +print("shape=arraynd-2d") +print(f"python-2d n={n} ns={t1 - t0} ns/iter={(t1 - t0) / n:.3f} acc={acc}") diff --git a/bench/iops/loop_array.kv b/bench/iops/loop_array.kv new file mode 100644 index 00000000..0a8a7c9e --- /dev/null +++ b/bench/iops/loop_array.kv @@ -0,0 +1,41 @@ +// 1-d compact ARRAYND: a[j]=0; j+=i (xv.set) plus checksum xv.at. +// L=32 ones; outer repeats so the stride-store is the timed work. +rwfunc test() -> () { + a:[]int64 = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + 32 -> L + 50000 -> n + 0 -> k + t0 <- time·now() + while (k < n) { + 0 -> t + while (t < L) { + 1 -> a[t] + t <- t + 1 + } + 2 -> i + while (i < L) { + i -> j + while (j < L) { + 0 -> a[j] + j <- j + i + } + i <- i + 1 + } + k <- k + 1 + } + t1 <- time·now() + delta <- time·sub(t1, t0) + ns <- time/duration·as_nanos(delta) + 0 -> acc + 0 -> t + while (t < L) { + x <- a[t] + acc <- acc + x + t <- t + 1 + } + println("shape=arraynd-stride") + println("kvlang-array n=", n) + println("ns=", ns) + println("acc=", acc) + println("k=", k) +} diff --git a/bench/iops/loop_array.py b/bench/iops/loop_array.py new file mode 100644 index 00000000..d2230c88 --- /dev/null +++ b/bench/iops/loop_array.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +import time + +L = 32 +n = 50000 +a = [1] * L +k = 0 +t0 = time.perf_counter_ns() +while k < n: + t = 0 + while t < L: + a[t] = 1 + t = t + 1 + i = 2 + while i < L: + j = i + while j < L: + a[j] = 0 + j = j + i + i = i + 1 + k = k + 1 +t1 = time.perf_counter_ns() +acc = 0 +t = 0 +while t < L: + acc = acc + a[t] + t = t + 1 +print("shape=arraynd-stride") +print(f"python-array n={n} ns={t1 - t0} acc={acc} k={k}") diff --git a/bench/iops/loop_f64.kv b/bench/iops/loop_f64.kv new file mode 100644 index 00000000..b75935b0 --- /dev/null +++ b/bench/iops/loop_f64.kv @@ -0,0 +1,16 @@ +// float64 while a = a + 1.0. Hits try_tight_while_f64. +rwfunc test() -> () { + 0.0 -> a + 1000000.0 -> n + t0 <- time·now() + while (a < n) { + a <- a + 1.0 + } + t1 <- time·now() + delta <- time·sub(t1, t0) + ns <- time/duration·as_nanos(delta) + println("shape=float64-inc") + println("kvlang-f64 n=", n) + println("ns=", ns) + println("a=", a) +} diff --git a/bench/iops/loop_f64.py b/bench/iops/loop_f64.py new file mode 100644 index 00000000..e599318d --- /dev/null +++ b/bench/iops/loop_f64.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +import time + +a = 0.0 +n = 1000000.0 +t0 = time.perf_counter_ns() +while a < n: + a = a + 1.0 +t1 = time.perf_counter_ns() +print("shape=float64-inc") +print(f"python-f64 n={n} ns={t1 - t0} ns/iter={(t1 - t0) / n:.3f} a={a}") diff --git a/bench/iops/loop_forin.kv b/bench/iops/loop_forin.kv new file mode 100644 index 00000000..2a63d5b7 --- /dev/null +++ b/bench/iops/loop_forin.kv @@ -0,0 +1,22 @@ +// for-in over compact 1-d. Hits try_tight_forin. 50000×32. +rwfunc test() -> () { + a:[]int64 = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + 50000 -> n + 0 -> k + 0 -> acc + t0 <- time·now() + while (k < n) { + for (x in a) { + acc <- acc + x + } + k <- k + 1 + } + t1 <- time·now() + delta <- time·sub(t1, t0) + ns <- time/duration·as_nanos(delta) + println("shape=forin-1d") + println("kvlang-forin n=", n) + println("ns=", ns) + println("acc=", acc) + println("k=", k) +} diff --git a/bench/iops/loop_forin.py b/bench/iops/loop_forin.py new file mode 100644 index 00000000..b996d926 --- /dev/null +++ b/bench/iops/loop_forin.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +import time + +L = 32 +n = 50000 +a = [1] * L +k = 0 +acc = 0 +t0 = time.perf_counter_ns() +while k < n: + for x in a: + acc = acc + x + k = k + 1 +t1 = time.perf_counter_ns() +print("shape=forin-1d") +print(f"python-forin n={n} ns={t1 - t0} acc={acc} k={k}") diff --git a/bench/iops/loop_forin_map.kv b/bench/iops/loop_forin_map.kv new file mode 100644 index 00000000..65832059 --- /dev/null +++ b/bench/iops/loop_forin_map.kv @@ -0,0 +1,22 @@ +// for-in over stringkeymap scatter. Hits try_tight_forin_map. 50000×32. +rwfunc test() -> () { + a = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} + 50000 -> n + 0 -> k + 0 -> acc + t0 <- time·now() + while (k < n) { + for (x in a) { + acc <- acc + x + } + k <- k + 1 + } + t1 <- time·now() + delta <- time·sub(t1, t0) + ns <- time/duration·as_nanos(delta) + println("shape=forin-map") + println("kvlang-forin-map n=", n) + println("ns=", ns) + println("acc=", acc) + println("k=", k) +} diff --git a/bench/iops/loop_forin_map.py b/bench/iops/loop_forin_map.py new file mode 100644 index 00000000..30706ddf --- /dev/null +++ b/bench/iops/loop_forin_map.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +import time + +L = 32 +n = 50000 +a = [1] * L +k = 0 +acc = 0 +t0 = time.perf_counter_ns() +while k < n: + for x in a: + acc = acc + x + k = k + 1 +t1 = time.perf_counter_ns() +print("shape=forin-map") +print(f"python-forin-map n={n} ns={t1 - t0} acc={acc} k={k}") diff --git a/bench/iops/loop_nested.kv b/bench/iops/loop_nested.kv new file mode 100644 index 00000000..3fde2e20 --- /dev/null +++ b/bench/iops/loop_nested.kv @@ -0,0 +1,23 @@ +// Nested integer while, no println in the nest. Outer+inner should specialize as one region. +rwfunc test() -> () { + 400 -> N + 0 -> acc + 0 -> n + t0 <- time·now() + while (n < N) { + 0 -> i + while (i < N) { + acc <- acc + 1 + i <- i + 1 + } + n <- n + 1 + } + t1 <- time·now() + delta <- time·sub(t1, t0) + ns <- time/duration·as_nanos(delta) + println("shape=nested-while") + println("kvlang-nested N=", N) + println("ns=", ns) + println("acc=", acc) + println("n=", n) +} diff --git a/bench/iops/loop_nested.py b/bench/iops/loop_nested.py new file mode 100644 index 00000000..a1720b64 --- /dev/null +++ b/bench/iops/loop_nested.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +import time + +N = 400 +acc = 0 +n = 0 +t0 = time.perf_counter_ns() +while n < N: + i = 0 + while i < N: + acc = acc + 1 + i = i + 1 + n = n + 1 +t1 = time.perf_counter_ns() +print("shape=nested-while") +print(f"python-nested N={N} ns={t1 - t0} ns/iter={(t1 - t0) / (N * N):.3f} acc={acc} n={n}") diff --git a/bench/iops/loop_odds.kv b/bench/iops/loop_odds.kv new file mode 100644 index 00000000..88fc6ee3 --- /dev/null +++ b/bench/iops/loop_odds.kv @@ -0,0 +1,23 @@ +// rem/eq/inner-if: sum odds. Hits tight-loop specialization (not add-only). +rwfunc test() -> () { + 1000000 -> n + 0 -> total + 1 -> i + t0 <- time·now() + while (i <= n) { + rem = i % 2 + rem == 1 -> is_odd + if (is_odd) { + total <- total + i + } + i = i + 1 + } + t1 <- time·now() + delta <- time·sub(t1, t0) + ns <- time/duration·as_nanos(delta) + println("shape=rem/eq/if") + println("kvlang-odds n=", n) + println("ns=", ns) + println("total=", total) + println("i=", i) +} diff --git a/bench/iops/loop_odds.py b/bench/iops/loop_odds.py new file mode 100644 index 00000000..1c0b0688 --- /dev/null +++ b/bench/iops/loop_odds.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +import os +import time + +n = int(os.environ.get("IOPS_N", "1000000")) +total = 0 +i = 1 +t0 = time.perf_counter_ns() +while i <= n: + rem = i % 2 + is_odd = rem == 1 + if is_odd: + total = total + i + i = i + 1 +t1 = time.perf_counter_ns() +print("shape=rem/eq/if") +print(f"python-odds n={n} ns={t1 - t0} ns/iter={(t1 - t0) / n:.3f} total={total} i={i}") diff --git a/bench/iops/loop_sieve.kv b/bench/iops/loop_sieve.kv new file mode 100644 index 00000000..642a07df --- /dev/null +++ b/bench/iops/loop_sieve.kv @@ -0,0 +1,32 @@ +// Tutorial sieve algorithm, no println in the nest. Count primes <= 200. +rwfunc test() -> () { + 200 -> limit + 0 -> count + 2 -> n + t0 <- time·now() + while (n <= limit) { + is_prime <- true + 2 -> d + while (d < n) { + n % d -> rem + divisible <- rem == 0 + if (divisible) { + is_prime = false + break + } else { + d <- d + 1 + } + } + if (is_prime) { + count = count + 1 + } + n + 1 -> n + } + t1 <- time·now() + delta <- time·sub(t1, t0) + ns <- time/duration·as_nanos(delta) + println("shape=sieve-noprint") + println("kvlang-sieve limit=", limit) + println("ns=", ns) + println("count=", count) +} diff --git a/bench/iops/loop_sieve.py b/bench/iops/loop_sieve.py new file mode 100644 index 00000000..88d30ef6 --- /dev/null +++ b/bench/iops/loop_sieve.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +import time + +limit = 200 +count = 0 +n = 2 +t0 = time.perf_counter_ns() +while n <= limit: + is_prime = True + d = 2 + while d < n: + rem = n % d + divisible = rem == 0 + if divisible: + is_prime = False + break + else: + d = d + 1 + if is_prime: + count = count + 1 + n = n + 1 +t1 = time.perf_counter_ns() +print("shape=sieve-noprint") +print(f"python-sieve limit={limit} ns={t1 - t0} count={count}") diff --git a/bench/iops/loop_sum.kv b/bench/iops/loop_sum.kv new file mode 100644 index 00000000..06a2bb69 --- /dev/null +++ b/bench/iops/loop_sum.kv @@ -0,0 +1,18 @@ +// while (i <= n) { acc += i; i += 1 } — integer small-loop specialization +rwfunc test() -> () { + 1000000 -> n + 0 -> acc + 1 -> i + t0 <- time·now() + while (i <= n) { + acc <- acc + i + i <- i + 1 + } + t1 <- time·now() + delta <- time·sub(t1, t0) + ns <- time/duration·as_nanos(delta) + println("kvlang-sum n=", n) + println("ns=", ns) + println("acc=", acc) + println("i=", i) +} diff --git a/bench/iops/loop_sum.py b/bench/iops/loop_sum.py new file mode 100644 index 00000000..5420043e --- /dev/null +++ b/bench/iops/loop_sum.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +import os +import time + +n = int(os.environ.get("IOPS_N", "1000000")) +acc = 0 +i = 1 +t0 = time.perf_counter_ns() +while i <= n: + acc = acc + i + i = i + 1 +t1 = time.perf_counter_ns() +print(f"python-sum n={n} ns={t1 - t0} ns/iter={(t1 - t0) / n:.3f} acc={acc} i={i}") diff --git a/bench/iops/loop_trial.kv b/bench/iops/loop_trial.kv new file mode 100644 index 00000000..1c56a690 --- /dev/null +++ b/bench/iops/loop_trial.kv @@ -0,0 +1,31 @@ +// Nested trial division, no println in the nest. Count primes <= limit. +rwfunc test() -> () { + 500 -> limit + 0 -> np + 2 -> n + t0 <- time·now() + while (n <= limit) { + is_prime <- true + 2 -> d + while (d < n) { + n % d -> rem + if (rem == 0) { + is_prime = false + break + } else { + d <- d + 1 + } + } + if (is_prime) { + np <- np + 1 + } + n + 1 -> n + } + t1 <- time·now() + delta <- time·sub(t1, t0) + ns <- time/duration·as_nanos(delta) + println("shape=nested-trial") + println("kvlang-trial limit=", limit) + println("ns=", ns) + println("np=", np) +} diff --git a/bench/iops/loop_trial.py b/bench/iops/loop_trial.py new file mode 100644 index 00000000..431b081b --- /dev/null +++ b/bench/iops/loop_trial.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +import time + +limit = 500 +np = 0 +n = 2 +t0 = time.perf_counter_ns() +while n <= limit: + is_prime = True + d = 2 + while d < n: + rem = n % d + if rem == 0: + is_prime = False + break + else: + d = d + 1 + if is_prime: + np = np + 1 + n = n + 1 +t1 = time.perf_counter_ns() +print("shape=nested-trial") +print(f"python-trial limit={limit} ns={t1 - t0} ns/iter={(t1 - t0) / max(limit, 1):.3f} np={np}") diff --git a/bench/iops/run.sh b/bench/iops/run.sh new file mode 100755 index 00000000..3d64573d --- /dev/null +++ b/bench/iops/run.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# IOPS floor (#204): Rust / Python / kvspace Get+Set, same a=a+1 loop. +# IOPS_N=1000000 ./bench/iops/run.sh # default (CI-sized) +# IOPS_N=100000000 ./bench/iops/run.sh # issue-sized (slow) +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +if [[ -z "${DEPS:-}" ]]; then + if [[ -d "$ROOT/.prefix" ]]; then + DEPS="$ROOT/.prefix" + else + DEPS="$ROOT/.deps" + fi +fi +HERE="$(cd "$(dirname "$0")" && pwd)" +OUT="$HERE/.run" +mkdir -p "$OUT" + +export IOPS_N="${IOPS_N:-1000000}" +if [[ -z "${KVSPACE_BACKEND_PATH:-}" && -d "$DEPS/lib/kvspace" ]]; then + export KVSPACE_BACKEND_PATH="$DEPS/lib/kvspace" +fi +LIBS="$DEPS/lib" +if [[ -n "${KVSPACE_BACKEND_PATH:-}" ]]; then + LIBS="$LIBS:$KVSPACE_BACKEND_PATH" +fi +export LD_LIBRARY_PATH="$LIBS:$ROOT/bin${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" +if [[ -z "${KVSPACE:-}" ]]; then + export KVSPACE="shm://${OUT}/kv.shm" +fi +rm -f "${OUT}/kv.shm" "${OUT}/kv.shm.sbo.head" "${OUT}/kv.shm.sbo.data" +echo "IOPS_N=$IOPS_N KVSPACE=$KVSPACE" + +rustc -O -o "$OUT/loop-rust" "$HERE/loop.rs" +cc -O2 -o "$OUT/loop-kv" "$HERE/kv.c" \ + -I "$DEPS/include" -L "$DEPS/lib" -lkvspace -ldl + +"$OUT/loop-rust" +python3 "$HERE/loop.py" +"$OUT/loop-kv" diff --git a/tutorial/13-stdlib/kv/cp.kv b/tutorial/13-stdlib/kv/cp.kv new file mode 100644 index 00000000..75ebb030 --- /dev/null +++ b/tutorial/13-stdlib/kv/cp.kv @@ -0,0 +1,17 @@ +// kvspace·cp:拷贝单 key,之后改 src 不影响 dst +// 期望输出: +// dst: 10 +// src after: 20 +// dst after: 10 + +rwfunc test() -> () { + kvspace·set("/cp_src", 10) + kvspace·cp("/cp_src", "/cp_dst") + d1 = kvspace·get("/cp_dst") + println("dst:", d1) + kvspace·set("/cp_src", 20) + s2 = kvspace·get("/cp_src") + d2 = kvspace·get("/cp_dst") + println("src after:", s2) + println("dst after:", d2) +} diff --git a/tutorial/13-stdlib/kv/cpdir.kv b/tutorial/13-stdlib/kv/cpdir.kv new file mode 100644 index 00000000..f97f9758 --- /dev/null +++ b/tutorial/13-stdlib/kv/cpdir.kv @@ -0,0 +1,14 @@ +// kvspace·cpdir:拷贝 key 子树,改 src 子节点不影响 dst +// 期望输出: +// child: 1 +// after: 1 + +rwfunc test() -> () { + kvspace·set("/cptree/a", 1) + kvspace·cpdir("/cptree", "/cptree2") + v = kvspace·get("/cptree2/a") + println("child:", v) + kvspace·set("/cptree/a", 9) + v2 = kvspace·get("/cptree2/a") + println("after:", v2) +}