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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ build/
*.o
*.a
*.lib
bench/iops/.run/

# ── IDE / Editor ─────────────────────────────────────
.idea/
Expand Down
31 changes: 31 additions & 0 deletions bench/baseline.md
Original file line number Diff line number Diff line change
@@ -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.
13 changes: 13 additions & 0 deletions bench/iops/README.md
Original file line number Diff line number Diff line change
@@ -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`.
118 changes: 118 additions & 0 deletions bench/iops/kv.c
Original file line number Diff line number Diff line change
@@ -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 <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

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;
}
15 changes: 15 additions & 0 deletions bench/iops/loop.kv
Original file line number Diff line number Diff line change
@@ -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)
}
15 changes: 15 additions & 0 deletions bench/iops/loop.py
Original file line number Diff line number Diff line change
@@ -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}")
24 changes: 24 additions & 0 deletions bench/iops/loop.rs
Original file line number Diff line number Diff line change
@@ -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
);
}
37 changes: 37 additions & 0 deletions bench/iops/loop_2d.kv
Original file line number Diff line number Diff line change
@@ -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)
}
27 changes: 27 additions & 0 deletions bench/iops/loop_2d.py
Original file line number Diff line number Diff line change
@@ -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}")
41 changes: 41 additions & 0 deletions bench/iops/loop_array.kv
Original file line number Diff line number Diff line change
@@ -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)
}
29 changes: 29 additions & 0 deletions bench/iops/loop_array.py
Original file line number Diff line number Diff line change
@@ -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}")
16 changes: 16 additions & 0 deletions bench/iops/loop_f64.kv
Original file line number Diff line number Diff line change
@@ -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)
}
11 changes: 11 additions & 0 deletions bench/iops/loop_f64.py
Original file line number Diff line number Diff line change
@@ -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}")
Loading
Loading