diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index d924fdc..4c7400c 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -4,6 +4,36 @@ > `**Status:**`/`**Confidence:**` line. A correction gets its own new, > dated entry that references the one it corrects — the storno rule. +## 2026-08-17 — E-LGJ-SIMD-SOA-IS-FOR-THE-ROW-STORE-NOT-THE-FLAT-LANES-1 + +**Status:** DECISION (declined refactor, with the trigger for revisiting named). +**Confidence:** High — decided by reading `ndarray/src/simd_soa.rs`'s full API, not by taste. + +Operator suggestion: "if you use SoA, calling simd_soa.rs would make sense" — should +`native/lgj-abi/src/kernels.rs` route through `ndarray::simd_soa::MultiLaneColumn` (the canonical +`Arc<[u8]>` SoA carrier) instead of raw `&[u32]`/`&[i32]` slices? **Answer: not for today's +flat-lane fixture; yes for the future 512-byte row-store slice.** Two concrete API mismatches, +not a style call: + +1. **No tail handling.** `MultiLaneColumn::new()` hard-requires `len % 64 == 0`; every `iter_*` + yields only full 64-byte chunks via `as_chunks::<64>()` — no remainder arm. The + `simd_int_ops` primitives this project consumes do the opposite by design: full 16-lane + groups + a scalar tail for arbitrary caller-chosen `n_rows`. Wrapping the fixture's lanes in + `MultiLaneColumn` would force 64-byte padding on every allocation, bought for nothing. +2. **No `u32` lane.** `MultiLaneColumn` ships u8x64/f32x16/f64x8/u64x8/i32x16/i64x8 iterators — + no u32. The fixture's `ids`/`classes` are `u32` (`eq_u32_to_mask`). + +So `kernels.rs` already calls the correct layer: the `ndarray::simd_int_ops` primitives own their +chunking internally. `MultiLaneColumn` sits *above* that layer, for uniform pre-padded columns. + +**Where it DOES fit — the operator-stated layout reference (recorded verbatim so it survives):** +"the 64k x 512 bytes SoA layout is enforced everywhere in lance-graph (32 Lanes each 4 bytes +classview+12 bytes). For Java the layout might differ — just for reference." A 512-byte, +64-byte-aligned row store (32 × 16-byte V3 facets) is padded/aligned *by construction* — no tail +problem — and each row is a natural `iter_u8x64` chunk-of-chunks. When the real +`NodeRow`/facet slice replaces the generic fixture (`docs/abi.md` §10, `docs/architecture.md` +"where a real graph slice would attach"), `MultiLaneColumn` is the type to reach for. Not before. + ## 2026-08-17 — E-LGJ-VECTOR-API-BEATS-THE-CROSSING-1 **Status:** FINDING. **Confidence:** High (real JMH 1.37, `Data.crossCheck()` guards every fork, diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index 06b6fe5..66f2e2e 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -1,3 +1,27 @@ +## 2026-08-17 (later) — Phase I docs written, fusion re-run merged, simd_soa question answered (PR #4) + +- **All four synthesis docs shipped** (`docs/architecture.md`, + `docs/panama.md`, `docs/valhalla-lab.md`, `docs/execution-boundary.md`) + — D-LGJ-I DONE. Each cites the proving artifact instead of restating it. +- **Fusion sweep re-run with a 256-row arm** (`./run.sh E_`): the first + pass's "fusion does nothing" (true at 65,536 rows, where kernel time + dominates) is false at small rows — unfused/fused grows 0.95× → 2.99× + at 256 rows × 8 predicates, because per-crossing overhead dominates + there. `RESULTS.md` rewritten from `jmh-results-merged.csv` (A/B/C from + the full sweep + E from the re-run), `TABLES.md` mechanically generated + from the same file. Valhalla lab result files refreshed by a same-box + re-run; findings unchanged. +- **`MultiLaneColumn` question answered** (operator: "if you use SoA, + calling simd_soa.rs would make sense"): declined for the flat-lane + fixture (64-byte-multiple constraint + no u32 lane — two concrete API + mismatches), earmarked for the 512-byte row-store slice where it fits + by construction. Operator layout reference recorded: 64K × 512 B rows, + 32 lanes × (4 B classid + 12 B), enforced everywhere in lance-graph; + Java-side layout may differ. See + `E-LGJ-SIMD-SOA-IS-FOR-THE-ROW-STORE-NOT-THE-FLAT-LANES-1`. +- **PR_ARC_INVENTORY backfilled** for merged PRs #1-#3 (hygiene lapse + owned in the file itself). + ## 2026-08-17 — D-LGJ-AUDIT complete, core vertical slice VERIFIED GREEN, PR #1 opened ### Current Contract Inventory — the vertical slice is real and green diff --git a/.claude/board/PR_ARC_INVENTORY.md b/.claude/board/PR_ARC_INVENTORY.md index 3f5a879..eacf076 100644 --- a/.claude/board/PR_ARC_INVENTORY.md +++ b/.claude/board/PR_ARC_INVENTORY.md @@ -3,14 +3,56 @@ # updatable in place — corrections append as new dated lines; reversals # get their own PR entry) -_No PR has been opened against this repository yet — session 1, 2026-08-17._ +> **Hygiene lapse, owned (2026-08-17):** PRs #1-#3 merged without their +> entries landing in the same commit — the exact retroactive-hygiene +> anti-pattern the imported board rules name. Backfilled below in one +> pass rather than left stale; PR #4 onward gets its entry at merge time. -The first entry in this file will be written when the first PR against -`lance-graph-java` merges (expected: the vertical slice on -`claude/lance-graph-java-panama-valhalla-sus9w8` → `main`, once -`ISS-LGJ-FANOUT-UNREVIEWED` closes). Until then, ground truth for -in-progress work lives on `LATEST_STATE.md` (current contract inventory), -`STATUS_BOARD.md` (per-D-id status), and `AGENT_LOG.md` (what actually -happened) — this file stays empty rather than backfilled with a -pre-registration entry that would misrepresent something as merged before -it is. +## PR #3 — Vector API bench: real JMH, cross-checked (merged 2026-08-17, squash) + +- **Added:** `bench/` — real JMH 1.37 suite (Components A/B/C/E: + downcall overhead, segment access, execution boundary sweep 64→4.2M + rows, fusion/planning), `Data.crossCheck()` gating every fork, + `summarise.sh` mechanical table generator, `RESULTS.md`, raw + run logs + CSV. +- **Locked:** the headline finding — Java Vector API zero-copy on the + native segment beats the native crossing at every row count tested + (56.4× → 1.33×); native beats Java *scalar* only past ~4K-16K rows. + Recorded as `E-LGJ-VECTOR-API-BEATS-THE-CROSSING-1`. +- **Deferred:** fusion sweep ran at 65,536 rows only (repaid post-merge + by the E_ re-run with a 256-row arm — see PR #4). +- **Docs:** `bench/README.md`, board updates. +- **Confidence:** High — 50/50 rows, 0 failures, two independent + computations of the same CSV agree. + +## PR #2 — Valhalla lab: three-truths, causal isolation, 3 reproducers (merged 2026-08-17, squash) + +- **Added:** `valhalla-lab/` — shared/stable/valhalla trees, self-verifying + `run.sh` (vocab-diff honesty gate + flattening-flag causal isolation), + `docs/three-truths.md`, reproducers R1/R2/R3 with observed outputs. +- **Locked:** the 8-byte array-flattening cliff (R2, VM-confirmed); + native-one-crossing beats hydration ~38-57× on BOTH JDKs; production + API adopts zero Valhalla-only mechanisms — migration stays + `record` → `value record`, one word per type. +- **Deferred:** nothing; the lab is complete for this vocabulary. +- **Docs:** lab README + three-truths; board updates. +- **Confidence:** High — one real defect (`Class::isValue()` not on + JDK 26) found by compile failure and fixed before landing. + +## PR #1 — Core vertical slice: ABI contract, native crate, Java facade (merged 2026-08-17, squash) + +- **Added:** `docs/abi.md` (normative, 14 symbols / 4 repr(C) types / + 13 status codes / generation-checked handles); `native/lgj-abi` + (72/72, clippy/fmt clean, 14/14 exported symbols via `nm -D`); + `java/` facade + FFM membrane (132/132, reflection-enforced zero-FFM + public surface); 5 new `ndarray::simd` primitives under the W1a + contract (41/41); the `.claude/` ensemble + board. +- **Locked:** disable-verified generation check (exactly 2 tests red + when broken, 70 green); the manifest cross-check rejects a real wrong + `.so`; laziness measured (0 crossings to build, exactly 1 to + evaluate); target-cpu=x86-64-v4 divergence recorded. +- **Deferred:** real graph types (`NodeRow`/`WideFieldMask`) — generic + fixture first, by design (`docs/abi.md` §10). +- **Docs:** `docs/abi.md`, knowledge docs, board. +- **Confidence:** High — one real audit violation (`ndarray::hpc` + import) found and fixed pre-merge; recorded in EPIPHANIES. diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 12ba44e..0f6883e 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -16,7 +16,7 @@ list. | D-LGJ-F | Valhalla lab — three-truths method on the small semantic value vocabulary | **DONE 2026-08-17** — `valhalla-lab/`: 4 experiments + a self-verifying `run.sh` (mechanically diffs the two `Vocab.java`s modulo the `value` keyword before trusting the A/B) + 3 causal-isolation runs (escape-analysis off; `UseArrayFlattening`/`UseFieldFlattening` toggled independently). 3 real Valhalla limitations reproduced and filed under `reproducers/` (R1: `@NullRestricted` field on an identity class is a `VerifyError`, javac's fault — no source form expresses required strict-field order; **R2: array flattening has a hard 8-byte payload cliff, VM-confirmed via `-XX:+PrintFlatArrayLayout`** — `LaneId`/`Ordinal`/`MaskId` (≤8B) flatten, `RowRange`/`Row` (16B) do not, so "Valhalla helps descriptors not entities" is a measured VM cutoff, not a hand-wave, and `RowRange` landing on the wrong side is flagged as the one place the expectation was too optimistic; R3: the densest null-restricted array form is `jdk.internal`-only and generics erase flattening entirely — `Foo!` type syntax confirmed NOT to parse, matching the archaeology finding). 1 real defect found + fixed before landing (see `EPIPHANIES.md`). None of the three limitations changed the production API — the migration path stays exactly `record` → `value record` | I | | D-LGJ-G | Java Vector API comparative bench vs Panama→`ndarray::simd` | **DONE 2026-08-17** — real JMH 1.37 (fork+warmup+blackholes confirmed in the log), 50/50 rows, 0 failures, `Data.crossCheck()` guards every fork. **Headline (Component C, single predicate, zero-copy `IntVector.fromMemorySegment`): the Java Vector API beats the native crossing at EVERY row count tested, 64 to 4,194,304** — 56.4x at small sizes down to 1.3-1.4x at the largest. Native beats a plain Java scalar loop only past ~4,096-16,384 rows. Component E: SIMD-vs-scalar is the biggest lever measured (10.8x-31.1x); fused vs unfused are within noise of each other at 65,536 rows (crossing-count guarantee matters more than measured time here, since Component A puts one downcall at ~22ns). Independently cross-checked: hand-written `RESULTS.md` numbers verified byte-for-byte against `summarise.sh`'s mechanically-generated tables from the same CSV | I | | D-LGJ-H | Falsification: handle lifecycle (adversarial), SIMD/scalar parity, Java/native parity | **DONE 2026-08-17, all scopes closed** — Rust+Java core (D-LGJ-C disable-verification, D-LGJ-E `FusionParityTest`/`LifetimeTest`); Valhalla lab (`run.sh`'s vocab-honesty self-check + causal-isolation runs); bench (`Data.crossCheck()` on every fork, `summarise.sh` cross-check) | I | -| D-LGJ-I | Docs: `architecture.md`, `panama.md`, `valhalla-lab.md`, `execution-boundary.md` | **Unblocked** — F and G both landed; next action | — | +| D-LGJ-I | Docs: `architecture.md`, `panama.md`, `valhalla-lab.md`, `execution-boundary.md` | **DONE 2026-08-17** — all four written as synthesis (each cites the artifact that proves its claim rather than restating it); `execution-boundary.md` additionally records the three structural hot-path facts (zero-copy precision incl. the lance-graph `SoaEnvelope` inheritance, no-thread-pool/caller-threads-are-the-parallelism, `array_windows`/`array_chunks` precisely traced as un-invoked at any input size). Ships in PR #4 with the fusion-sweep 256-row re-run merged into `RESULTS.md`/`TABLES.md` | — | | D-LGJ-AUDIT | Mechanical post-fan-out audit: `grep` for `ndarray::hpc` imports, any `.h`/`cbindgen`/`jextract` artifact, any FFM type leaking into public Java API | **DONE 2026-08-17** — 1 real violation found (`kernels.rs::simd_popcount` used the internal `ndarray::hpc::bitwise` path), fixed in place; everything else confirmed to be the one sanctioned exception or explanatory prose | closed D-LGJ-C/D/E for the core | ### Reading this table diff --git a/bench/RESULTS.md b/bench/RESULTS.md index ce2f6ce..5f67dad 100644 --- a/bench/RESULTS.md +++ b/bench/RESULTS.md @@ -1,127 +1,279 @@ -# Where does execution belong? — measured, not assumed - -Real JMH 1.37, `--enable-preview` off (Vector API only needs `--add-modules jdk.incubator.vector`, -not preview), JDK 26 GA, `@Fork(1) @Warmup(5×500ms) @Measurement(8×500ms)`, `AverageTime`. Full run: -`results/jmh-run.txt` (1,679 lines, every warm-up iteration). Machine-readable: -`results/jmh-results.csv`. Reproduce with `./run.sh` (~12 min on 4 vCPU — this run: 00:12:22). - -**Gate.** 50/50 benchmark rows completed, 0 failures. `Data.crossCheck()` (native vs Vector vs -scalar agree on both count and sum) ran in `@Setup` for every fork and never threw — the three -kernels compute the same answer, so a speed comparison between them is meaningful rather than a -race between a correct implementation and a subtly wrong faster one. - -## The headline finding — and it complicates the thesis in an honest way - -Component C (`java_scalarLoop` / `java_vectorApi` / `native_fusedPlan`) sweeps one predicate -(`class == 7`) over row counts from 64 to 4,194,304, all three arms answering the identical -question from the identical native lane: - -| rows | scalar (µs) | vectorApi (µs) | native (µs) | native beats scalar by | **vectorApi beats native by** | -|---:|---:|---:|---:|---:|---:| -| 64 | 0.028 | 0.011 | 0.612 | 0.05× (native LOSES) | 56.40× | -| 256 | 0.085 | 0.025 | 0.623 | 0.14× (native LOSES) | 24.96× | -| 1,024 | 0.338 | 0.078 | 0.708 | 0.48× (native LOSES) | 9.07× | -| 4,096 | 1.252 | 0.385 | 1.524 | 0.82× (native LOSES) | 3.96× | -| 16,384 | 5.553 | 1.744 | 4.291 | **1.29×** | 2.46× | -| 65,536 | 42.846 | 8.027 | 15.324 | 2.80× | 1.91× | -| 262,144 | 343.389 | 42.519 | 69.374 | 4.95× | 1.63× | -| 1,048,576 | 1,623.313 | 310.405 | 411.333 | 3.95× | 1.33× | -| 4,194,304 | 6,602.036 | 1,319.107 | 1,858.686 | 3.55× | 1.41× | - -Two crossovers, both real: - -1. **Native beats a plain Java scalar loop only past roughly 4,096–16,384 rows.** Below that, the - crossing overhead (the ~0.6 µs floor visible at row=64, consistent with Component A's raw - downcall cost) is not repaid yet — a scalar loop over a few thousand elements is simply cheap - enough in Java that there is nothing to win by leaving the JVM. -2. **The Java Vector API, reading the SAME native `MemorySegment` with zero copy - (`IntVector.fromMemorySegment`), beats the native crossing at every single row count - tested** — never below 1.3×, and by more than an order of magnitude at small sizes. This - is the finding this project's own mission brief asked for by name: *"Where is the cheapest - and cleanest execution boundary? Not: how can we maximize the amount of Java code?"* — and - the honest answer, for this one-predicate/one-lane workload, is that it is **not** the Rust - crossing. - -**Why this does not overturn the thesis, and where it does bite.** Component C measures ONE -predicate over ONE lane — exactly the case where a zero-copy Vector kernel has nothing to fuse and -nothing to coordinate. Component E (below) measures what happens once there is more than one -predicate, which is the case the fluent `View` API actually optimizes for. - -## Component E — fusion matters once there is more than one predicate - -`fused` (native, one crossing, N predicates AND-combined in one plan) vs `unfused` (native, N -crossings, one `mask_and` per predicate) vs `fusedScalarKernel` (the SAME fused plan forced through -the crate's own scalar reference path, not SIMD), at 65,536 rows: - -| predicates | fused (µs) | unfused (µs) | fusedScalarKernel (µs) | SIMD speedup over scalar | -|---:|---:|---:|---:|---:| -| 1 | 6.818 | 7.641 | 73.419 | 10.8× | -| 2 | 15.599 | 15.025 | 405.096 | 26.0× | -| 4 | 29.721 | 27.309 | 923.100 | 31.1× | -| 8 | 59.363 | 61.916 | 1,807.261 | 30.4× | - -Two findings, neither of which was assumed going in: - -- **`fused` and `unfused` are close** — within noise of each other at this row count (see the - single-fork caveat below). The `lgj_plan_eval` fused path exists to guarantee ONE crossing - regardless of predicate count (a structural property `LazinessTest` in the Java suite already - proves), not because N separate crossings at 65,536 rows are individually expensive — Component A - already showed a bare downcall costs ~22 ns, so 8 of them add roughly 176 ns against a - multi-microsecond total. The value of fusion at this scale is the crossing-count GUARANTEE, not a - large measured time saving. -- **SIMD vs scalar is the biggest lever in this whole benchmark suite** — 10.8×–31.1×, growing - with predicate count. This is the number that justifies routing every kernel through - `ndarray::simd` rather than a portable scalar loop, and it dwarfs the crossing-cost questions - Components A/B/C spend most of their effort isolating. - -`planConstructionOnly` (0.053–0.634 µs, scaling with predicate count but NOT with row count — 65,536 -rows throughout) confirms `LazinessTest`'s claim under real JMH conditions: building the fluent -chain costs time proportional to the number of `.where()` calls, never to the number of rows. - -## Component A — the floor - -| benchmark | ns/op | -|---|---:| -| `javaCallControl` (a plain Java method call, the noise floor) | 0.497 | -| `bareDowncall_noArgs` | 21.911 | -| `downcall_twoArgs_outPointer` | 117.855 | - -A bare Panama downcall costs ~22 ns over a plain Java call; adding two arguments and an out-pointer -roughly quintuples that. Both numbers are the ~0.6 µs floor Component C's small-row-count native -arm sits on top of (downcall cost + the fixed per-call marshalling `Engine`/`Downcalls` do above the -raw linker). - -## Component B — raw throughput, no crossing - -| benchmark | µs/op (65,536 elements) | -|---|---:| -| `heapArrayBaseline` (data already in a Java `int[]`) | 5.158 | -| `segmentScalar` (same data, read from a native `MemorySegment`, scalar loop) | 5.220 | -| `segmentVector` (same data, `IntVector.fromMemorySegment`) | 3.337 | - -Reading a native segment scalar-wise costs essentially the same as reading a heap array (1.2% -difference — within this harness's own stated ~10% noise floor, see below) — `MemorySegment` access -is not itself a tax. The Vector API is the thing that's actually faster here (1.55× over both), not -the memory's location. - -## Honest limitations (stated in `README.md`, repeated here because they qualify every number above) - -- **Single fork.** `@Fork(1)` cannot see run-to-run JIT/ASLR variance. Differences under ~10% between - arms are not established by this harness — this is why `fused` vs `unfused` above is reported as - "close" rather than a specific winner. -- **Shared container, not a tuned host.** No CPU pinning, no disabled turbo/hyperthreading. Large - effects (the order-of-magnitude ones — scalar-vs-vector, the small-row native crossover) are safe - to read; anything under ~10% is not. -- **Component C's ONE-predicate shape is deliberate, not the whole story.** It isolates the - crossing question cleanly; Component E is what shows the picture changes once predicates compose. - -## The verdict, stated the way the mission brief asked for it - -*"Where is the cheapest and cleanest execution boundary?"* — measured, not assumed: for a single -predicate over a native lane, **Java itself, via the Vector API reading the segment zero-copy, is -the fastest arm tested at every scale**. The native crossing earns its keep once real work — SIMD -kernels for multi-predicate fusion, the guaranteed-one-crossing property for an arbitrarily long -`View` chain, and (per `valhalla-lab/`) genuine per-population bulk operations — is on the other -side of it. The honest reading is not "Rust wins" or "Java wins" but **"the crossing is worth -paying for composed work, not for one predicate read alone"** — exactly the nuance the mission -brief's Phase G asked this benchmark to establish rather than assume in either direction. +# Measured results — where does execution belong? + +> Every number below was produced by `bench/run.sh` on this box, and every table is generated by +> `summarise.sh` so it cannot drift from its data. +> +> | file | what it is | +> |---|---| +> | `results/jmh-run-full.txt` / `jmh-results-full.csv` | the first full sweep (A, B, C/D, and E at 65,536 rows only) | +> | `results/jmh-run.txt` / `jmh-results.csv` | the `./run.sh E_` re-run, after a 256-row arm was added to the fusion sweep | +> | `results/jmh-results-merged.csv` | A/B/C from the full sweep + E from the re-run — **the input to every table below** | +> | `results/TABLES.md` | `./summarise.sh results/jmh-results-merged.csv` | +> +> The fusion sweep was re-run because the first pass measured only 65,536 rows and reported +> "fusion does nothing", which is true there and false in general. Adding the small-row arm is +> what turned a wrong summary into a correct one — see § E/F. + +## Environment + +| | | +|---|---| +| JDK | `OpenJDK 64-Bit Server VM 26.0.2+10-55` | +| harness | JMH 1.37, compiler blackholes auto-detected and in use | +| VM flags | `--enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.vector` | +| OS / arch | Linux x86-64 | +| CPU | Intel Xeon @ 2.10 GHz, 4 logical processors, AVX-512 (`avx512f/dq/bw/vl/vbmi/ifma/cd`) | +| vector species | `Species[int, 16, S_512_BIT]` — **16 int lanes, 512 bit**, the same width the Rust kernel uses | +| native library | `abi 0.1`, `simd ndarray::simd avx512`, `profile release` | +| predicate | `class == 7 AND value > 100` (≈3.3 % selectivity, straddles zero so the signed compare is real) | +| settings | `@Fork(1)`, warm-up 5 × 500 ms, measurement 8 × 500 ms, `Mode.AverageTime`, GC between iterations | + +Shared 4-vCPU container: no CPU pinning, no isolated cores, no turbo/HT control. **Read +order-of-magnitude differences as established and sub-10 % differences as not.** + +--- + +## The headline, stated before the tables because it is not what was expected + +**The Java Vector API is faster than the native kernel at every row count measured, from 64 rows +to 4,194,304 rows.** There is no crossover point in this data. The naive expectation — "Rust with +AVX-512 beats the JVM, the only question is where the crossing overhead stops mattering" — is +falsified by this measurement. + +That result is real, and the explanation is *not* "Rust is slower than Java". It is that **the two +arms are not doing the same amount of work**, and the difference is architectural. See +[§ Why](#why-the-vector-arm-wins) below, which is grounded in the Rust source rather than in a +guess. + +--- + +## A — the membrane crossing, isolated + +| benchmark | mean | ±99.9 % CI | unit | +|---|---:|---:|---| +| `javaCallControl` — an ordinary Java call, no crossing | 0.497 | ±0.037 | ns/op | +| `bareDowncall_noArgs` — `lgj_abi_manifest()` | **21.911** | ±0.715 | ns/op | +| `downcall_twoArgs_outPointer` — `lgj_mask_count(handle, out)` | 117.855 | ±1.808 | ns/op | + +**A bare Panama downcall costs ~22 ns**, about 44 ordinary Java calls. That is the number the +whole design rests on: it is cheap enough that one crossing per query is free, and expensive +enough that one crossing per *row* would be catastrophic — 65,536 rows × 22 ns ≈ 1.4 ms of pure +overhead, which is 90× the entire fused query. The anti-JNI rule is not stylistic. + +**The 118 ns row must not be read as "argument marshalling costs 96 ns".** `lgj_mask_count` takes +a read lock on the resource registry, resolves a generation-checked handle, clones an `Arc`, then +locks the entry — that is the ABI's safety machinery, not Panama's marshalling. Attributing it to +the linker would be wrong. What it *does* bound honestly is the cost of any real ABI call that +resolves a handle, and it is the reason the row-sweep's native arm has a ~0.6 µs floor rather than +a ~0.02 µs one. + +--- + +## B — reading native memory from Java + +65,536 `i32`, summed. Same data, same answer (cross-checked in `@Setup`). + +| benchmark | mean | ±99.9 % CI | unit | +|---|---:|---:|---| +| `segmentVector` — `MemorySegment`, Vector API, zero copy | **3.337** | ±0.094 | µs/op | +| `heapArrayBaseline` — `int[]` on the Java heap | 5.158 | ±0.179 | µs/op | +| `segmentScalar` — `MemorySegment`, scalar loop | 5.220 | ±0.241 | µs/op | + +**FFM access is at parity with heap access** (5.220 vs 5.158 µs — a 1.2 % difference, inside the +confidence intervals). Java pays no penalty for its data living in native memory. This is what +makes the whole comparison in C possible: the Java arm is not handicapped, so if it wins it wins +on merit. + +Explicitly vectorising beats both by 1.55×, on a plain `sum` where C2's auto-vectoriser might have +been expected to do the same job. + +--- + +## C/D — native kernel vs Java Vector API vs Java scalar + +All three read **the same native lanes** with **zero copies**. All three produce the same answer, +asserted before timing. + +| rows | lane KiB | `native_fusedPlan` | `java_vectorApi` | `java_scalarLoop` | fastest | native ÷ vector | +|---:|---:|---:|---:|---:|---|---:| +| 64 | 0.25 | 0.612 ±0.039 | **0.011 ±0.001** | 0.028 ±0.002 | vector | 56.4× | +| 256 | 1 | 0.623 ±0.027 | **0.025 ±0.001** | 0.085 ±0.011 | vector | 25.0× | +| 1,024 | 4 | 0.708 ±0.037 | **0.078 ±0.003** | 0.338 ±0.018 | vector | 9.1× | +| 4,096 | 16 | 1.524 ±0.346 | **0.385 ±0.016** | 1.252 ±0.020 | vector | 4.0× | +| 16,384 | 64 | 4.291 ±0.190 | **1.744 ±0.055** | 5.553 ±0.288 | vector | 2.5× | +| 65,536 | 256 | 15.324 ±0.867 | **8.027 ±0.309** | 42.846 ±9.355 | vector | 1.9× | +| 262,144 | 1,024 | 69.374 ±5.998 | **42.519 ±5.261** | 343.389 ±7.332 | vector | 1.6× | +| 1,048,576 | 4,096 | 411.333 ±37.244 | **310.405 ±17.660** | 1623.313 ±26.973 | vector | 1.3× | +| 4,194,304 | 16,384 | 1858.686 ±149.400 | **1319.107 ±37.240** | 6602.036 ±100.771 | vector | 1.4× | + +**Gate.** 50/50 benchmark rows completed, 0 failures, whole sweep 00:12:22. `Data.crossCheck()` +ran in `@Setup` for every fork and never threw, so the three arms provably agree on both the count +and the sum — a speed comparison between them is a comparison, not a race between a correct +implementation and a subtly wrong faster one. + +### There ARE two crossovers — they are just not the one that was expected + +**Crossover 1 — native overtakes the Java *scalar* loop at roughly 4,096–16,384 rows.** +`native ÷ scalar` runs 0.05×, 0.14×, 0.48×, 0.82× (native losing), then 1.29×, 2.80×, 4.95×, +3.95×, 3.55×. Below ~4,096 rows the ~0.6 µs crossing floor is not repaid; above ~16,384 it is, +several times over. If the Java side were written the way most Java is written — an ordinary loop — +this is the crossover, and it sits where the fixed-cost model says it should. + +**Crossover 2 — there is none against the Vector API.** `native ÷ vector` is above 1.0 at every +row count, monotonically decreasing from 56.4× to 1.33× and then flattening at 1.41×. Extrapolating +the last three points does not project a crossing: both arms are memory-bound by then and the ratio +has stopped moving. + +Three further things this table says. + +**1. The native arm has a ~0.6 µs floor and the Java arm has none.** Below ~1,024 rows the native +number barely moves (0.612 → 0.708 µs) because it is almost entirely fixed cost: build the +predicate list, marshal two 24-byte op descriptors, cross, resolve the handle under a lock, +allocate two mask buffers, and copy the result out. The Java arm starts at 11 ns because it does +none of that. **The ratio narrowing from 56× to 1.4× is the crossing overhead being amortised**, +exactly as the model predicts — it is the *residual* 1.4× that needs explaining, not the shape. + +**2. The Vector API is worth using — the scalar control proves it.** `java_scalarLoop` is +**2.5× to 8.1×** slower than `java_vectorApi` at every row count (ratios by size: 2.5, 3.4, 4.3, +3.3, 3.2, 5.3, 8.1, 5.2, 5.0). So C2 is not auto-vectorising this two-lane predicate-and-count +into anything like the explicit version, and "the Vector API bought nothing" is falsified. Note +the ratio is *not* monotonic — it peaks at 262,144 rows and comes back down — so it should be read +as "consistently several-fold", not as a trend. + +The scalar arm also shows a sharp discontinuity between 16,384 rows (5.6 µs) and 65,536 rows +(42.8 µs) — 7.7× for 4× the data, with a wide ±9.4 µs interval. That is around where the two lanes +together stop fitting in L2. It is flagged, not explained: confirming it would need hardware +counters, which this harness does not collect. + +**3. Both arms become memory-bound at the top.** At 4 M rows the two lanes are 32 MiB; the vector +arm's 1.32 ms is ≈24 GiB/s and the native arm's 1.86 ms is ≈17 GiB/s. Neither is compute-limited, +which is why the ratio stops improving. + +### Why the vector arm wins + +Not speculation — this is what `native/lgj-abi/src/exports.rs::plan_eval_impl` does, per call: + +```rust +let mut acc = vec![0u64; n_words]; // allocation #1 ─┐ 512 KiB each at 4 M rows +let mut scratch = vec![0u64; n_words]; // allocation #2 ─┘ +for w in acc.iter_mut() { *w = u64::MAX; } // pass over the mask +for op in ops { + kernels::eval_predicate(..., &mut scratch)?; // read lane, WRITE full mask + kernels::combine_into(path, op.combine, &mut acc, &scratch)?; // read+write mask +} +let count = kernels::popcount(path, &acc); // another pass +g.words.copy_from_slice(&acc); // another pass +``` + +For a two-predicate query the native path performs **two heap allocations, a memset, two full mask +writes, two mask read-modify-writes, a popcount pass and a copy pass** — six-plus passes over the +mask on top of the two lane reads. The Java kernel does **two lane reads and keeps the count in a +register**; it never materialises a mask at all. + +**So the arms answer the same question but produce different artifacts.** The native path also +yields a *persisted, reusable selection* — a `Mask` the caller can keep, intersect, and reduce +over later. The Java path yields a number. Charging the native path for that artifact and then +declaring it slower would be the benchmark lying by omission. + +What this measurement therefore establishes, precisely: + +- ✅ For a **count-only** query, executing in Java over the native lanes is 1.3–1.9× faster at + large sizes and up to 56× faster at small ones. +- ✅ The native path's fixed cost is ~0.6 µs and is fully amortised past ~10⁵ rows. +- ❌ It does **not** establish that `ndarray::simd` is slower than the JVM's vectoriser. The two + kernels do different work, and separating those would need a native entry point that counts + without materialising a mask — which does not exist and which I did not add, because adding a + symbol to make my own benchmark look better is exactly the wrong move. + +**Fixable inefficiencies visible in the source, reported not patched** (`native/` is another +agent's tree): the two `vec![0u64; n_words]` allocations are per call and could be a reusable +scratch on the resource; and a count-only fast path that skips mask materialisation would remove +most of the residual gap. Both are ABI-implementation changes, not ABI-contract changes. + +--- + +## E/F — fusion, and what the fluent API itself costs + +| rows | predicates | `fused` | `unfused` | `fusedScalarKernel` | `planConstructionOnly` | unfused ÷ fused | +|---:|---:|---:|---:|---:|---:|---:| +| 256 | 1 | 0.404 ±0.010 | 0.385 ±0.009 | 0.503 ±0.015 | 0.056 ±0.004 | 0.95× | +| 256 | 2 | **0.520 ±0.032** | 0.931 ±0.018 | 0.835 ±0.039 | 0.111 ±0.003 | **1.79×** | +| 256 | 4 | **0.808 ±0.033** | 2.097 ±0.064 | 1.662 ±0.084 | 0.282 ±0.056 | **2.60×** | +| 256 | 8 | **1.482 ±0.070** | 4.437 ±0.204 | 3.489 ±0.113 | 0.657 ±0.196 | **2.99×** | +| 65,536 | 1 | 6.913 ±0.165 | 6.326 ±0.175 | 74.340 ±1.659 | 0.053 ±0.005 | 0.92× | +| 65,536 | 2 | 15.222 ±0.707 | 14.204 ±0.804 | 408.170 ±20.242 | 0.113 ±0.006 | 0.93× | +| 65,536 | 4 | 25.591 ±0.582 | 31.790 ±3.337 | 917.387 ±27.768 | 0.261 ±0.012 | 1.24× | +| 65,536 | 8 | 58.978 ±4.509 | 60.968 ±2.657 | 1825.916 ±125.373 | 0.601 ±0.032 | 1.03× | + +**Fusion's benefit is a fixed cost saved, so it shows up exactly where fixed cost matters — and +the sweep is what makes that visible.** At 256 rows it grows cleanly with predicate count: +0.95× → 1.79× → 2.60× → **2.99×**. At 65,536 rows it is **not measurable** (0.92×–1.24×, straddling +1.0, inside the intervals) because memory traffic dwarfs eight 22 ns crossings. + +Had this been measured at one row count, it would have produced either "fusion gives 3×" or +"fusion does nothing", and both would have been wrong as stated. The honest claim is: *fusion is +worth up to 3× on small selections and is invisible on large ones — and its primary justification +is architectural (one crossing per query, never per row) rather than throughput.* + +**`fusedScalarKernel` shows the SIMD kernel is doing real work**: at 65,536 rows it is **10.8×** +(1 predicate) to **31.0×** (8 predicates) slower than the same plan through the same membrane with +`ndarray::simd` replaced by the scalar reference. At 256 rows the same comparison is only 1.2× to +2.4×, because there the fixed crossing cost dominates on both sides — the SIMD contribution is a +property of the kernel, so it only becomes visible once there is enough data for the kernel to +matter. This path is the ABI's parity escape hatch, never production; the spread is a lower bound +on what SIMD contributes. + +**`planConstructionOnly` — the fluent API costs 53 ns for one predicate and 601 ns for eight, and +crosses the membrane zero times.** The benchmark asserts `Diagnostics.crossings()` is unchanged +across the construction and throws if it moved, so laziness is verified, not assumed. It is also +independent of row count (0.053 vs 0.056 µs at 256 vs 65,536 rows), which is what "the chain is +just a list of predicates" should look like. + +--- + +## Verdict — where does execution belong? + +On the evidence, **not where the architecture currently puts it, for count-only queries.** Stated +as separable claims, each with its supporting row: + +1. **The membrane is not the problem.** 22 ns per crossing, and FFM reads native memory at heap + parity (B). A design that crosses once per query pays essentially nothing for the boundary. + +2. **For a count/aggregate that returns a scalar, Java over the native lanes is the faster + execution site — at every size measured.** 1.3–1.9× at large sizes, up to 56× below ~1,000 rows + where the native path is all fixed cost (C). The zero-copy `IntVector.fromMemorySegment` bridge + is what makes this possible, and it is a genuinely zero-copy bridge. + +3. **But the comparison is between different amounts of work, and that is the actionable finding.** + The native path materialises a reusable mask and allocates twice per call; the Java path keeps a + counter in a register. The gap at 4 M rows is 1.4×, and the source shows enough avoidable work + to plausibly account for it. **The right response is to fix the native path, not to move + execution to Java** — because the mask is not waste, it is the thing that makes + `.where(...).select()` composable and reusable. + +4. **Bulk data still belongs native, and that is a separate question this harness did not put at + risk.** Nothing here compares against materialising Java objects; that is + [`valhalla-lab`](../valhalla-lab), which measures the object path at **49× slower** than the + native query and 2 MiB of heap for 65,536 rows against 816 bytes. The choice this harness + informs is "which side of the membrane runs the *kernel*", not "should the data be in Java". + +5. **The honest architectural conclusion.** The native side earns its place through *what it + owns* — the lanes, the packed mask, the composable selection, one allocation for the whole + dataset — not through raw kernel speed, where the JVM's vectoriser is currently competitive to + better on this hardware. A Java-side kernel over native lanes is a legitimate and measurably + fast option for scalar-returning queries, and the ABI already makes it possible without + violating anything: `lgj_lane_describe` hands out a bounded read-only window and **no crossing + happens when Java reads it**. + + That is worth saying plainly: the design's own escape hatch is currently the fast path for one + class of query. The next slice should either close the native gap (count-only fast path, + scratch reuse) or make the Java-side kernel an explicit, documented execution strategy — not + leave it as an accident. + +### What this harness does not establish + +- Anything about multi-threaded or concurrent execution. Everything here is single-threaded; the + ABI's registry locking is measured only as latency in the A row. +- `ndarray::simd` vs the JVM vectoriser as kernels. See § Why — the arms differ structurally. +- Run-to-run variance from JIT nondeterminism: `@Fork(1)`. +- Anything about a machine that is not a 4-vCPU shared container. diff --git a/bench/results/TABLES.md b/bench/results/TABLES.md new file mode 100644 index 0000000..780dc5c --- /dev/null +++ b/bench/results/TABLES.md @@ -0,0 +1,44 @@ + +### A — membrane crossing, isolated + +| benchmark | mean | ±99.9% CI | unit | +|---|---:|---:|---| +| `javaCallControl` | 0.497 | ±0.037 | ns/op | +| `bareDowncall_noArgs` | 21.911 | ±0.715 | ns/op | +| `downcall_twoArgs_outPointer` | 117.855 | ±1.808 | ns/op | + +### B — reading native memory (65,536 i32) + +| benchmark | mean | ±99.9% CI | unit | +|---|---:|---:|---| +| `segmentVector` | 3.337 | ±0.094 | us/op | +| `heapArrayBaseline` | 5.158 | ±0.179 | us/op | +| `segmentScalar` | 5.220 | ±0.241 | us/op | + +### C/D — where does execution belong? (µs/op, mean ± 99.9% CI) + +| rows | lane KiB | `native_fusedPlan` | `java_vectorApi` | `java_scalarLoop` | fastest | native/vector | +|---:|---:|---:|---:|---:|---:|---:| +| 64 | 0 | 0.612 ±0.039 | 0.011 ±0.001 | 0.028 ±0.002 | **java_vectorApi** | 56.40x | +| 256 | 1 | 0.623 ±0.027 | 0.025 ±0.001 | 0.085 ±0.011 | **java_vectorApi** | 24.96x | +| 1,024 | 4 | 0.708 ±0.037 | 0.078 ±0.003 | 0.338 ±0.018 | **java_vectorApi** | 9.07x | +| 4,096 | 16 | 1.524 ±0.346 | 0.385 ±0.016 | 1.252 ±0.020 | **java_vectorApi** | 3.96x | +| 16,384 | 64 | 4.291 ±0.190 | 1.744 ±0.055 | 5.553 ±0.288 | **java_vectorApi** | 2.46x | +| 65,536 | 256 | 15.324 ±0.867 | 8.027 ±0.309 | 42.846 ±9.355 | **java_vectorApi** | 1.91x | +| 262,144 | 1024 | 69.374 ±5.998 | 42.519 ±5.261 | 343.389 ±7.332 | **java_vectorApi** | 1.63x | +| 1,048,576 | 4096 | 411.333 ±37.244 | 310.405 ±17.660 | 1623.313 ±26.973 | **java_vectorApi** | 1.33x | +| 4,194,304 | 16384 | 1858.686 ±149.400 | 1319.107 ±37.240 | 6602.036 ±100.771 | **java_vectorApi** | 1.41x | + +### E/F — fusion and the cost of the fluent API (µs/op) + +| rows | predicates | `fused` | `unfused` | `fusedScalarKernel` | `planConstructionOnly` | unfused/fused | +|---:|---:|---:|---:|---:|---:|---:| +| 256 | 1 | 0.404 ±0.010 | 0.385 ±0.009 | 0.503 ±0.015 | 0.056 ±0.004 | **0.95x** | +| 256 | 2 | 0.520 ±0.032 | 0.931 ±0.018 | 0.835 ±0.039 | 0.111 ±0.003 | **1.79x** | +| 256 | 4 | 0.808 ±0.033 | 2.097 ±0.064 | 1.662 ±0.084 | 0.282 ±0.056 | **2.60x** | +| 256 | 8 | 1.482 ±0.070 | 4.437 ±0.204 | 3.489 ±0.113 | 0.657 ±0.196 | **2.99x** | +| 65,536 | 1 | 6.913 ±0.165 | 6.326 ±0.175 | 74.340 ±1.659 | 0.053 ±0.005 | **0.92x** | +| 65,536 | 2 | 15.222 ±0.707 | 14.204 ±0.804 | 408.170 ±20.242 | 0.113 ±0.006 | **0.93x** | +| 65,536 | 4 | 25.591 ±0.582 | 31.790 ±3.337 | 917.387 ±27.768 | 0.261 ±0.012 | **1.24x** | +| 65,536 | 8 | 58.978 ±4.509 | 60.968 ±2.657 | 1825.916 ±125.373 | 0.601 ±0.032 | **1.03x** | + diff --git a/bench/results/jmh-results-merged.csv b/bench/results/jmh-results-merged.csv new file mode 100644 index 0000000..23b6cbc --- /dev/null +++ b/bench/results/jmh-results-merged.csv @@ -0,0 +1,66 @@ +Benchmark,Mode,Threads,Samples,Score,Score Error (99.9%),Unit,Param: predicates,Param: rows +com.adaworldapi.lancegraph.bench.A_DowncallOverhead.bareDowncall_noArgs,avgt,1,8,21.910816,0.714719,ns/op,, +com.adaworldapi.lancegraph.bench.A_DowncallOverhead.downcall_twoArgs_outPointer,avgt,1,8,117.854789,1.808283,ns/op,, +com.adaworldapi.lancegraph.bench.A_DowncallOverhead.javaCallControl,avgt,1,8,0.496614,0.037361,ns/op,, +com.adaworldapi.lancegraph.bench.B_SegmentAccess.heapArrayBaseline,avgt,1,8,5.157633,0.179433,us/op,,65536 +com.adaworldapi.lancegraph.bench.B_SegmentAccess.segmentScalar,avgt,1,8,5.219871,0.241453,us/op,,65536 +com.adaworldapi.lancegraph.bench.B_SegmentAccess.segmentVector,avgt,1,8,3.337320,0.093847,us/op,,65536 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_scalarLoop,avgt,1,8,0.027947,0.001631,us/op,,64 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_scalarLoop,avgt,1,8,0.085439,0.010847,us/op,,256 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_scalarLoop,avgt,1,8,0.337707,0.017531,us/op,,1024 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_scalarLoop,avgt,1,8,1.251500,0.020465,us/op,,4096 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_scalarLoop,avgt,1,8,5.553163,0.287911,us/op,,16384 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_scalarLoop,avgt,1,8,42.846109,9.354832,us/op,,65536 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_scalarLoop,avgt,1,8,343.389077,7.331871,us/op,,262144 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_scalarLoop,avgt,1,8,1623.313038,26.972711,us/op,,1048576 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_scalarLoop,avgt,1,8,6602.036020,100.770602,us/op,,4194304 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_vectorApi,avgt,1,8,0.010845,0.000597,us/op,,64 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_vectorApi,avgt,1,8,0.024940,0.000515,us/op,,256 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_vectorApi,avgt,1,8,0.078068,0.002504,us/op,,1024 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_vectorApi,avgt,1,8,0.385167,0.015544,us/op,,4096 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_vectorApi,avgt,1,8,1.744103,0.055036,us/op,,16384 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_vectorApi,avgt,1,8,8.027116,0.308605,us/op,,65536 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_vectorApi,avgt,1,8,42.518604,5.260801,us/op,,262144 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_vectorApi,avgt,1,8,310.404608,17.660428,us/op,,1048576 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_vectorApi,avgt,1,8,1319.107387,37.239509,us/op,,4194304 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.native_fusedPlan,avgt,1,8,0.611653,0.039113,us/op,,64 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.native_fusedPlan,avgt,1,8,0.622588,0.026833,us/op,,256 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.native_fusedPlan,avgt,1,8,0.708059,0.036741,us/op,,1024 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.native_fusedPlan,avgt,1,8,1.523730,0.346193,us/op,,4096 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.native_fusedPlan,avgt,1,8,4.291253,0.190398,us/op,,16384 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.native_fusedPlan,avgt,1,8,15.324083,0.867382,us/op,,65536 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.native_fusedPlan,avgt,1,8,69.373781,5.998053,us/op,,262144 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.native_fusedPlan,avgt,1,8,411.333183,37.244044,us/op,,1048576 +com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.native_fusedPlan,avgt,1,8,1858.686441,149.400062,us/op,,4194304 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fused,avgt,1,8,0.404255,0.010091,us/op,1,256 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fused,avgt,1,8,6.913186,0.165479,us/op,1,65536 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fused,avgt,1,8,0.520053,0.032240,us/op,2,256 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fused,avgt,1,8,15.222049,0.707462,us/op,2,65536 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fused,avgt,1,8,0.807558,0.033095,us/op,4,256 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fused,avgt,1,8,25.590826,0.582056,us/op,4,65536 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fused,avgt,1,8,1.482240,0.070287,us/op,8,256 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fused,avgt,1,8,58.977540,4.508809,us/op,8,65536 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel,avgt,1,8,0.502951,0.015378,us/op,1,256 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel,avgt,1,8,74.340346,1.659056,us/op,1,65536 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel,avgt,1,8,0.835215,0.038908,us/op,2,256 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel,avgt,1,8,408.169551,20.241519,us/op,2,65536 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel,avgt,1,8,1.662239,0.084279,us/op,4,256 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel,avgt,1,8,917.386626,27.768400,us/op,4,65536 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel,avgt,1,8,3.488951,0.112650,us/op,8,256 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel,avgt,1,8,1825.916402,125.373079,us/op,8,65536 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly,avgt,1,8,0.056078,0.004036,us/op,1,256 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly,avgt,1,8,0.052973,0.005193,us/op,1,65536 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly,avgt,1,8,0.111424,0.003422,us/op,2,256 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly,avgt,1,8,0.113013,0.005714,us/op,2,65536 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly,avgt,1,8,0.281631,0.055958,us/op,4,256 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly,avgt,1,8,0.261441,0.012488,us/op,4,65536 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly,avgt,1,8,0.657211,0.196387,us/op,8,256 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly,avgt,1,8,0.600529,0.031814,us/op,8,65536 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused,avgt,1,8,0.384609,0.009362,us/op,1,256 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused,avgt,1,8,6.326314,0.174757,us/op,1,65536 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused,avgt,1,8,0.930643,0.017781,us/op,2,256 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused,avgt,1,8,14.203920,0.803807,us/op,2,65536 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused,avgt,1,8,2.097204,0.064309,us/op,4,256 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused,avgt,1,8,31.789786,3.337085,us/op,4,65536 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused,avgt,1,8,4.437382,0.204156,us/op,8,256 +com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused,avgt,1,8,60.967976,2.656815,us/op,8,65536 diff --git a/bench/results/jmh-results.csv b/bench/results/jmh-results.csv index 09d68af..d2669ae 100644 --- a/bench/results/jmh-results.csv +++ b/bench/results/jmh-results.csv @@ -1,50 +1,33 @@ "Benchmark","Mode","Threads","Samples","Score","Score Error (99.9%)","Unit","Param: predicates","Param: rows" -"com.adaworldapi.lancegraph.bench.A_DowncallOverhead.bareDowncall_noArgs","avgt",1,8,21.910816,0.714719,"ns/op",, -"com.adaworldapi.lancegraph.bench.A_DowncallOverhead.downcall_twoArgs_outPointer","avgt",1,8,117.854789,1.808283,"ns/op",, -"com.adaworldapi.lancegraph.bench.A_DowncallOverhead.javaCallControl","avgt",1,8,0.496614,0.037361,"ns/op",, -"com.adaworldapi.lancegraph.bench.B_SegmentAccess.heapArrayBaseline","avgt",1,8,5.157633,0.179433,"us/op",,65536 -"com.adaworldapi.lancegraph.bench.B_SegmentAccess.segmentScalar","avgt",1,8,5.219871,0.241453,"us/op",,65536 -"com.adaworldapi.lancegraph.bench.B_SegmentAccess.segmentVector","avgt",1,8,3.337320,0.093847,"us/op",,65536 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_scalarLoop","avgt",1,8,0.027947,0.001631,"us/op",,64 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_scalarLoop","avgt",1,8,0.085439,0.010847,"us/op",,256 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_scalarLoop","avgt",1,8,0.337707,0.017531,"us/op",,1024 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_scalarLoop","avgt",1,8,1.251500,0.020465,"us/op",,4096 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_scalarLoop","avgt",1,8,5.553163,0.287911,"us/op",,16384 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_scalarLoop","avgt",1,8,42.846109,9.354832,"us/op",,65536 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_scalarLoop","avgt",1,8,343.389077,7.331871,"us/op",,262144 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_scalarLoop","avgt",1,8,1623.313038,26.972711,"us/op",,1048576 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_scalarLoop","avgt",1,8,6602.036020,100.770602,"us/op",,4194304 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_vectorApi","avgt",1,8,0.010845,0.000597,"us/op",,64 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_vectorApi","avgt",1,8,0.024940,0.000515,"us/op",,256 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_vectorApi","avgt",1,8,0.078068,0.002504,"us/op",,1024 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_vectorApi","avgt",1,8,0.385167,0.015544,"us/op",,4096 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_vectorApi","avgt",1,8,1.744103,0.055036,"us/op",,16384 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_vectorApi","avgt",1,8,8.027116,0.308605,"us/op",,65536 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_vectorApi","avgt",1,8,42.518604,5.260801,"us/op",,262144 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_vectorApi","avgt",1,8,310.404608,17.660428,"us/op",,1048576 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.java_vectorApi","avgt",1,8,1319.107387,37.239509,"us/op",,4194304 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.native_fusedPlan","avgt",1,8,0.611653,0.039113,"us/op",,64 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.native_fusedPlan","avgt",1,8,0.622588,0.026833,"us/op",,256 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.native_fusedPlan","avgt",1,8,0.708059,0.036741,"us/op",,1024 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.native_fusedPlan","avgt",1,8,1.523730,0.346193,"us/op",,4096 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.native_fusedPlan","avgt",1,8,4.291253,0.190398,"us/op",,16384 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.native_fusedPlan","avgt",1,8,15.324083,0.867382,"us/op",,65536 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.native_fusedPlan","avgt",1,8,69.373781,5.998053,"us/op",,262144 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.native_fusedPlan","avgt",1,8,411.333183,37.244044,"us/op",,1048576 -"com.adaworldapi.lancegraph.bench.C_ExecutionBoundary.native_fusedPlan","avgt",1,8,1858.686441,149.400062,"us/op",,4194304 -"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fused","avgt",1,8,6.818039,0.158841,"us/op",1,65536 -"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fused","avgt",1,8,15.599132,0.689941,"us/op",2,65536 -"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fused","avgt",1,8,29.721204,0.865278,"us/op",4,65536 -"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fused","avgt",1,8,59.362899,4.549396,"us/op",8,65536 -"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel","avgt",1,8,73.418548,1.464802,"us/op",1,65536 -"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel","avgt",1,8,405.096000,7.322619,"us/op",2,65536 -"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel","avgt",1,8,923.099907,31.760771,"us/op",4,65536 -"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel","avgt",1,8,1807.261109,28.175197,"us/op",8,65536 -"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly","avgt",1,8,0.052921,0.003815,"us/op",1,65536 -"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly","avgt",1,8,0.112476,0.005216,"us/op",2,65536 -"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly","avgt",1,8,0.269718,0.029700,"us/op",4,65536 -"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly","avgt",1,8,0.633565,0.029509,"us/op",8,65536 -"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused","avgt",1,8,7.641421,0.918093,"us/op",1,65536 -"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused","avgt",1,8,15.024855,0.818124,"us/op",2,65536 -"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused","avgt",1,8,27.308656,2.182643,"us/op",4,65536 -"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused","avgt",1,8,61.915508,4.111936,"us/op",8,65536 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fused","avgt",1,8,0.404255,0.010091,"us/op",1,256 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fused","avgt",1,8,6.913186,0.165479,"us/op",1,65536 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fused","avgt",1,8,0.520053,0.032240,"us/op",2,256 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fused","avgt",1,8,15.222049,0.707462,"us/op",2,65536 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fused","avgt",1,8,0.807558,0.033095,"us/op",4,256 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fused","avgt",1,8,25.590826,0.582056,"us/op",4,65536 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fused","avgt",1,8,1.482240,0.070287,"us/op",8,256 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fused","avgt",1,8,58.977540,4.508809,"us/op",8,65536 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel","avgt",1,8,0.502951,0.015378,"us/op",1,256 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel","avgt",1,8,74.340346,1.659056,"us/op",1,65536 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel","avgt",1,8,0.835215,0.038908,"us/op",2,256 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel","avgt",1,8,408.169551,20.241519,"us/op",2,65536 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel","avgt",1,8,1.662239,0.084279,"us/op",4,256 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel","avgt",1,8,917.386626,27.768400,"us/op",4,65536 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel","avgt",1,8,3.488951,0.112650,"us/op",8,256 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel","avgt",1,8,1825.916402,125.373079,"us/op",8,65536 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly","avgt",1,8,0.056078,0.004036,"us/op",1,256 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly","avgt",1,8,0.052973,0.005193,"us/op",1,65536 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly","avgt",1,8,0.111424,0.003422,"us/op",2,256 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly","avgt",1,8,0.113013,0.005714,"us/op",2,65536 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly","avgt",1,8,0.281631,0.055958,"us/op",4,256 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly","avgt",1,8,0.261441,0.012488,"us/op",4,65536 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly","avgt",1,8,0.657211,0.196387,"us/op",8,256 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly","avgt",1,8,0.600529,0.031814,"us/op",8,65536 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused","avgt",1,8,0.384609,0.009362,"us/op",1,256 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused","avgt",1,8,6.326314,0.174757,"us/op",1,65536 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused","avgt",1,8,0.930643,0.017781,"us/op",2,256 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused","avgt",1,8,14.203920,0.803807,"us/op",2,65536 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused","avgt",1,8,2.097204,0.064309,"us/op",4,256 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused","avgt",1,8,31.789786,3.337085,"us/op",4,65536 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused","avgt",1,8,4.437382,0.204156,"us/op",8,256 +"com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused","avgt",1,8,60.967976,2.656815,"us/op",8,65536 diff --git a/bench/results/jmh-run.txt b/bench/results/jmh-run.txt index c84bb87..cb4ad38 100644 --- a/bench/results/jmh-run.txt +++ b/bench/results/jmh-run.txt @@ -342,3 +342,1070 @@ Iteration 3: 56.783 us/op Iteration 4: 57.691 us/op Iteration 5: 59.234 us/op Iteration 6: 57.057 us/op +Iteration 7: 64.073 us/op +Iteration 8: 59.373 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fused": + 58.978 ±(99.9%) 4.509 us/op [Average] + (min, avg, max) = (56.783, 58.978, 64.073), stdev = 2.358 + CI (99.9%): [54.469, 63.486] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel +# Parameters: (predicates = 1, rows = 256) + +# Run progress: 25.00% complete, ETA 00:06:02 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 0.644 us/op +# Warmup Iteration 2: 0.501 us/op +# Warmup Iteration 3: 0.515 us/op +# Warmup Iteration 4: 0.505 us/op +# Warmup Iteration 5: 0.501 us/op +Iteration 1: 0.497 us/op +Iteration 2: 0.508 us/op +Iteration 3: 0.520 us/op +Iteration 4: 0.496 us/op +Iteration 5: 0.501 us/op +Iteration 6: 0.499 us/op +Iteration 7: 0.497 us/op +Iteration 8: 0.506 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel": + 0.503 ±(99.9%) 0.015 us/op [Average] + (min, avg, max) = (0.496, 0.503, 0.520), stdev = 0.008 + CI (99.9%): [0.488, 0.518] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel +# Parameters: (predicates = 1, rows = 65536) + +# Run progress: 28.13% complete, ETA 00:05:47 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 77.770 us/op +# Warmup Iteration 2: 75.651 us/op +# Warmup Iteration 3: 76.166 us/op +# Warmup Iteration 4: 76.732 us/op +# Warmup Iteration 5: 76.651 us/op +Iteration 1: 73.627 us/op +Iteration 2: 73.285 us/op +Iteration 3: 75.483 us/op +Iteration 4: 75.007 us/op +Iteration 5: 75.203 us/op +Iteration 6: 74.805 us/op +Iteration 7: 73.605 us/op +Iteration 8: 73.708 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel": + 74.340 ±(99.9%) 1.659 us/op [Average] + (min, avg, max) = (73.285, 74.340, 75.483), stdev = 0.868 + CI (99.9%): [72.681, 75.999] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel +# Parameters: (predicates = 2, rows = 256) + +# Run progress: 31.25% complete, ETA 00:05:32 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 0.976 us/op +# Warmup Iteration 2: 0.848 us/op +# Warmup Iteration 3: 0.833 us/op +# Warmup Iteration 4: 0.826 us/op +# Warmup Iteration 5: 0.832 us/op +Iteration 1: 0.833 us/op +Iteration 2: 0.822 us/op +Iteration 3: 0.823 us/op +Iteration 4: 0.851 us/op +Iteration 5: 0.817 us/op +Iteration 6: 0.820 us/op +Iteration 7: 0.877 us/op +Iteration 8: 0.838 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel": + 0.835 ±(99.9%) 0.039 us/op [Average] + (min, avg, max) = (0.817, 0.835, 0.877), stdev = 0.020 + CI (99.9%): [0.796, 0.874] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel +# Parameters: (predicates = 2, rows = 65536) + +# Run progress: 34.38% complete, ETA 00:05:16 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 422.391 us/op +# Warmup Iteration 2: 409.456 us/op +# Warmup Iteration 3: 410.748 us/op +# Warmup Iteration 4: 406.293 us/op +# Warmup Iteration 5: 402.930 us/op +Iteration 1: 399.740 us/op +Iteration 2: 399.729 us/op +Iteration 3: 402.465 us/op +Iteration 4: 406.314 us/op +Iteration 5: 402.886 us/op +Iteration 6: 412.331 us/op +Iteration 7: 410.116 us/op +Iteration 8: 431.775 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel": + 408.170 ±(99.9%) 20.242 us/op [Average] + (min, avg, max) = (399.729, 408.170, 431.775), stdev = 10.587 + CI (99.9%): [387.928, 428.411] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel +# Parameters: (predicates = 4, rows = 256) + +# Run progress: 37.50% complete, ETA 00:05:01 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 1.974 us/op +# Warmup Iteration 2: 1.637 us/op +# Warmup Iteration 3: 1.678 us/op +# Warmup Iteration 4: 1.667 us/op +# Warmup Iteration 5: 1.677 us/op +Iteration 1: 1.681 us/op +Iteration 2: 1.708 us/op +Iteration 3: 1.644 us/op +Iteration 4: 1.641 us/op +Iteration 5: 1.720 us/op +Iteration 6: 1.636 us/op +Iteration 7: 1.682 us/op +Iteration 8: 1.585 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel": + 1.662 ±(99.9%) 0.084 us/op [Average] + (min, avg, max) = (1.585, 1.662, 1.720), stdev = 0.044 + CI (99.9%): [1.578, 1.747] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel +# Parameters: (predicates = 4, rows = 65536) + +# Run progress: 40.63% complete, ETA 00:04:46 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 949.977 us/op +# Warmup Iteration 2: 916.566 us/op +# Warmup Iteration 3: 912.454 us/op +# Warmup Iteration 4: 949.298 us/op +# Warmup Iteration 5: 937.251 us/op +Iteration 1: 930.736 us/op +Iteration 2: 910.570 us/op +Iteration 3: 914.896 us/op +Iteration 4: 897.725 us/op +Iteration 5: 901.273 us/op +Iteration 6: 939.607 us/op +Iteration 7: 927.639 us/op +Iteration 8: 916.648 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel": + 917.387 ±(99.9%) 27.768 us/op [Average] + (min, avg, max) = (897.725, 917.387, 939.607), stdev = 14.523 + CI (99.9%): [889.618, 945.155] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel +# Parameters: (predicates = 8, rows = 256) + +# Run progress: 43.75% complete, ETA 00:04:31 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 3.966 us/op +# Warmup Iteration 2: 3.496 us/op +# Warmup Iteration 3: 3.586 us/op +# Warmup Iteration 4: 3.423 us/op +# Warmup Iteration 5: 3.463 us/op +Iteration 1: 3.623 us/op +Iteration 2: 3.473 us/op +Iteration 3: 3.506 us/op +Iteration 4: 3.446 us/op +Iteration 5: 3.479 us/op +Iteration 6: 3.493 us/op +Iteration 7: 3.451 us/op +Iteration 8: 3.441 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel": + 3.489 ±(99.9%) 0.113 us/op [Average] + (min, avg, max) = (3.441, 3.489, 3.623), stdev = 0.059 + CI (99.9%): [3.376, 3.602] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel +# Parameters: (predicates = 8, rows = 65536) + +# Run progress: 46.88% complete, ETA 00:04:16 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 1914.384 us/op +# Warmup Iteration 2: 1834.769 us/op +# Warmup Iteration 3: 1845.272 us/op +# Warmup Iteration 4: 1846.484 us/op +# Warmup Iteration 5: 1793.076 us/op +Iteration 1: 1793.263 us/op +Iteration 2: 1798.992 us/op +Iteration 3: 1781.046 us/op +Iteration 4: 1790.907 us/op +Iteration 5: 1973.222 us/op +Iteration 6: 1790.764 us/op +Iteration 7: 1809.896 us/op +Iteration 8: 1869.240 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.fusedScalarKernel": + 1825.916 ±(99.9%) 125.373 us/op [Average] + (min, avg, max) = (1781.046, 1825.916, 1973.222), stdev = 65.573 + CI (99.9%): [1700.543, 1951.289] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly +# Parameters: (predicates = 1, rows = 256) + +# Run progress: 50.00% complete, ETA 00:04:01 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 0.071 us/op +# Warmup Iteration 2: 0.057 us/op +# Warmup Iteration 3: 0.055 us/op +# Warmup Iteration 4: 0.055 us/op +# Warmup Iteration 5: 0.054 us/op +Iteration 1: 0.059 us/op +Iteration 2: 0.057 us/op +Iteration 3: 0.059 us/op +Iteration 4: 0.056 us/op +Iteration 5: 0.054 us/op +Iteration 6: 0.054 us/op +Iteration 7: 0.055 us/op +Iteration 8: 0.055 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly": + 0.056 ±(99.9%) 0.004 us/op [Average] + (min, avg, max) = (0.054, 0.056, 0.059), stdev = 0.002 + CI (99.9%): [0.052, 0.060] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly +# Parameters: (predicates = 1, rows = 65536) + +# Run progress: 53.13% complete, ETA 00:03:46 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 0.069 us/op +# Warmup Iteration 2: 0.059 us/op +# Warmup Iteration 3: 0.051 us/op +# Warmup Iteration 4: 0.054 us/op +# Warmup Iteration 5: 0.051 us/op +Iteration 1: 0.058 us/op +Iteration 2: 0.053 us/op +Iteration 3: 0.053 us/op +Iteration 4: 0.051 us/op +Iteration 5: 0.051 us/op +Iteration 6: 0.051 us/op +Iteration 7: 0.051 us/op +Iteration 8: 0.056 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly": + 0.053 ±(99.9%) 0.005 us/op [Average] + (min, avg, max) = (0.051, 0.053, 0.058), stdev = 0.003 + CI (99.9%): [0.048, 0.058] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly +# Parameters: (predicates = 2, rows = 256) + +# Run progress: 56.25% complete, ETA 00:03:31 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 0.138 us/op +# Warmup Iteration 2: 0.115 us/op +# Warmup Iteration 3: 0.125 us/op +# Warmup Iteration 4: 0.117 us/op +# Warmup Iteration 5: 0.111 us/op +Iteration 1: 0.111 us/op +Iteration 2: 0.111 us/op +Iteration 3: 0.113 us/op +Iteration 4: 0.113 us/op +Iteration 5: 0.111 us/op +Iteration 6: 0.108 us/op +Iteration 7: 0.111 us/op +Iteration 8: 0.114 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly": + 0.111 ±(99.9%) 0.003 us/op [Average] + (min, avg, max) = (0.108, 0.111, 0.114), stdev = 0.002 + CI (99.9%): [0.108, 0.115] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly +# Parameters: (predicates = 2, rows = 65536) + +# Run progress: 59.38% complete, ETA 00:03:16 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 0.135 us/op +# Warmup Iteration 2: 0.110 us/op +# Warmup Iteration 3: 0.107 us/op +# Warmup Iteration 4: 0.114 us/op +# Warmup Iteration 5: 0.107 us/op +Iteration 1: 0.114 us/op +Iteration 2: 0.111 us/op +Iteration 3: 0.109 us/op +Iteration 4: 0.110 us/op +Iteration 5: 0.113 us/op +Iteration 6: 0.116 us/op +Iteration 7: 0.118 us/op +Iteration 8: 0.113 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly": + 0.113 ±(99.9%) 0.006 us/op [Average] + (min, avg, max) = (0.109, 0.113, 0.118), stdev = 0.003 + CI (99.9%): [0.107, 0.119] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly +# Parameters: (predicates = 4, rows = 256) + +# Run progress: 62.50% complete, ETA 00:03:01 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 0.318 us/op +# Warmup Iteration 2: 0.306 us/op +# Warmup Iteration 3: 0.291 us/op +# Warmup Iteration 4: 0.289 us/op +# Warmup Iteration 5: 0.272 us/op +Iteration 1: 0.257 us/op +Iteration 2: 0.261 us/op +Iteration 3: 0.347 us/op +Iteration 4: 0.270 us/op +Iteration 5: 0.259 us/op +Iteration 6: 0.288 us/op +Iteration 7: 0.289 us/op +Iteration 8: 0.283 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly": + 0.282 ±(99.9%) 0.056 us/op [Average] + (min, avg, max) = (0.257, 0.282, 0.347), stdev = 0.029 + CI (99.9%): [0.226, 0.338] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly +# Parameters: (predicates = 4, rows = 65536) + +# Run progress: 65.63% complete, ETA 00:02:46 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 0.316 us/op +# Warmup Iteration 2: 0.262 us/op +# Warmup Iteration 3: 0.261 us/op +# Warmup Iteration 4: 0.265 us/op +# Warmup Iteration 5: 0.309 us/op +Iteration 1: 0.258 us/op +Iteration 2: 0.261 us/op +Iteration 3: 0.275 us/op +Iteration 4: 0.256 us/op +Iteration 5: 0.267 us/op +Iteration 6: 0.257 us/op +Iteration 7: 0.262 us/op +Iteration 8: 0.256 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly": + 0.261 ±(99.9%) 0.012 us/op [Average] + (min, avg, max) = (0.256, 0.261, 0.275), stdev = 0.007 + CI (99.9%): [0.249, 0.274] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly +# Parameters: (predicates = 8, rows = 256) + +# Run progress: 68.75% complete, ETA 00:02:31 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 0.776 us/op +# Warmup Iteration 2: 0.649 us/op +# Warmup Iteration 3: 0.653 us/op +# Warmup Iteration 4: 0.628 us/op +# Warmup Iteration 5: 0.598 us/op +Iteration 1: 0.658 us/op +Iteration 2: 0.661 us/op +Iteration 3: 0.640 us/op +Iteration 4: 0.589 us/op +Iteration 5: 0.594 us/op +Iteration 6: 0.902 us/op +Iteration 7: 0.597 us/op +Iteration 8: 0.616 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly": + 0.657 ±(99.9%) 0.196 us/op [Average] + (min, avg, max) = (0.589, 0.657, 0.902), stdev = 0.103 + CI (99.9%): [0.461, 0.854] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly +# Parameters: (predicates = 8, rows = 65536) + +# Run progress: 71.88% complete, ETA 00:02:16 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 0.755 us/op +# Warmup Iteration 2: 0.660 us/op +# Warmup Iteration 3: 0.592 us/op +# Warmup Iteration 4: 0.704 us/op +# Warmup Iteration 5: 0.633 us/op +Iteration 1: 0.616 us/op +Iteration 2: 0.617 us/op +Iteration 3: 0.587 us/op +Iteration 4: 0.576 us/op +Iteration 5: 0.599 us/op +Iteration 6: 0.583 us/op +Iteration 7: 0.607 us/op +Iteration 8: 0.619 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.planConstructionOnly": + 0.601 ±(99.9%) 0.032 us/op [Average] + (min, avg, max) = (0.576, 0.601, 0.619), stdev = 0.017 + CI (99.9%): [0.569, 0.632] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused +# Parameters: (predicates = 1, rows = 256) + +# Run progress: 75.00% complete, ETA 00:02:00 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 0.501 us/op +# Warmup Iteration 2: 0.387 us/op +# Warmup Iteration 3: 0.381 us/op +# Warmup Iteration 4: 0.391 us/op +# Warmup Iteration 5: 0.378 us/op +Iteration 1: 0.393 us/op +Iteration 2: 0.390 us/op +Iteration 3: 0.387 us/op +Iteration 4: 0.382 us/op +Iteration 5: 0.380 us/op +Iteration 6: 0.381 us/op +Iteration 7: 0.385 us/op +Iteration 8: 0.380 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused": + 0.385 ±(99.9%) 0.009 us/op [Average] + (min, avg, max) = (0.380, 0.385, 0.393), stdev = 0.005 + CI (99.9%): [0.375, 0.394] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused +# Parameters: (predicates = 1, rows = 65536) + +# Run progress: 78.13% complete, ETA 00:01:45 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 6.595 us/op +# Warmup Iteration 2: 6.526 us/op +# Warmup Iteration 3: 6.280 us/op +# Warmup Iteration 4: 6.308 us/op +# Warmup Iteration 5: 7.104 us/op +Iteration 1: 6.245 us/op +Iteration 2: 6.238 us/op +Iteration 3: 6.513 us/op +Iteration 4: 6.304 us/op +Iteration 5: 6.308 us/op +Iteration 6: 6.337 us/op +Iteration 7: 6.270 us/op +Iteration 8: 6.397 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused": + 6.326 ±(99.9%) 0.175 us/op [Average] + (min, avg, max) = (6.238, 6.326, 6.513), stdev = 0.091 + CI (99.9%): [6.152, 6.501] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused +# Parameters: (predicates = 2, rows = 256) + +# Run progress: 81.25% complete, ETA 00:01:30 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 1.103 us/op +# Warmup Iteration 2: 0.946 us/op +# Warmup Iteration 3: 0.943 us/op +# Warmup Iteration 4: 0.932 us/op +# Warmup Iteration 5: 0.931 us/op +Iteration 1: 0.923 us/op +Iteration 2: 0.951 us/op +Iteration 3: 0.930 us/op +Iteration 4: 0.927 us/op +Iteration 5: 0.925 us/op +Iteration 6: 0.937 us/op +Iteration 7: 0.922 us/op +Iteration 8: 0.930 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused": + 0.931 ±(99.9%) 0.018 us/op [Average] + (min, avg, max) = (0.922, 0.931, 0.951), stdev = 0.009 + CI (99.9%): [0.913, 0.948] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused +# Parameters: (predicates = 2, rows = 65536) + +# Run progress: 84.38% complete, ETA 00:01:15 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 14.757 us/op +# Warmup Iteration 2: 13.827 us/op +# Warmup Iteration 3: 13.941 us/op +# Warmup Iteration 4: 13.639 us/op +# Warmup Iteration 5: 13.879 us/op +Iteration 1: 13.673 us/op +Iteration 2: 14.664 us/op +Iteration 3: 14.181 us/op +Iteration 4: 13.711 us/op +Iteration 5: 14.745 us/op +Iteration 6: 13.901 us/op +Iteration 7: 14.212 us/op +Iteration 8: 14.543 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused": + 14.204 ±(99.9%) 0.804 us/op [Average] + (min, avg, max) = (13.673, 14.204, 14.745), stdev = 0.420 + CI (99.9%): [13.400, 15.008] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused +# Parameters: (predicates = 4, rows = 256) + +# Run progress: 87.50% complete, ETA 00:01:00 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 2.248 us/op +# Warmup Iteration 2: 2.127 us/op +# Warmup Iteration 3: 2.068 us/op +# Warmup Iteration 4: 2.095 us/op +# Warmup Iteration 5: 2.067 us/op +Iteration 1: 2.088 us/op +Iteration 2: 2.054 us/op +Iteration 3: 2.086 us/op +Iteration 4: 2.080 us/op +Iteration 5: 2.091 us/op +Iteration 6: 2.124 us/op +Iteration 7: 2.166 us/op +Iteration 8: 2.088 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused": + 2.097 ±(99.9%) 0.064 us/op [Average] + (min, avg, max) = (2.054, 2.097, 2.166), stdev = 0.034 + CI (99.9%): [2.033, 2.162] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused +# Parameters: (predicates = 4, rows = 65536) + +# Run progress: 90.63% complete, ETA 00:00:45 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 34.677 us/op +# Warmup Iteration 2: 30.707 us/op +# Warmup Iteration 3: 30.336 us/op +# Warmup Iteration 4: 30.246 us/op +# Warmup Iteration 5: 33.637 us/op +Iteration 1: 31.002 us/op +Iteration 2: 35.002 us/op +Iteration 3: 29.954 us/op +Iteration 4: 30.045 us/op +Iteration 5: 33.427 us/op +Iteration 6: 32.171 us/op +Iteration 7: 31.979 us/op +Iteration 8: 30.739 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused": + 31.790 ±(99.9%) 3.337 us/op [Average] + (min, avg, max) = (29.954, 31.790, 35.002), stdev = 1.745 + CI (99.9%): [28.453, 35.127] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused +# Parameters: (predicates = 8, rows = 256) + +# Run progress: 93.75% complete, ETA 00:00:30 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 4.932 us/op +# Warmup Iteration 2: 4.552 us/op +# Warmup Iteration 3: 4.348 us/op +# Warmup Iteration 4: 4.379 us/op +# Warmup Iteration 5: 4.290 us/op +Iteration 1: 4.327 us/op +Iteration 2: 4.626 us/op +Iteration 3: 4.465 us/op +Iteration 4: 4.337 us/op +Iteration 5: 4.370 us/op +Iteration 6: 4.488 us/op +Iteration 7: 4.526 us/op +Iteration 8: 4.361 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused": + 4.437 ±(99.9%) 0.204 us/op [Average] + (min, avg, max) = (4.327, 4.437, 4.626), stdev = 0.107 + CI (99.9%): [4.233, 4.642] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 26.0.2, OpenJDK 64-Bit Server VM, 26.0.2+10-55 +# VM invoker: /opt/jdks/jdk-26.0.2/bin/java +# VM options: --enable-native-access=ALL-UNNAMED --add-modules=jdk.incubator.vector -Dstdout.encoding=UTF-8 -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so --enable-native-access=ALL-UNNAMED +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 5 iterations, 500 ms each +# Measurement: 8 iterations, 500 ms each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused +# Parameters: (predicates = 8, rows = 65536) + +# Run progress: 96.88% complete, ETA 00:00:15 +# Fork: 1 of 1 +Picked up JAVA_TOOL_OPTIONS: +WARNING: Using incubator modules: jdk.incubator.vector +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.openjdk.jmh.util.Utils (file:/home/user/lance-graph-java/bench/lib/jmh-core-1.37.jar) +WARNING: Please consider reporting this to the maintainers of class org.openjdk.jmh.util.Utils +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +# Warmup Iteration 1: 67.063 us/op +# Warmup Iteration 2: 61.358 us/op +# Warmup Iteration 3: 60.608 us/op +# Warmup Iteration 4: 63.889 us/op +# Warmup Iteration 5: 60.896 us/op +Iteration 1: 63.044 us/op +Iteration 2: 61.284 us/op +Iteration 3: 61.587 us/op +Iteration 4: 60.711 us/op +Iteration 5: 58.430 us/op +Iteration 6: 59.685 us/op +Iteration 7: 61.525 us/op +Iteration 8: 61.479 us/op + + +Result "com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused": + 60.968 ±(99.9%) 2.657 us/op [Average] + (min, avg, max) = (58.430, 60.968, 63.044), stdev = 1.390 + CI (99.9%): [58.311, 63.625] (assumes normal distribution) + + +# Run complete. Total time: 00:08:03 + +REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on +why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial +experiments, perform baseline and negative tests that provide experimental control, make sure +the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts. +Do not assume the numbers tell you what you want them to tell. + +NOTE: Current JVM experimentally supports Compiler Blackholes, and they are in use. Please exercise +extra caution when trusting the results, look into the generated code to check the benchmark still +works, and factor in a small probability of new VM bugs. Additionally, while comparisons between +different JVMs are already problematic, the performance difference caused by different Blackhole +modes can be very significant. Please make sure you use the consistent Blackhole mode for comparisons. + +Benchmark (predicates) (rows) Mode Cnt Score Error Units +E_FusionAndPlanning.fused 1 256 avgt 8 0.404 ± 0.010 us/op +E_FusionAndPlanning.fused 1 65536 avgt 8 6.913 ± 0.165 us/op +E_FusionAndPlanning.fused 2 256 avgt 8 0.520 ± 0.032 us/op +E_FusionAndPlanning.fused 2 65536 avgt 8 15.222 ± 0.707 us/op +E_FusionAndPlanning.fused 4 256 avgt 8 0.808 ± 0.033 us/op +E_FusionAndPlanning.fused 4 65536 avgt 8 25.591 ± 0.582 us/op +E_FusionAndPlanning.fused 8 256 avgt 8 1.482 ± 0.070 us/op +E_FusionAndPlanning.fused 8 65536 avgt 8 58.978 ± 4.509 us/op +E_FusionAndPlanning.fusedScalarKernel 1 256 avgt 8 0.503 ± 0.015 us/op +E_FusionAndPlanning.fusedScalarKernel 1 65536 avgt 8 74.340 ± 1.659 us/op +E_FusionAndPlanning.fusedScalarKernel 2 256 avgt 8 0.835 ± 0.039 us/op +E_FusionAndPlanning.fusedScalarKernel 2 65536 avgt 8 408.170 ± 20.242 us/op +E_FusionAndPlanning.fusedScalarKernel 4 256 avgt 8 1.662 ± 0.084 us/op +E_FusionAndPlanning.fusedScalarKernel 4 65536 avgt 8 917.387 ± 27.768 us/op +E_FusionAndPlanning.fusedScalarKernel 8 256 avgt 8 3.489 ± 0.113 us/op +E_FusionAndPlanning.fusedScalarKernel 8 65536 avgt 8 1825.916 ± 125.373 us/op +E_FusionAndPlanning.planConstructionOnly 1 256 avgt 8 0.056 ± 0.004 us/op +E_FusionAndPlanning.planConstructionOnly 1 65536 avgt 8 0.053 ± 0.005 us/op +E_FusionAndPlanning.planConstructionOnly 2 256 avgt 8 0.111 ± 0.003 us/op +E_FusionAndPlanning.planConstructionOnly 2 65536 avgt 8 0.113 ± 0.006 us/op +E_FusionAndPlanning.planConstructionOnly 4 256 avgt 8 0.282 ± 0.056 us/op +E_FusionAndPlanning.planConstructionOnly 4 65536 avgt 8 0.261 ± 0.012 us/op +E_FusionAndPlanning.planConstructionOnly 8 256 avgt 8 0.657 ± 0.196 us/op +E_FusionAndPlanning.planConstructionOnly 8 65536 avgt 8 0.601 ± 0.032 us/op +E_FusionAndPlanning.unfused 1 256 avgt 8 0.385 ± 0.009 us/op +E_FusionAndPlanning.unfused 1 65536 avgt 8 6.326 ± 0.175 us/op +E_FusionAndPlanning.unfused 2 256 avgt 8 0.931 ± 0.018 us/op +E_FusionAndPlanning.unfused 2 65536 avgt 8 14.204 ± 0.804 us/op +E_FusionAndPlanning.unfused 4 256 avgt 8 2.097 ± 0.064 us/op +E_FusionAndPlanning.unfused 4 65536 avgt 8 31.790 ± 3.337 us/op +E_FusionAndPlanning.unfused 8 256 avgt 8 4.437 ± 0.204 us/op +E_FusionAndPlanning.unfused 8 65536 avgt 8 60.968 ± 2.657 us/op + +Benchmark result is saved to results/jmh-results.csv diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..83ed33b --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,140 @@ +# Architecture + +> Read `../README.md` first for the one-page picture. This document is the +> "why," not a restatement of the "what" — each section links to the real +> artifact that proves its claim rather than re-describing it. + +## The thesis, restated precisely + +> 64,000 logical entities do not become 64,000 Java objects. They become +> 1 native lane set + 1 packed mask + a handful of tiny typed descriptors +> + one bulk operation. + +This is not a performance slogan — it is a falsifiable claim, and every +layer of this project exists to make it either provably true or provably +false at the point where it would break. See +`.claude/knowledge/john-doe-migration-thesis.md` for the full framing and +the migration story ("yesterday's object-heavy Java... gets a +generated/schema-fed API that still feels like Java") that motivates it. + +## The four layers, and what each one is actually responsible for + +``` +Java semantic plane → Panama FFM membrane → Rust ABI crate → ndarray::simd +``` + +### 1. The Java semantic plane (`java/`) + +**Responsible for:** looking like ordinary, boring Java. `NativePattern` / +`View` / `Predicate` / `Pattern` / `Mask` / `Lens`. Nothing here may mention +`MemorySegment`, `Arena`, a lane id, an opcode, or which SIMD backend ran — +`ApiSurfaceTest` enforces this by *reflection*, not by convention (walks +every public member of every public type in +`com.adaworldapi.lancegraph`, fails if any signature mentions +`java.lang.foreign.*`, `java.lang.invoke.*`, or `internal.*`). + +**Not responsible for:** deciding when to cross the membrane. `View.where()` +composes pure data (a `Predicate` list) and crosses **zero** times — +proven by `LazinessTest`, which counts actual downcalls before and after +building a 16-condition chain and asserts the count didn't move. A terminal +operation (`count()`, `sumOf()`) is the only thing that ever crosses, and it +crosses **exactly once**, independent of predicate count (`FusionParityTest`) +and independent of row count up to 1,048,576+ (measured directly in +`bench/RESULTS.md`'s `planConstructionOnly` row, which scales with predicate +count and is flat across every row count tested). + +### 2. The Panama FFM membrane (`java/.../internal/ffm/`) + +**Responsible for:** turning the ABI contract (`docs/abi.md`) into real +`MethodHandle`s, `MemoryLayout`s, and a runtime **manifest cross-check** +that proves the loaded `.so` actually matches what this Java build was +compiled against — not by convention, by comparing two independently +computed numbers (`Layouts.java`'s `MemoryLayout.byteSize()` against the +manifest's own `size_of` fields) and refusing to proceed on any mismatch. +`AbiContractTest` proves this is a real check, not a formality: a real +shared library that happens to load fine (`libz.so.1`) is still rejected, +because it exports no `lgj_abi_manifest` symbol. + +**Not responsible for:** any policy about what a "resource" or "mask" +*means* semantically — that's the layer above. This layer only knows about +bytes, handles, and status codes. + +### 3. The Rust ABI crate (`native/lgj-abi`) + +**Responsible for:** the 14-symbol `extern "C"` surface in `docs/abi.md` +§7, the generation-checked handle registry (`.claude/knowledge/ +abi-ownership-and-handles.md`), and the generic SoA fixture. Bulk-only — +every function's cost scales with `n_rows`, or is lifecycle. No strings, no +callbacks, no per-element crossings (`docs/abi.md` §6, the anti-JNI rule). + +**Provably safe, not just tested-safe:** the registry's core invariant +(a stale handle can never dereference freed memory) was +**disable-verified** — the generation check was deliberately broken and +the suite re-run to confirm exactly the two tests that should catch it went +red, and only those two. See `.claude/board/EPIPHANIES.md` +`E-LGJ-CORE-SLICE-GREEN-DISABLE-VERIFIED-1` for the exact procedure and +numbers. + +### 4. `ndarray::simd` (a sibling repo, consumed not re-implemented) + +**Responsible for:** every bulk kernel. `native/lgj-abi/src/kernels.rs` is +the *only* file in this crate allowed to import from `ndarray`, and it +reaches everything exclusively through `ndarray::simd::*` — never +`ndarray::hpc::*` (the internal implementation namespace) directly. This +project added five primitives to `ndarray` under that repo's own W1a +consumer contract (`eq_u32_to_mask`, `gt_i32_to_mask`, `mask_and`/`mask_or` +(`_assign`), `masked_sum_i32`) rather than reimplementing anything locally. +See `.claude/knowledge/simd-provenance.md`. + +**Measured payoff:** SIMD vs. the crate's own independent scalar reference +kernel is 10.8×–31.1× faster on the fused multi-predicate path, growing +with predicate count (`bench/RESULTS.md`, Component E). This is the +single largest measured lever in the whole project — larger than the +membrane-crossing cost itself. + +## What the measurements actually say about where the boundaries pay off + +This is the part a pure architecture diagram cannot show, and it's the +reason `bench/` and `valhalla-lab/` exist as first-class deliverables +rather than an afterthought: + +- **The Rust↔Java membrane pays off for composed, multi-predicate work** — + where SIMD fusion (10.8×–31.1×) and the one-crossing guarantee for an + arbitrarily long `View` chain matter. It does **not** pay off for a + single predicate read off one lane, where the Java Vector API reading the + same native memory zero-copy is measurably *faster* at every scale tested + (see `docs/execution-boundary.md`). +- **Valhalla pays off for the tiny descriptor vocabulary** (`LaneId`, + `Ordinal`, `MaskId` — single-field types, ≤8 bytes, genuinely flatten) — + and does **not** rescue per-entity materialization (`Row`, 16 bytes, + measured `NOT-FLAT` even under Valhalla). See `docs/valhalla-lab.md`. + +Both of these are measured surprises relative to a naive "the native side +always wins" assumption, and both are load-bearing for how this project's +API is actually shaped: the fluent `View` stays lazy and fuses because +fusion is where the payoff is real, and the semantic vocabulary stays +`record`-shaped (one-word migration to `value record` when JEP 401 ships) +because that's exactly the shape Valhalla rewards. + +## Where a real graph slice would attach (not built yet, by design) + +`docs/abi.md` §10 names this explicitly: `WideFieldMask` (already has +`intersect`/`union`/`count` in `lance-graph-contract`) and `NodeRow` +(`#[repr(C, align(64))]`, 16|16|480 bytes, already size-locked) are the +existing Rust-side types this ABI's `MASK_WORD` lane and lane-descriptor +shapes are already compatible with. Operator-stated layout reference +(2026-08-17): the lance-graph substrate enforces **64K rows × 512 bytes +per row, read as 32 lanes of 16 bytes each (4-byte classid + 12-byte +payload — the V3 content-blind facet)** everywhere; the Java-side layout +may legitimately differ, but that 512-byte, 64-byte-aligned row is the +shape a real slice inherits — and it is exactly the shape +`ndarray::simd_soa::MultiLaneColumn` (64-byte-chunk iteration over an +`Arc<[u8]>`) was built for, which is why that type is earmarked for the +row-store slice and deliberately NOT used by today's flat-lane fixture +kernels (see `.claude/board/EPIPHANIES.md` +`E-LGJ-SIMD-SOA-IS-FOR-THE-ROW-STORE-NOT-THE-FLAT-LANES-1`) — see the lance-graph archaeology +findings in `.claude/board/AGENT_LOG.md`. The generic fixture in this +first slice was deliberately chosen over wiring the real graph types +immediately, so the membrane's physics could be proven independent of graph +semantics. Wiring `ClassView`/`WideFieldMask` is the natural next slice, +not a redesign. diff --git a/docs/execution-boundary.md b/docs/execution-boundary.md new file mode 100644 index 0000000..df9ca04 --- /dev/null +++ b/docs/execution-boundary.md @@ -0,0 +1,155 @@ +# The execution boundary — measured, and what the measurements imply + +> Companion to `bench/RESULTS.md` (the raw numbers + reproduction commands). +> This document is the synthesis: what the numbers mean for where work should +> execute, and the three structural facts about the hot path that the numbers +> only make sense in light of. + +## The question, as the mission posed it + +> "Where is the cheapest and cleanest execution boundary? Not: how can we +> maximize the amount of Java code?" + +Measured answer, from real JMH over identical data with a cross-checked +answer on every arm (`Data.crossCheck()`): **it depends on composition, and +the dependence is now quantified.** + +| workload shape | winner | evidence | +|---|---|---| +| one predicate, one lane, any row count 64→4M | **Java Vector API, zero-copy on the native segment** — 1.3×–56× faster than the crossing | `bench/RESULTS.md` Component C | +| one predicate vs a plain Java *scalar* loop | native wins only past ~4,096–16,384 rows | same sweep, scalar column | +| N predicates fused, 65,536 rows | native SIMD, 10.8×–31.1× over its own scalar reference | Component E | +| the crossing itself, empty | ~22 ns bare, ~118 ns with two args + out-pointer | Component A | +| reading native memory from Java at all | free — segment scalar ≈ heap array (within noise); segment *vector* 1.55× faster than both | Component B | + +The two headline implications: + +1. **The membrane's cost is real but small and fixed** (~0.6 µs including + wrapper overhead) — it is repaid by *work*, not by *data volume alone*. + A single predicate never generates enough work per byte to repay it, + because the Vector API can do that same predicate on the same bytes + without crossing at all. +2. **SIMD fusion is the largest lever measured anywhere in this project** + (10.8×–31.1×) — larger than any crossing cost. The crossing is how you + *reach* `ndarray::simd`'s fused kernels; that, not the crossing itself, + is what the Java side is buying. + +## Three structural facts the numbers rest on + +### 1. Zero-copy is precise language here, not marketing + +The project's invariant (per the mission brief): **crossing the boundary +must not itself require serialization or copying.** It does not claim no +allocation ever happens — a mask result is a legitimate, semantically +required native allocation. What is eliminated is *boundary-induced* +copying, and this is checkable in the code: + +- The Vector API arm reads the native lane via + `IntVector.fromMemorySegment(species, segment, offset, nativeOrder())` — + no `byte[]`, no `int[]` staging, no `MemorySegment.toArray`. The bench's + own README states the rule and why a copy anywhere would make the + comparison dishonest in *both* directions. +- On the native side, lanes are allocated once and never relocated + (`docs/abi.md` §4), so a `MemorySegment` view stays valid for the + resource's whole lifetime — the precondition for Java reading it in place. + +**This is also exactly the shape a real lance-graph slice inherits.** The +lance-graph side's `SoaEnvelope::{as_le_bytes, row_le, column_le}` are +already zero-copy `&[u8]` views over LE-resident backing bytes (that repo's +own doctrine: "every SoA envelope is zero-copy from creation to Lance +tombstone" — nothing is serialized between mailboxes). When those replace +the generic fixture, no serialization step *exists anywhere in the stack*: +Lance's columnar bytes are the wire format, the membrane hands Java a +bounded view of them, and both execution engines — `ndarray::simd` on one +side, the Vector API on the other — operate on the same un-copied bytes. +That is the whole point, and it is why the Vector-API finding below is a +*feature* of the design rather than an embarrassment to it. + +### 2. There is no thread pool in the hot path — the JVM's threads ARE the parallelism + +Checked, not assumed (2026-08-17): + +- `lgj-abi` has exactly one dependency: `ndarray` with + `default-features = false, features = ["std"]`. **Rayon is not in the + tree** (it is an optional ndarray feature, not enabled here). +- The only `thread::spawn` in the crate is `#[cfg(test)]`-only — two tests + proving the concurrency *shape* (8 threads on distinct resources: no + deadlock, no cross-talk, independently correct answers; and + opposite-order mask binops that would deadlock without address-ordered + locking, run 2,000 times each way). + +The design instead makes the *caller's* threads the unit of parallelism: +the registry takes a short read-lock only to resolve `handle → +Arc`, drops it, then locks only that entry +(`.claude/knowledge/abi-ownership-and-handles.md`). So N Java threads +driving N distinct resources — the "64K thoughts as many mailboxes, each +owned by its caller" model — run concurrently through the membrane with no +Rust-side scheduler, no fork-join pool, no rayon. Parallelism is implicit +in ownership, exactly as the sibling lance-graph substrate's +one-writer-per-mailbox doctrine intends. + +**Honest boundary:** the shape is proven (the two tests above); throughput +under real contention is NOT yet benchmarked — filed as +`TD-LGJ-REGISTRY-CONCURRENCY-UNMEASURED` in `.claude/board/TECH_DEBT.md`, +to be paid when a concurrent caller actually exists rather than +speculatively. + +### 3. The kernels chunk by direct lane-group indexing, not via `array_windows`/`array_chunks` + +Checked precisely (2026-08-17), not assumed either way: `ndarray::simd_ops` +exports `array_windows`/`array_chunks` as opt-in, generic const-N staging +helpers. A full trace of this project's call graph — +`eq_u32_to_mask`/`gt_i32_to_mask` → `load_u32x16`/`load_i32x16` → +`copy_from_slice(&src[..16])` — shows neither is invoked, at any input +size; nothing about data volume triggers them, since they only run if a +caller literally writes `array_chunks::(slice)`, which this call +graph never does. A repo-wide grep confirms the same is true of +`ndarray`'s own internals: `simd_soa.rs`'s `MultiLaneColumn` doc comment +*cross-references* `array_chunks` as living in `simd_ops.rs`, but does not +call it either. + +This is a considered choice for the mask kernels specifically, not an +omission: `array_windows` is a *sliding* window (every element visited N +times — right for stencils/filters, wrong for a linear scan, the same +reasoning the sibling tesseract-rs repo recorded when it evaluated and +declined `array_windows` for its own integral-image kernels). The mask +kernels instead stride directly: 16 elements per group through +`U32x16::eq_bitmask` / `I32x16::gt_bitmask`, ORing each group's 16-bit +result into position `(g % 4) * 16` of word `g / 4`, with a scalar tail — +zero iterator overhead, and the "trailing bits are zero" guarantee made +structural by zeroing the output first. This achieves the same *effect* +`array_chunks` exists to provide (fixed-width grouping), through each +primitive's own indexing rather than the shared utility — a legitimate +alternative, not a gap, though routing through `array_chunks` for +uniformity across `ndarray::simd`'s kernels would be a reasonable future +refactor if consistency across primitives becomes a goal in its own right. + +## The resulting execution model (the synthesis) + +Not "Rust executes, Java orchestrates" — the measured picture is finer: + +``` + the same un-serialized native bytes + ┌───────────────────────────────────┐ + │ lance / lane storage │ + └───────────────┬───────────────────┘ + borrowed segment │ one fused crossing + ┌──────────────────────┴─────────────────────┐ + ▼ ▼ + Java Vector API Rust ndarray::simd + — single-predicate reads — multi-predicate fused plans + — small/any row counts — the 10.8-31.1x SIMD kernels + — anything the JIT can see whole — anything worth ONE crossing + │ │ + └──────────────────────┬─────────────────────┘ + ▼ + tiny results (a count, a sum, a mask handle) +``` + +A future planner could even choose the side per-operation using exactly the +crossover table in `bench/RESULTS.md` — the data to make that choice +mechanically now exists. What keeps the model honest is the invariant both +sides share: **the bytes never serialize, never bounce, never mirror into +the Java heap as N objects.** Which side loops over them is an +implementation decision the measurements can now drive; that they are the +same bytes is the architecture. diff --git a/docs/panama.md b/docs/panama.md new file mode 100644 index 0000000..3bce268 --- /dev/null +++ b/docs/panama.md @@ -0,0 +1,100 @@ +# The Panama membrane + +> Companion to `docs/abi.md` (the normative Rust-side contract this +> document's Java-side machinery is checked against) and +> `.claude/agents/panama-bridge-engineer.md` (the review checklist for this +> code). This document explains the design decisions; the code and the +> tests are the proof. + +## The one property that matters: a header is a claim, a manifest is a fact + +Project Panama exists so the JVM can speak the platform calling convention +directly — no C compiler, no header, no `jextract` (see `docs/abi.md` §1, +`.claude/knowledge/no-c-ever.md`). But that only removes the *tool*; it +does not remove the *risk* a header used to (loudly) warn about: Java's +compiled-in idea of a struct's layout silently disagreeing with what the +native artifact actually produces. + +This project's answer is `lgj_abi_manifest()` — a function that returns a +pointer to a `'static` struct the compiled `.so` fills in from +`core::mem::size_of`/`align_of` on its own real types, never a hand-typed +constant (`native/lgj-abi/src/abi.rs`, with `const _: () = +assert!(size_of::() == N)` compile-time locks on every `#[repr(C)]` +type). `Abi.java` reads this manifest at load time and compares it against +**a second, independently-derived number**: Java's own `MemoryLayout` +definitions in `Layouts.java`, via `layout.byteSize()`/`byteAlignment()`. + +Two independently-computed numbers, not one number checked against itself. +`AbiContractTest` proves the check is real: a genuine shared library that +loads fine (`libz.so.1`) is still rejected — refused, not called into — +because it exports no `lgj_abi_manifest` symbol at all. + +## Ownership crosses the FFM boundary as belt-and-braces, not once + +`docs/abi.md` §4 gives Rust the generation-checked handle as the ground +truth for whether a resource is alive. Panama gives the *Java-side* +bookkeeping no borrow checker at all, so `Abi`/the public facade adds its +own fail-fast layer on top rather than trusting the native check alone: +a Java-side closed flag on a resource makes a use-after-close throw a +clear Java exception (`ClosedResourceException`) *before* the call ever +reaches native code — verified by `LifetimeTest`'s 23 checks (use after +close, double close, a selection outliving its parent, a selection closed +before its parent, an empty resource behaving legally). The native +`INVALID_HANDLE`/`PARENT_CLOSED` status codes are what actually prevent +memory unsafety; the Java-side flag is what keeps the failure mode +readable instead of an opaque native error surfacing through five layers +of `MethodHandle.invokeExact`. + +## Restricted methods are a feature, not friction + +Every FFM operation this project needs — `SymbolLookup.libraryLookup`, +`Linker.downcallHandle`, `MemorySegment.reinterpret` — is `@Restricted` in +the JDK, meaning it needs `--enable-native-access` and produces a compiler +warning without a suppression. This project does not suppress it: `javac +-Xlint:all` on the shipped tree produces **exactly 7** `[restricted]` +warnings, every one of them inside `internal/ffm/*` or a test file +deliberately exercising the same restricted call independently +(`AbiContractTest`). That count is a machine-checkable statement — not +prose — that every unsafe FFM operation in this project lives in the one +package `ApiSurfaceTest` already proves the public API never exposes. + +## Downcall handles are resolved once, never per call + +`Downcalls.java` resolves every `MethodHandle` into a `static final` at +class-init, matching `FunctionDescriptor`s to `docs/abi.md` §7 argument- +for-argument. `Component A` in `bench/` deliberately re-binds its own +method handles independently of `Downcalls` — not out of duplication, but +because measuring the JDK's own linker cost separately from this +project's wrapper is the only way to attribute the wrapper's overhead +correctly (`bench/README.md` rule 4). Measured: a bare downcall costs +~22 ns over a plain Java call (`bench/RESULTS.md`, Component A) — the +floor every native-side operation in this project sits on top of. + +## `--enable-preview` never reaches the shipped path + +The production Java tree (`java/`) targets `/opt/jdks/jdk-26.0.2`, where +FFM is **final** — the only flag needed anywhere is +`--enable-native-access=ALL-UNNAMED`. The Valhalla lab +(`valhalla-lab/src/valhalla`) is compiled *separately*, with its own +`-source 27 --enable-preview`, into its own output directory +(`results/valhalla-lab/`), and is never on the classpath the production +tests or the bench harness run against. `--enable-preview`-compiled +classfiles carry a preview marker that poisons every consumer that loads +them; keeping the two trees physically separate (rather than, say, +compiling once and gating features at runtime) is what makes this a +structural guarantee rather than a discipline someone could accidentally +violate. See `.claude/knowledge/jdk-toolchain-facts.md` for the exact +verified flag matrix across all three JDKs this project touches. + +## What Panama did NOT need to solve here + +Two things worth naming because they are easy to assume Panama handles and +it does not, by design, in this project: + +- **No upcalls.** `Linker.Option`/`upcallStub` exist in the API; this + project has zero uses of them. An upcall per element is the JNI + anti-pattern in a different costume (`docs/abi.md` §10), and the bulk-op + shape never needs a callback into Java mid-kernel. +- **No `captureCallState`/`errno`.** Nothing this membrane wraps is a + syscall. Every failure is a negative `i32` status the Rust side computed + deliberately, never an OS error code Panama would need to capture. diff --git a/docs/valhalla-lab.md b/docs/valhalla-lab.md new file mode 100644 index 0000000..1e118dd --- /dev/null +++ b/docs/valhalla-lab.md @@ -0,0 +1,127 @@ +# The Valhalla lab — synthesis + +> Companion to `valhalla-lab/README.md` and `valhalla-lab/docs/three-truths.md` +> (the raw findings and numbers) and `.claude/knowledge/valhalla-three-truths-method.md` +> (the method this lab is required to follow). This document is the "so +> what" — how the measurements bear on this project's actual API and +> migration story. + +## The method, briefly + +For each semantic value type in this project's small vocabulary (`LaneId`, +`Ordinal`, `MaskId`, `RowRange`, `Row`), three truths are distinguished and +never conflated: + +1. **Semantic truth** — what the type *should* mean (identity-free, an + opaque descriptor, safe to treat as a plain value). +2. **Stable-Java truth** — the `record` implementation on JDK 26 GA, + measured, not assumed to already deliver (1). +3. **Valhalla truth** — the *same source shape*, compiled as `value + record` against the JEP 401 early-access build, likewise measured. + +The mechanism that keeps this honest: `valhalla-lab/run.sh` step 0 +mechanically diffs `src/valhalla/Vocab.java` against `src/stable/Vocab.java` +*modulo the literal word `value`* and refuses to run if they differ by +more than that — so the A/B compares two runtimes on one program, never +two different programs. + +## The headline result, and why it is more useful than a clean "yes" + +The mission's mandatory experiment — 65,536 rows as (i) one native lane +plus one packed mask, (ii) hydrated Java objects, (iii) hydrated Valhalla +value objects — returned a nuanced answer, and the nuance is the finding: + +| | native, one crossing | hydrate 65,536 `Row`, then scan | +|---|---:|---:| +| stable JDK 26 | 19.5 µs, 289 KiB | 746 µs, 2.00 MiB | +| Valhalla JDK 27 EA | 15.7 µs, 289.5 KiB | 900 µs, 2.50 MiB | + +Native wins ~38–57× on time and ~7–9× on heap, **on both platforms** — +Valhalla does not close this gap. The reason is not "object headers are +still there" hand-waving; it's a hard, VM-confirmed cutoff: + +## R2 — the flattening cliff explains the whole result + +`ValueClass.isFlatArray` (the VM answering about its own array, not an +inference) shows array flattening stops dead at an **8-byte payload**: + +| type | payload | flattens? | +|---|---:|---| +| `LaneId`, `Ordinal` | 4 B | **yes** | +| `MaskId` | 8 B | **yes** | +| `RowRange` | 16 B | no | +| `Row` (id + class + value) | 16 B | no | + +`RowRange` landing on the wrong side is recorded in +`valhalla-lab/reproducers/README.md` as the one place the expectation was +too optimistic going in — a descriptor that was *expected* to flatten and +measurably does not. The line the VM draws is exactly the line separating +"tiny descriptor vocabulary" from "per-entity payload" in this project's +own design vocabulary: an id plus one more field already exceeds the +budget, so no realistic `Row`-shaped entity can benefit, by construction, +on this build. + +**Causal isolation, not correlation:** `run.sh` runs the whole suite three +more times with `UseArrayFlattening`/`UseFieldFlattening` toggled off +independently. Turning `UseArrayFlattening` off alone drops `LaneId`'s +per-element cost from 6.89 B to 28.00 B (matches "not flat" exactly); +turning `UseFieldFlattening` off alone changes nothing for `LaneId` (an +`int`-payload type has no sub-fields to flatten) — confirming array +flattening, not field flattening, is the mechanism actually in play for +this vocabulary. + +## What this means for the project's own API — and what it does NOT mean + +**The production `java/` API adopts none of the three Valhalla-only +mechanisms found** (R1's `@NullRestricted` container trick, R2's flat-array +allocation, R3's `jdk.internal.value.ValueClass` factories). Per +`reproducers/README.md`: *"distorting a public API to fit a preview VM's +current budget would bake a temporary constraint into a permanent +surface."* Concretely: + +- No `--add-exports` in the shipped build. +- No `jdk.internal.*` dependency anywhere in `java/`. +- The migration path from today's `record`-based vocabulary to Valhalla, + the day JEP 401 ships as final, stays **exactly one word per type** + (`record` → `value record`) — because nothing about today's API was + bent around the preview build's current limits. + +**What Valhalla DOES already buy, measured, not theoretical:** for the +single-field descriptor types this project actually uses as its public +vocabulary (`LaneId`, `Ordinal`), array storage is 5.5× smaller and array +reads are up to 8.3× faster once flattening applies — real numbers +Structure the semantic types were already shaped to receive, because they +were designed to be tiny and identity-free *before* this lab ever ran (per +`.claude/knowledge/john-doe-migration-thesis.md`'s litmus test: the small +vocabulary, never the bulk data, is where Valhalla was ever expected to +matter). + +## The three reproducers, and which JDK component each belongs to + +Filed as minimal, self-contained, independently-runnable files under +`valhalla-lab/reproducers/`, per the mission's explicit instruction that a +genuine Valhalla limitation gets a reproducer rather than an API +distortion: + +| # | limitation | belongs to | +|---|---|---| +| R1 | `@NullRestricted` field on an ordinary (identity) class fails `VerifyError` at class load — javac emits field initializers after `super()`, the VM demands strict fields before it, no source form expresses the required order | **javac** | +| R2 | Array flattening has a hard 8-byte payload cliff | **HotSpot / Valhalla** | +| R3 | The densest null-restricted array form is `jdk.internal`-only; generics erase flattening entirely (`List` is `Object[]` underneath); `Foo!` null-restricted type syntax does not parse | **Valhalla (language + libraries)** | + +R3's `Foo!` finding corroborates the earlier archaeology independently: a +direct compiler probe (`javac`, not documentation) confirms the syntax +genuinely does not exist in this build, matching what the three-JDK +source-checkout comparison found before any lab code was written (see +`.claude/board/EPIPHANIES.md` `E-LGJ-VALHALLA-ALREADY-MAINLINE-1`). + +## The honest limit of this lab + +Single-fork-equivalent caveats apply here too, stated in +`valhalla-lab/docs/three-truths.md`: this is a hand-rolled harness +(`Lab.time`), not JMH — labelled as such deliberately, with its timing +numbers treated as secondary evidence supporting the byte-count +measurements (`getThreadAllocatedBytes`, the primary instrument), not as +a benchmark-grade claim in their own right. JMH-grade timing for the +execution-boundary question lives in `bench/`, not here — see +`docs/execution-boundary.md`. diff --git a/valhalla-lab/docs/three-truths.md b/valhalla-lab/docs/three-truths.md index 15e4dad..8f50a6f 100644 --- a/valhalla-lab/docs/three-truths.md +++ b/valhalla-lab/docs/three-truths.md @@ -89,13 +89,13 @@ All figures per operation, `N = 1,000,000`. | measurement | stable | Valhalla | | |---|---:|---:|---| -| construct a `LaneId`, store into an array | 16.00 B | **2.89 B** | 5.5× less | -| construct a `LaneId`, never escaping | 7.03 B | 8.00 B | ~equal — escape analysis already handles this | -| `LaneId[N]` array + elements, per element | 20.00 B | **6.89 B** | 2.9× less | +| construct a `LaneId`, store into an array | 16.00 B | **3.29 B** | 4.9× less | +| construct a `LaneId`, never escaping | 7.57 B | 7.64 B | ~equal — escape analysis already handles this | +| `LaneId[N]` array + elements, per element | 20.00 B | **6.87 B** | 2.9× less | | bare `LaneId[N]`, per slot | 4.00 B | 4.00 B | equal (compressed oops vs flat int) | -| construct a `Descriptor` (two wrappers) | 56.00 B | **38.84 B** | 1.4× less | -| pass two wrappers through 3 call levels | 8.29 B | 10.45 B | ~equal | -| **read 65,536 `LaneId` from an array** | 44,182 ns | **5,349 ns** | **8.3× faster** | +| construct a `Descriptor` (two wrappers) | 56.00 B | **36.50 B** | 1.5× less | +| pass two wrappers through 3 call levels | 9.33 B | 10.87 B | ~equal | +| **read 65,536 `LaneId` from an array** | 43,739 ns | **5,339 ns** | **8.2× faster** | `LaneId[1024]` reports `FLAT` on Valhalla and `UNKNOWN` on stable — deliberately not `false`. A stable JDK has no `ValueClass.isFlatArray` to ask, so "the question does not exist here" is the @@ -107,10 +107,10 @@ Re-running the Valhalla build with the VM's own flattening disabled: | | default | `-XX:-UseArrayFlattening` | `-XX:-UseFieldFlattening` | |---|---:|---:|---:| -| `LaneId` array, per element | 6.89 B | 28.00 B | 6.71 B | +| `LaneId` array, per element | 6.87 B | 28.00 B | 6.71 B | | `LaneId[1024]` flat? | FLAT | NOT-FLAT | FLAT | -| read 65,536 from array | 5,349 ns | 47,469 ns | 5,303 ns | -| `Descriptor`, per instance | 38.84 B | 40.51 B | 80.00 B | +| read 65,536 from array | 5,339 ns | 47,469 ns | 5,303 ns | +| `Descriptor`, per instance | 36.50 B | 40.51 B | 80.00 B | Turning array flattening off returns the array numbers to roughly the stable baseline and makes the read **8.9× slower**; turning field flattening off doubles the `Descriptor` cost and leaves @@ -123,18 +123,38 @@ With `-XX:-DoEscapeAnalysis`, i.e. what the object model costs when the JIT cann | measurement | stable default | stable, no EA | Valhalla default | Valhalla, no EA | |---|---:|---:|---:|---:| -| `LaneId` never escaping | 7.03 B | 16.00 B | 8.00 B | 25.60 B | -| pass 2 wrappers, 3 levels | 8.29 B | 32.00 B | 10.45 B | 31.00 B | -| **`Ordinal` built per element** (65,536 iterations) | 50,062 ns | **5,166,233 ns** | 50,021 ns | **94,346 ns** | +| `LaneId` never escaping | 7.57 B | 16.00 B | 7.64 B | 25.60 B | +| pass 2 wrappers, 3 levels | 9.33 B | 32.00 B | 10.87 B | 30.90 B | +| **`Ordinal` built per element** (65,536 iterations) | 50,732 ns | **621,827 ns** | 49,851 ns | **91,383 ns** | That last row is the clearest single number in the lab. Unaided by escape analysis, building a -one-`int` wrapper per element costs the stable JDK **103×**; it costs Valhalla **1.9×**. This is -the concrete meaning of "the abstraction stops being something you pay for" — not that it is -faster when the JIT can see through it, but that it stays cheap when the JIT cannot. +one-`int` wrapper per element costs the stable JDK **12.3×** its own escape-analysed time; it +costs Valhalla **1.8×**. Head to head with the JIT unable to help, Valhalla is **6.8× faster**. +This is the concrete meaning of "the abstraction stops being something you pay for" — not that it +is faster when the JIT can see through it, but that it stays cheap when the JIT cannot. Honesty about what this row is *not*: with escape analysis on — the configuration anyone actually -ships — the two are indistinguishable at 50 µs. Valhalla's gain here is in **robustness**, not in -peak. That distinction is easy to lose and worth keeping. +ships — the two are indistinguishable (50.7 µs vs 49.9 µs, inside this harness's spread). Valhalla's +gain here is in **robustness**, not in peak. That distinction is easy to lose and worth keeping. + +A measurement error worth recording rather than quietly fixing: an earlier draft of this table +reported 5,166,233 ns for the stable no-EA cell, a 103× ratio. That run was executed while the +JMH suite in `bench/` was saturating the same four vCPUs. The number was real and the conclusion +it supported was the same, but it was inflated roughly 8× by contention. The table above is from a +later, uncontended run. + +**The general warning that follows from it:** this lab shares a 4-vCPU container with whatever +else is running. Every number in this document comes from one uncontended execution of `run.sh`, +and re-running it moves the allocation figures by a few percent and the timing figures by more. +Treat the *ratios and the order of magnitude* as the result and the third significant figure as +noise — the byte counts that land on exact multiples of 8 (16.00, 20.00, 32.00, 40.00) are the +stable ones, because those are object layouts rather than measurements of speed. + +One row in `valhalla-noea` illustrates the point loudly: `hydrate THEN scan` measured 25.8 ms +median with a 6.1–39.2 ms spread on the final run, against 0.88 ms in the default configuration. +A 30× median with a 6× spread is not a result about Valhalla; it is GC and a disabled optimiser +interacting on a loaded box. It is left in `results/` rather than deleted, and it is not quoted as +a finding anywhere. ### `-XX:±InlineTypePassFieldsAsArgs` @@ -220,19 +240,21 @@ supports the thesis more strongly than the expected result would have. | | stable | Valhalla | |---|---:|---:| -| **(1) native — one crossing, fused plan** | **16,378 ns** | **18,805 ns** | -| (2/3) hydrate 65,536 `Row` objects | 603,139 ns | 768,624 ns | -| (2/3) scan the materialised objects | 151,886 ns | 91,178 ns | -| (2/3) hydrate **then** scan (honest total) | **788,227 ns** | **898,955 ns** | +| **(1) native — one crossing, fused plan** | **16,040 ns** | **15,162 ns** | +| (2/3) hydrate 65,536 `Row` objects | 1,612,494 ns | 658,842 ns | +| (2/3) scan the materialised objects | 128,057 ns | 87,213 ns | +| (2/3) hydrate **then** scan (honest total) | **704,743 ns** | **871,361 ns** | Medians of 51 iterations after 200–2,000 warm-up runs. Spreads are in `results/*.txt`; the -hydration rows have long tails (stable max 5.2 ms) because they allocate 2 MiB per iteration and -occasionally meet a GC, which is exactly why the median is reported. +hydration rows have long tails (stable min 499 µs, max 4.8 ms) because they allocate 2 MiB per +iteration and occasionally meet a GC, which is exactly why the median is reported and why the +isolated-hydrate row should be read as "somewhere between 0.5 and 1.4 ms", not as a precise +figure. The hydrate-then-scan row is the stable one and is the one to use. -**Native is 48× faster than the honest object total on stable, 48× on Valhalla.** Note where -Valhalla's one real win sits: *scanning* already-materialised objects is 1.7× faster (91 µs vs -152 µs), because the scan is a read-only walk that benefits from better locality. It does not -matter, because the hydration that had to happen first costs 8× what the scan saves. +**Native is 44× faster than the honest object total on stable, 57× on Valhalla.** Note where +Valhalla's one real win sits: *scanning* already-materialised objects is 1.5× faster (87 µs vs +128 µs), because the scan is a read-only walk that benefits from better locality. It does not +matter, because the hydration that had to happen first costs several times what the scan saves. That is the thesis, measured: the expensive thing is not *scanning* 65,536 objects, it is *existing* as 65,536 objects. Valhalla makes the scan cheaper and does not make the existing @@ -252,9 +274,9 @@ entire program. No arena, no segment, no mask, no lane, no opcode, no row loop, both object models; only the behaviours it was written to avoid differ. The migration is a one-word source change, and it stays that way because the lab did **not** bend the API to fit a preview VM (see `reproducers/README.md`). -2. **Valhalla is a real win for the descriptor vocabulary.** 5.5× less allocation per `LaneId`, - 8.3× faster array reads, and — the durable part — 103× → 1.9× when escape analysis cannot help. - A `LaneId` really does stop being something you pay for. +2. **Valhalla is a real win for the descriptor vocabulary.** 4.9× less allocation per `LaneId`, + 8.2× faster array reads, and — the durable part — 6.8× faster than the stable JDK once escape + analysis is out of the picture. A `LaneId` really does stop being something you pay for. 3. **Valhalla does not rescue per-entity materialisation, and on this build it makes it worse.** 40 B/row against 32 B/row, `NOT-FLAT`, because a 16-byte payload is over the VM's flattening budget. The expected finding was "it does not help"; the observed finding is "it costs 25 % diff --git a/valhalla-lab/results/AB-default.diff b/valhalla-lab/results/AB-default.diff index 22da5ba..57b0910 100644 --- a/valhalla-lab/results/AB-default.diff +++ b/valhalla-lab/results/AB-default.diff @@ -5,18 +5,18 @@ > platform valhalla > java.vm.version 27-jep401ea3+1-1 6c6 -< jvm args [--enable-native-access=ALL-UNNAMED, -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so] +< jvm args [--enable-native-access=ALL-UNNAMED, -Dstdout.encoding=UTF-8, -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so] --- -> jvm args [--enable-native-access=ALL-UNNAMED, -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so, --enable-preview, --add-exports=java.base/jdk.internal.vm.annotation=ALL-UNNAMED, --add-exports=java.base/jdk.internal.value=ALL-UNNAMED] +> jvm args [--enable-native-access=ALL-UNNAMED, -Dstdout.encoding=UTF-8, -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so, --enable-preview, --add-exports=java.base/jdk.internal.vm.annotation=ALL-UNNAMED, --add-exports=java.base/jdk.internal.value=ALL-UNNAMED] 9,14c9,14 -< platform stable ? stable-record vocabulary; arrays are reference arrays; fields are references +< platform stable — stable-record vocabulary; arrays are reference arrays; fields are references < LaneId.class.isValue() false < Ordinal.class.isValue() false < MaskId.class.isValue() false < RowRange.class.isValue() false < Row.class.isValue() false --- -> platform valhalla ? value-record vocabulary; null-restricted non-atomic arrays; @NullRestricted fields +> platform valhalla — value-record vocabulary; null-restricted non-atomic arrays; @NullRestricted fields > LaneId.class.isValue() true > Ordinal.class.isValue() true > MaskId.class.isValue() true @@ -59,74 +59,74 @@ 40,46c40,46 < construct N LaneId, store into array 15.26 MiB < ... per LaneId 16.00 B -< construct N LaneId, never escaping 6.71 MiB -< ... per LaneId 7.03 B +< construct N LaneId, never escaping 7.22 MiB +< ... per LaneId 7.57 B < LaneId[1024] flatness UNKNOWN(no ValueClass API on a stable JDK; a reference array is never flat) < allocate+fill LaneId[N] (array + elements) 19.07 MiB < ... per element 20.00 B --- -> construct N LaneId, store into array 2.75 MiB -> ... per LaneId 2.89 B -> construct N LaneId, never escaping 7.63 MiB -> ... per LaneId 8.00 B +> construct N LaneId, store into array 3.14 MiB +> ... per LaneId 3.29 B +> construct N LaneId, never escaping 7.29 MiB +> ... per LaneId 7.64 B > LaneId[1024] flatness FLAT -> allocate+fill LaneId[N] (array + elements) 6.57 MiB -> ... per element 6.89 B +> allocate+fill LaneId[N] (array + elements) 6.56 MiB +> ... per element 6.87 B 49,55c49,55 < Descriptor kind identity class with two reference fields < Descriptor fields null-restricted false < construct N Descriptor (2 wrappers each) 53.41 MiB < ... per Descriptor 56.00 B -< pass 2 wrappers through 3 call levels 7.90 MiB -< ... per call 8.29 B -< read 65,536 LaneId from array median= 44182.0 ns [min 41690.0 .. max 59914.0] n=51 +< pass 2 wrappers through 3 call levels 8.90 MiB +< ... per call 9.33 B +< read 65,536 LaneId from array median= 43739.0 ns [min 42371.0 .. max 73206.0] n=51 --- > Descriptor kind value class with two @NullRestricted value fields > Descriptor fields null-restricted true -> construct N Descriptor (2 wrappers each) 37.05 MiB -> ... per Descriptor 38.84 B -> pass 2 wrappers through 3 call levels 9.97 MiB -> ... per call 10.45 B -> read 65,536 LaneId from array median= 5349.0 ns [min 5179.0 .. max 11356.0] n=51 +> construct N Descriptor (2 wrappers each) 34.81 MiB +> ... per Descriptor 36.50 B +> pass 2 wrappers through 3 call levels 10.36 MiB +> ... per call 10.87 B +> read 65,536 LaneId from array median= 5339.0 ns [min 5262.0 .. max 9259.0] n=51 59c59 < platform stable --- > platform valhalla 61,65c61,65 -< bare long index median= 59148.0 ns [min 59077.0 .. max 114952.0] n=51 -< RowRange bounds (wrapper hoisted) median= 47952.0 ns [min 47759.0 .. max 65535.0] n=51 +< bare long index median= 59836.0 ns [min 59076.0 .. max 125353.0] n=51 +< RowRange bounds (wrapper hoisted) median= 47964.0 ns [min 47876.0 .. max 168118.0] n=51 < per-element wrapper: bytes allocated 1.00 MiB < ... per element 16.00 B -< Ordinal built per element median= 50062.0 ns [min 49790.0 .. max 61194.0] n=51 +< Ordinal built per element median= 50732.0 ns [min 50046.0 .. max 99100.0] n=51 --- -> bare long index median= 63304.0 ns [min 59079.0 .. max 131743.0] n=51 -> RowRange bounds (wrapper hoisted) median= 57557.0 ns [min 56212.0 .. max 87932.0] n=51 +> bare long index median= 59134.0 ns [min 59058.0 .. max 122610.0] n=51 +> RowRange bounds (wrapper hoisted) median= 56975.0 ns [min 56642.0 .. max 112526.0] n=51 > per-element wrapper: bytes allocated 1.50 MiB > ... per element 24.00 B -> Ordinal built per element median= 50021.0 ns [min 49762.0 .. max 75999.0] n=51 +> Ordinal built per element median= 49851.0 ns [min 49725.0 .. max 70877.0] n=51 68c68 < platform stable --- > platform valhalla 79,83c79,83 -< (2)/(3) hydrate 65536 Row ? allocated 2.00 MiB +< (2)/(3) hydrate 65536 Row — allocated 2.00 MiB < ... per row 32.00 B < array flatness UNKNOWN(no ValueClass API on a stable JDK; a reference array is never flat) < ratio vs native lane bytes 2.00x < retained heap (APPROX, gc-delta) 2.25 MiB --- -> (2)/(3) hydrate 65536 Row ? allocated 2.50 MiB +> (2)/(3) hydrate 65536 Row — allocated 2.50 MiB > ... per row 40.00 B > array flatness NOT-FLAT > ratio vs native lane bytes 2.50x > retained heap (APPROX, gc-delta) 2.75 MiB 86,89c86,89 -< (1) native one crossing, fused plan median= 16378.0 ns [min 15962.0 .. max 33819.0] n=51 -< (2/3) hydrate 65536 Row objects median= 603139.0 ns [min 529172.0 .. max 5246059.0] n=51 -< (2/3) scan the materialised objects median= 151886.0 ns [min 112687.0 .. max 201271.0] n=51 -< (2/3) hydrate THEN scan (honest total) median= 788227.0 ns [min 710221.0 .. max 1146017.0] n=51 +< (1) native one crossing, fused plan median= 16040.0 ns [min 15900.0 .. max 31577.0] n=51 +< (2/3) hydrate 65536 Row objects median= 1612494.0 ns [min 499222.0 .. max 4777342.0] n=51 +< (2/3) scan the materialised objects median= 128057.0 ns [min 112482.0 .. max 227766.0] n=51 +< (2/3) hydrate THEN scan (honest total) median= 704743.0 ns [min 631780.0 .. max 974235.0] n=51 --- -> (1) native one crossing, fused plan median= 18805.0 ns [min 15515.0 .. max 38457.0] n=51 -> (2/3) hydrate 65536 Row objects median= 768624.0 ns [min 645509.0 .. max 1229947.0] n=51 -> (2/3) scan the materialised objects median= 91178.0 ns [min 83390.0 .. max 141648.0] n=51 -> (2/3) hydrate THEN scan (honest total) median= 898955.0 ns [min 822997.0 .. max 5059624.0] n=51 +> (1) native one crossing, fused plan median= 15162.0 ns [min 14976.0 .. max 55083.0] n=51 +> (2/3) hydrate 65536 Row objects median= 658842.0 ns [min 594094.0 .. max 1085753.0] n=51 +> (2/3) scan the materialised objects median= 87213.0 ns [min 83223.0 .. max 135141.0] n=51 +> (2/3) hydrate THEN scan (honest total) median= 871361.0 ns [min 787060.0 .. max 1483819.0] n=51 diff --git a/valhalla-lab/results/stable-default.txt b/valhalla-lab/results/stable-default.txt index 3f30f78..5d91d9c 100644 --- a/valhalla-lab/results/stable-default.txt +++ b/valhalla-lab/results/stable-default.txt @@ -3,10 +3,10 @@ lance-graph-java :: valhalla lab platform stable java.vm.version 26.0.2+10-55 java.vendor.version - -jvm args [--enable-native-access=ALL-UNNAMED, -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so] +jvm args [--enable-native-access=ALL-UNNAMED, -Dstdout.encoding=UTF-8, -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so] -== (a) SEMANTIC TRUTH ? is identity observable? ============================== -platform stable ? stable-record vocabulary; arrays are reference arrays; fields are references +== (a) SEMANTIC TRUTH — is identity observable? ============================== +platform stable — stable-record vocabulary; arrays are reference arrays; fields are references LaneId.class.isValue() false Ordinal.class.isValue() false MaskId.class.isValue() false @@ -24,7 +24,7 @@ array flatness UNKNOWN(no ValueClass API on a stab array slot accepts null true synchronized(x) legality legal but never used by the production API -== FLATTENING CLIFF ? which payload shapes does the VM flatten? ============== +== FLATTENING CLIFF — which payload shapes does the VM flatten? ============== platform stable note payload = declared field bytes, ignoring any header LaneId 1 int payload= 4 B array=UNKNOWN(no ValueClass API on a stable JDK; a reference array is never flat) @@ -33,14 +33,14 @@ MaskId 1 long payload= 8 B array=UNKNOWN(no ValueClass API on a RowRange 2 long payload=16 B array=UNKNOWN(no ValueClass API on a stable JDK; a reference array is never flat) Row 1 long + 2 int payload=16 B array=UNKNOWN(no ValueClass API on a stable JDK; a reference array is never flat) -== (b)/(c) REPRESENTATION ? allocation, arrays, fields, arguments ============ +== (b)/(c) REPRESENTATION — allocation, arrays, fields, arguments ============ platform stable allocation instrument baseline 0 B N (operations per measurement) 1000000 construct N LaneId, store into array 15.26 MiB ... per LaneId 16.00 B -construct N LaneId, never escaping 6.71 MiB - ... per LaneId 7.03 B +construct N LaneId, never escaping 7.22 MiB + ... per LaneId 7.57 B LaneId[1024] flatness UNKNOWN(no ValueClass API on a stable JDK; a reference array is never flat) allocate+fill LaneId[N] (array + elements) 19.07 MiB ... per element 20.00 B @@ -50,21 +50,21 @@ Descriptor kind identity class with two reference f Descriptor fields null-restricted false construct N Descriptor (2 wrappers each) 53.41 MiB ... per Descriptor 56.00 B -pass 2 wrappers through 3 call levels 7.90 MiB - ... per call 8.29 B -read 65,536 LaneId from array median= 44182.0 ns [min 41690.0 .. max 59914.0] n=51 +pass 2 wrappers through 3 call levels 8.90 MiB + ... per call 9.33 B +read 65,536 LaneId from array median= 43739.0 ns [min 42371.0 .. max 73206.0] n=51 native runtime lance-graph native runtime: abi 0.1, simd ndarray::simd avx512, profile release, library /home/user/lance-graph-java/target/release/liblgj_abi.so -== FFM ADDRESSING ? is the wrapper free where it touches native memory? ====== +== FFM ADDRESSING — is the wrapper free where it touches native memory? ====== platform stable sum (identical across all three) 6929623 -bare long index median= 59148.0 ns [min 59077.0 .. max 114952.0] n=51 -RowRange bounds (wrapper hoisted) median= 47952.0 ns [min 47759.0 .. max 65535.0] n=51 +bare long index median= 59836.0 ns [min 59076.0 .. max 125353.0] n=51 +RowRange bounds (wrapper hoisted) median= 47964.0 ns [min 47876.0 .. max 168118.0] n=51 per-element wrapper: bytes allocated 1.00 MiB ... per element 16.00 B -Ordinal built per element median= 50062.0 ns [min 49790.0 .. max 61194.0] n=51 +Ordinal built per element median= 50732.0 ns [min 50046.0 .. max 99100.0] n=51 -== THE THESIS ? 65,536 entities, three representations ======================= +== THE THESIS — 65,536 entities, three representations ======================= platform stable rows 65536 question count(class==7 AND value>100) and sum(value) @@ -72,20 +72,20 @@ answer (identical across all paths) 2173 rows, sum 499246 selectivity 3.32% == heap cost =============================================================== -(1) native ? Java bytes allocated (warm) 816 B per query, for the fluent chain itself -(1) native ? Java objects per row 0 -(1) native ? native lane bytes 1.00 MiB (u64 id + u32 class + i32 value) -(1) native ? mask bytes 8.0 KiB (1 bit per row, packed) -(2)/(3) hydrate 65536 Row ? allocated 2.00 MiB +(1) native — Java bytes allocated (warm) 816 B per query, for the fluent chain itself +(1) native — Java objects per row 0 +(1) native — native lane bytes 1.00 MiB (u64 id + u32 class + i32 value) +(1) native — mask bytes 8.0 KiB (1 bit per row, packed) +(2)/(3) hydrate 65536 Row — allocated 2.00 MiB ... per row 32.00 B array flatness UNKNOWN(no ValueClass API on a stable JDK; a reference array is never flat) ratio vs native lane bytes 2.00x retained heap (APPROX, gc-delta) 2.25 MiB == time to answer the question ============================================= -(1) native one crossing, fused plan median= 16378.0 ns [min 15962.0 .. max 33819.0] n=51 -(2/3) hydrate 65536 Row objects median= 603139.0 ns [min 529172.0 .. max 5246059.0] n=51 -(2/3) scan the materialised objects median= 151886.0 ns [min 112687.0 .. max 201271.0] n=51 -(2/3) hydrate THEN scan (honest total) median= 788227.0 ns [min 710221.0 .. max 1146017.0] n=51 +(1) native one crossing, fused plan median= 16040.0 ns [min 15900.0 .. max 31577.0] n=51 +(2/3) hydrate 65536 Row objects median= 1612494.0 ns [min 499222.0 .. max 4777342.0] n=51 +(2/3) scan the materialised objects median= 128057.0 ns [min 112482.0 .. max 227766.0] n=51 +(2/3) hydrate THEN scan (honest total) median= 704743.0 ns [min 631780.0 .. max 974235.0] n=51 lab complete. diff --git a/valhalla-lab/results/stable-noea.txt b/valhalla-lab/results/stable-noea.txt index 371624c..9a62f99 100644 --- a/valhalla-lab/results/stable-noea.txt +++ b/valhalla-lab/results/stable-noea.txt @@ -3,10 +3,10 @@ lance-graph-java :: valhalla lab platform stable java.vm.version 26.0.2+10-55 java.vendor.version - -jvm args [--enable-native-access=ALL-UNNAMED, -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so, -XX:-DoEscapeAnalysis] +jvm args [--enable-native-access=ALL-UNNAMED, -Dstdout.encoding=UTF-8, -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so, -XX:-DoEscapeAnalysis] -== (a) SEMANTIC TRUTH ? is identity observable? ============================== -platform stable ? stable-record vocabulary; arrays are reference arrays; fields are references +== (a) SEMANTIC TRUTH — is identity observable? ============================== +platform stable — stable-record vocabulary; arrays are reference arrays; fields are references LaneId.class.isValue() false Ordinal.class.isValue() false MaskId.class.isValue() false @@ -24,7 +24,7 @@ array flatness UNKNOWN(no ValueClass API on a stab array slot accepts null true synchronized(x) legality legal but never used by the production API -== FLATTENING CLIFF ? which payload shapes does the VM flatten? ============== +== FLATTENING CLIFF — which payload shapes does the VM flatten? ============== platform stable note payload = declared field bytes, ignoring any header LaneId 1 int payload= 4 B array=UNKNOWN(no ValueClass API on a stable JDK; a reference array is never flat) @@ -33,7 +33,7 @@ MaskId 1 long payload= 8 B array=UNKNOWN(no ValueClass API on a RowRange 2 long payload=16 B array=UNKNOWN(no ValueClass API on a stable JDK; a reference array is never flat) Row 1 long + 2 int payload=16 B array=UNKNOWN(no ValueClass API on a stable JDK; a reference array is never flat) -== (b)/(c) REPRESENTATION ? allocation, arrays, fields, arguments ============ +== (b)/(c) REPRESENTATION — allocation, arrays, fields, arguments ============ platform stable allocation instrument baseline 0 B N (operations per measurement) 1000000 @@ -52,19 +52,19 @@ construct N Descriptor (2 wrappers each) 53.41 MiB ... per Descriptor 56.00 B pass 2 wrappers through 3 call levels 30.52 MiB ... per call 32.00 B -read 65,536 LaneId from array median= 44861.0 ns [min 42156.0 .. max 69264.0] n=51 +read 65,536 LaneId from array median= 39359.0 ns [min 39266.0 .. max 63844.0] n=51 native runtime lance-graph native runtime: abi 0.1, simd ndarray::simd avx512, profile release, library /home/user/lance-graph-java/target/release/liblgj_abi.so -== FFM ADDRESSING ? is the wrapper free where it touches native memory? ====== +== FFM ADDRESSING — is the wrapper free where it touches native memory? ====== platform stable sum (identical across all three) 6929623 -bare long index median= 59417.0 ns [min 59067.0 .. max 140131.0] n=51 -RowRange bounds (wrapper hoisted) median= 48136.0 ns [min 47847.0 .. max 105233.0] n=51 +bare long index median= 59174.0 ns [min 59067.0 .. max 117139.0] n=51 +RowRange bounds (wrapper hoisted) median= 47949.0 ns [min 47870.0 .. max 61164.0] n=51 per-element wrapper: bytes allocated 1.00 MiB ... per element 16.00 B -Ordinal built per element median= 5166233.0 ns [min 4636587.0 .. max 10323741.0] n=51 +Ordinal built per element median= 621827.0 ns [min 584504.0 .. max 873212.0] n=51 -== THE THESIS ? 65,536 entities, three representations ======================= +== THE THESIS — 65,536 entities, three representations ======================= platform stable rows 65536 question count(class==7 AND value>100) and sum(value) @@ -72,20 +72,20 @@ answer (identical across all paths) 2173 rows, sum 499246 selectivity 3.32% == heap cost =============================================================== -(1) native ? Java bytes allocated (warm) 816 B per query, for the fluent chain itself -(1) native ? Java objects per row 0 -(1) native ? native lane bytes 1.00 MiB (u64 id + u32 class + i32 value) -(1) native ? mask bytes 8.0 KiB (1 bit per row, packed) -(2)/(3) hydrate 65536 Row ? allocated 2.00 MiB +(1) native — Java bytes allocated (warm) 816 B per query, for the fluent chain itself +(1) native — Java objects per row 0 +(1) native — native lane bytes 1.00 MiB (u64 id + u32 class + i32 value) +(1) native — mask bytes 8.0 KiB (1 bit per row, packed) +(2)/(3) hydrate 65536 Row — allocated 2.00 MiB ... per row 32.00 B array flatness UNKNOWN(no ValueClass API on a stable JDK; a reference array is never flat) ratio vs native lane bytes 2.00x retained heap (APPROX, gc-delta) 2.22 MiB == time to answer the question ============================================= -(1) native one crossing, fused plan median= 17631.0 ns [min 15693.0 .. max 40703.0] n=51 -(2/3) hydrate 65536 Row objects median= 575654.0 ns [min 513071.0 .. max 4428601.0] n=51 -(2/3) scan the materialised objects median= 129828.0 ns [min 108841.0 .. max 159354.0] n=51 -(2/3) hydrate THEN scan (honest total) median= 703240.0 ns [min 642514.0 .. max 789762.0] n=51 +(1) native one crossing, fused plan median= 15431.0 ns [min 15368.0 .. max 28346.0] n=51 +(2/3) hydrate 65536 Row objects median= 537476.0 ns [min 478467.0 .. max 4136743.0] n=51 +(2/3) scan the materialised objects median= 118276.0 ns [min 108392.0 .. max 199142.0] n=51 +(2/3) hydrate THEN scan (honest total) median= 805562.0 ns [min 689612.0 .. max 1107352.0] n=51 lab complete. diff --git a/valhalla-lab/results/valhalla-default.txt b/valhalla-lab/results/valhalla-default.txt index 29b202f..43ebf51 100644 --- a/valhalla-lab/results/valhalla-default.txt +++ b/valhalla-lab/results/valhalla-default.txt @@ -3,10 +3,10 @@ lance-graph-java :: valhalla lab platform valhalla java.vm.version 27-jep401ea3+1-1 java.vendor.version - -jvm args [--enable-native-access=ALL-UNNAMED, -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so, --enable-preview, --add-exports=java.base/jdk.internal.vm.annotation=ALL-UNNAMED, --add-exports=java.base/jdk.internal.value=ALL-UNNAMED] +jvm args [--enable-native-access=ALL-UNNAMED, -Dstdout.encoding=UTF-8, -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so, --enable-preview, --add-exports=java.base/jdk.internal.vm.annotation=ALL-UNNAMED, --add-exports=java.base/jdk.internal.value=ALL-UNNAMED] -== (a) SEMANTIC TRUTH ? is identity observable? ============================== -platform valhalla ? value-record vocabulary; null-restricted non-atomic arrays; @NullRestricted fields +== (a) SEMANTIC TRUTH — is identity observable? ============================== +platform valhalla — value-record vocabulary; null-restricted non-atomic arrays; @NullRestricted fields LaneId.class.isValue() true Ordinal.class.isValue() true MaskId.class.isValue() true @@ -24,7 +24,7 @@ array flatness FLAT array slot accepts null false synchronized(x) legality COMPILE ERROR under Valhalla (required: a type with identity) -== FLATTENING CLIFF ? which payload shapes does the VM flatten? ============== +== FLATTENING CLIFF — which payload shapes does the VM flatten? ============== platform valhalla note payload = declared field bytes, ignoring any header LaneId 1 int payload= 4 B array=FLAT @@ -33,38 +33,38 @@ MaskId 1 long payload= 8 B array=FLAT RowRange 2 long payload=16 B array=NOT-FLAT Row 1 long + 2 int payload=16 B array=NOT-FLAT -== (b)/(c) REPRESENTATION ? allocation, arrays, fields, arguments ============ +== (b)/(c) REPRESENTATION — allocation, arrays, fields, arguments ============ platform valhalla allocation instrument baseline 0 B N (operations per measurement) 1000000 -construct N LaneId, store into array 2.75 MiB - ... per LaneId 2.89 B -construct N LaneId, never escaping 7.63 MiB - ... per LaneId 8.00 B +construct N LaneId, store into array 3.14 MiB + ... per LaneId 3.29 B +construct N LaneId, never escaping 7.29 MiB + ... per LaneId 7.64 B LaneId[1024] flatness FLAT -allocate+fill LaneId[N] (array + elements) 6.57 MiB - ... per element 6.89 B +allocate+fill LaneId[N] (array + elements) 6.56 MiB + ... per element 6.87 B bare LaneId[N] with no elements stored 3.81 MiB ... per slot 4.00 B Descriptor kind value class with two @NullRestricted value fields Descriptor fields null-restricted true -construct N Descriptor (2 wrappers each) 37.05 MiB - ... per Descriptor 38.84 B -pass 2 wrappers through 3 call levels 9.97 MiB - ... per call 10.45 B -read 65,536 LaneId from array median= 5349.0 ns [min 5179.0 .. max 11356.0] n=51 +construct N Descriptor (2 wrappers each) 34.81 MiB + ... per Descriptor 36.50 B +pass 2 wrappers through 3 call levels 10.36 MiB + ... per call 10.87 B +read 65,536 LaneId from array median= 5339.0 ns [min 5262.0 .. max 9259.0] n=51 native runtime lance-graph native runtime: abi 0.1, simd ndarray::simd avx512, profile release, library /home/user/lance-graph-java/target/release/liblgj_abi.so -== FFM ADDRESSING ? is the wrapper free where it touches native memory? ====== +== FFM ADDRESSING — is the wrapper free where it touches native memory? ====== platform valhalla sum (identical across all three) 6929623 -bare long index median= 63304.0 ns [min 59079.0 .. max 131743.0] n=51 -RowRange bounds (wrapper hoisted) median= 57557.0 ns [min 56212.0 .. max 87932.0] n=51 +bare long index median= 59134.0 ns [min 59058.0 .. max 122610.0] n=51 +RowRange bounds (wrapper hoisted) median= 56975.0 ns [min 56642.0 .. max 112526.0] n=51 per-element wrapper: bytes allocated 1.50 MiB ... per element 24.00 B -Ordinal built per element median= 50021.0 ns [min 49762.0 .. max 75999.0] n=51 +Ordinal built per element median= 49851.0 ns [min 49725.0 .. max 70877.0] n=51 -== THE THESIS ? 65,536 entities, three representations ======================= +== THE THESIS — 65,536 entities, three representations ======================= platform valhalla rows 65536 question count(class==7 AND value>100) and sum(value) @@ -72,20 +72,20 @@ answer (identical across all paths) 2173 rows, sum 499246 selectivity 3.32% == heap cost =============================================================== -(1) native ? Java bytes allocated (warm) 816 B per query, for the fluent chain itself -(1) native ? Java objects per row 0 -(1) native ? native lane bytes 1.00 MiB (u64 id + u32 class + i32 value) -(1) native ? mask bytes 8.0 KiB (1 bit per row, packed) -(2)/(3) hydrate 65536 Row ? allocated 2.50 MiB +(1) native — Java bytes allocated (warm) 816 B per query, for the fluent chain itself +(1) native — Java objects per row 0 +(1) native — native lane bytes 1.00 MiB (u64 id + u32 class + i32 value) +(1) native — mask bytes 8.0 KiB (1 bit per row, packed) +(2)/(3) hydrate 65536 Row — allocated 2.50 MiB ... per row 40.00 B array flatness NOT-FLAT ratio vs native lane bytes 2.50x retained heap (APPROX, gc-delta) 2.75 MiB == time to answer the question ============================================= -(1) native one crossing, fused plan median= 18805.0 ns [min 15515.0 .. max 38457.0] n=51 -(2/3) hydrate 65536 Row objects median= 768624.0 ns [min 645509.0 .. max 1229947.0] n=51 -(2/3) scan the materialised objects median= 91178.0 ns [min 83390.0 .. max 141648.0] n=51 -(2/3) hydrate THEN scan (honest total) median= 898955.0 ns [min 822997.0 .. max 5059624.0] n=51 +(1) native one crossing, fused plan median= 15162.0 ns [min 14976.0 .. max 55083.0] n=51 +(2/3) hydrate 65536 Row objects median= 658842.0 ns [min 594094.0 .. max 1085753.0] n=51 +(2/3) scan the materialised objects median= 87213.0 ns [min 83223.0 .. max 135141.0] n=51 +(2/3) hydrate THEN scan (honest total) median= 871361.0 ns [min 787060.0 .. max 1483819.0] n=51 lab complete. diff --git a/valhalla-lab/results/valhalla-noarrayflat.txt b/valhalla-lab/results/valhalla-noarrayflat.txt index 6c75ae5..be708cb 100644 --- a/valhalla-lab/results/valhalla-noarrayflat.txt +++ b/valhalla-lab/results/valhalla-noarrayflat.txt @@ -3,10 +3,10 @@ lance-graph-java :: valhalla lab platform valhalla java.vm.version 27-jep401ea3+1-1 java.vendor.version - -jvm args [--enable-native-access=ALL-UNNAMED, -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so, --enable-preview, --add-exports=java.base/jdk.internal.vm.annotation=ALL-UNNAMED, --add-exports=java.base/jdk.internal.value=ALL-UNNAMED, -XX:+UnlockDiagnosticVMOptions, -XX:-UseArrayFlattening] +jvm args [--enable-native-access=ALL-UNNAMED, -Dstdout.encoding=UTF-8, -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so, --enable-preview, --add-exports=java.base/jdk.internal.vm.annotation=ALL-UNNAMED, --add-exports=java.base/jdk.internal.value=ALL-UNNAMED, -XX:+UnlockDiagnosticVMOptions, -XX:-UseArrayFlattening] -== (a) SEMANTIC TRUTH ? is identity observable? ============================== -platform valhalla ? value-record vocabulary; null-restricted non-atomic arrays; @NullRestricted fields +== (a) SEMANTIC TRUTH — is identity observable? ============================== +platform valhalla — value-record vocabulary; null-restricted non-atomic arrays; @NullRestricted fields LaneId.class.isValue() true Ordinal.class.isValue() true MaskId.class.isValue() true @@ -24,7 +24,7 @@ array flatness NOT-FLAT array slot accepts null false synchronized(x) legality COMPILE ERROR under Valhalla (required: a type with identity) -== FLATTENING CLIFF ? which payload shapes does the VM flatten? ============== +== FLATTENING CLIFF — which payload shapes does the VM flatten? ============== platform valhalla note payload = declared field bytes, ignoring any header LaneId 1 int payload= 4 B array=NOT-FLAT @@ -33,14 +33,14 @@ MaskId 1 long payload= 8 B array=NOT-FLAT RowRange 2 long payload=16 B array=NOT-FLAT Row 1 long + 2 int payload=16 B array=NOT-FLAT -== (b)/(c) REPRESENTATION ? allocation, arrays, fields, arguments ============ +== (b)/(c) REPRESENTATION — allocation, arrays, fields, arguments ============ platform valhalla allocation instrument baseline 0 B N (operations per measurement) 1000000 construct N LaneId, store into array 22.89 MiB ... per LaneId 24.00 B -construct N LaneId, never escaping 8.22 MiB - ... per LaneId 8.62 B +construct N LaneId, never escaping 7.26 MiB + ... per LaneId 7.61 B LaneId[1024] flatness NOT-FLAT allocate+fill LaneId[N] (array + elements) 26.70 MiB ... per element 28.00 B @@ -48,23 +48,23 @@ bare LaneId[N] with no elements stored 3.81 MiB ... per slot 4.00 B Descriptor kind value class with two @NullRestricted value fields Descriptor fields null-restricted true -construct N Descriptor (2 wrappers each) 38.37 MiB - ... per Descriptor 40.23 B -pass 2 wrappers through 3 call levels 11.49 MiB - ... per call 12.05 B -read 65,536 LaneId from array median= 53465.0 ns [min 49219.0 .. max 88865.0] n=51 +construct N Descriptor (2 wrappers each) 39.11 MiB + ... per Descriptor 41.01 B +pass 2 wrappers through 3 call levels 9.61 MiB + ... per call 10.07 B +read 65,536 LaneId from array median= 47245.0 ns [min 46645.0 .. max 80026.0] n=51 native runtime lance-graph native runtime: abi 0.1, simd ndarray::simd avx512, profile release, library /home/user/lance-graph-java/target/release/liblgj_abi.so -== FFM ADDRESSING ? is the wrapper free where it touches native memory? ====== +== FFM ADDRESSING — is the wrapper free where it touches native memory? ====== platform valhalla sum (identical across all three) 6929623 -bare long index median= 59142.0 ns [min 59071.0 .. max 114270.0] n=51 -RowRange bounds (wrapper hoisted) median= 56997.0 ns [min 56660.0 .. max 81796.0] n=51 +bare long index median= 59875.0 ns [min 59085.0 .. max 132585.0] n=51 +RowRange bounds (wrapper hoisted) median= 56943.0 ns [min 56683.0 .. max 82939.0] n=51 per-element wrapper: bytes allocated 1.50 MiB ... per element 24.00 B -Ordinal built per element median= 51224.0 ns [min 49782.0 .. max 91443.0] n=51 +Ordinal built per element median= 49934.0 ns [min 49758.0 .. max 94648.0] n=51 -== THE THESIS ? 65,536 entities, three representations ======================= +== THE THESIS — 65,536 entities, three representations ======================= platform valhalla rows 65536 question count(class==7 AND value>100) and sum(value) @@ -72,20 +72,20 @@ answer (identical across all paths) 2173 rows, sum 499246 selectivity 3.32% == heap cost =============================================================== -(1) native ? Java bytes allocated (warm) 816 B per query, for the fluent chain itself -(1) native ? Java objects per row 0 -(1) native ? native lane bytes 1.00 MiB (u64 id + u32 class + i32 value) -(1) native ? mask bytes 8.0 KiB (1 bit per row, packed) -(2)/(3) hydrate 65536 Row ? allocated 2.50 MiB - ... per row 40.00 B +(1) native — Java bytes allocated (warm) 816 B per query, for the fluent chain itself +(1) native — Java objects per row 0 +(1) native — native lane bytes 1.00 MiB (u64 id + u32 class + i32 value) +(1) native — mask bytes 8.0 KiB (1 bit per row, packed) +(2)/(3) hydrate 65536 Row — allocated 2.50 MiB + ... per row 40.01 B array flatness NOT-FLAT ratio vs native lane bytes 2.50x retained heap (APPROX, gc-delta) 2.75 MiB == time to answer the question ============================================= -(1) native one crossing, fused plan median= 14946.0 ns [min 14841.0 .. max 49967.0] n=51 -(2/3) hydrate 65536 Row objects median= 1708590.0 ns [min 610808.0 .. max 8047970.0] n=51 -(2/3) scan the materialised objects median= 148382.0 ns [min 119556.0 .. max 229605.0] n=51 -(2/3) hydrate THEN scan (honest total) median= 843903.0 ns [min 781103.0 .. max 1306914.0] n=51 +(1) native one crossing, fused plan median= 16317.0 ns [min 15502.0 .. max 36120.0] n=51 +(2/3) hydrate 65536 Row objects median= 664702.0 ns [min 597322.0 .. max 1009820.0] n=51 +(2/3) scan the materialised objects median= 152176.0 ns [min 115700.0 .. max 238478.0] n=51 +(2/3) hydrate THEN scan (honest total) median= 1983973.0 ns [min 828040.0 .. max 6143362.0] n=51 lab complete. diff --git a/valhalla-lab/results/valhalla-noea.txt b/valhalla-lab/results/valhalla-noea.txt index 529ee73..79574fa 100644 --- a/valhalla-lab/results/valhalla-noea.txt +++ b/valhalla-lab/results/valhalla-noea.txt @@ -3,10 +3,10 @@ lance-graph-java :: valhalla lab platform valhalla java.vm.version 27-jep401ea3+1-1 java.vendor.version - -jvm args [--enable-native-access=ALL-UNNAMED, -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so, --enable-preview, --add-exports=java.base/jdk.internal.vm.annotation=ALL-UNNAMED, --add-exports=java.base/jdk.internal.value=ALL-UNNAMED, -XX:-DoEscapeAnalysis] +jvm args [--enable-native-access=ALL-UNNAMED, -Dstdout.encoding=UTF-8, -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so, --enable-preview, --add-exports=java.base/jdk.internal.vm.annotation=ALL-UNNAMED, --add-exports=java.base/jdk.internal.value=ALL-UNNAMED, -XX:-DoEscapeAnalysis] -== (a) SEMANTIC TRUTH ? is identity observable? ============================== -platform valhalla ? value-record vocabulary; null-restricted non-atomic arrays; @NullRestricted fields +== (a) SEMANTIC TRUTH — is identity observable? ============================== +platform valhalla — value-record vocabulary; null-restricted non-atomic arrays; @NullRestricted fields LaneId.class.isValue() true Ordinal.class.isValue() true MaskId.class.isValue() true @@ -24,7 +24,7 @@ array flatness FLAT array slot accepts null false synchronized(x) legality COMPILE ERROR under Valhalla (required: a type with identity) -== FLATTENING CLIFF ? which payload shapes does the VM flatten? ============== +== FLATTENING CLIFF — which payload shapes does the VM flatten? ============== platform valhalla note payload = declared field bytes, ignoring any header LaneId 1 int payload= 4 B array=FLAT @@ -33,12 +33,12 @@ MaskId 1 long payload= 8 B array=FLAT RowRange 2 long payload=16 B array=NOT-FLAT Row 1 long + 2 int payload=16 B array=NOT-FLAT -== (b)/(c) REPRESENTATION ? allocation, arrays, fields, arguments ============ +== (b)/(c) REPRESENTATION — allocation, arrays, fields, arguments ============ platform valhalla allocation instrument baseline 0 B N (operations per measurement) 1000000 -construct N LaneId, store into array 3.86 MiB - ... per LaneId 4.05 B +construct N LaneId, store into array 3.90 MiB + ... per LaneId 4.09 B construct N LaneId, never escaping 24.41 MiB ... per LaneId 25.60 B LaneId[1024] flatness FLAT @@ -48,23 +48,23 @@ bare LaneId[N] with no elements stored 3.81 MiB ... per slot 4.00 B Descriptor kind value class with two @NullRestricted value fields Descriptor fields null-restricted true -construct N Descriptor (2 wrappers each) 38.98 MiB - ... per Descriptor 40.87 B -pass 2 wrappers through 3 call levels 29.57 MiB - ... per call 31.00 B -read 65,536 LaneId from array median= 5369.0 ns [min 5321.0 .. max 30666.0] n=51 +construct N Descriptor (2 wrappers each) 38.70 MiB + ... per Descriptor 40.58 B +pass 2 wrappers through 3 call levels 29.47 MiB + ... per call 30.90 B +read 65,536 LaneId from array median= 4954.0 ns [min 4910.0 .. max 5998.0] n=51 native runtime lance-graph native runtime: abi 0.1, simd ndarray::simd avx512, profile release, library /home/user/lance-graph-java/target/release/liblgj_abi.so -== FFM ADDRESSING ? is the wrapper free where it touches native memory? ====== +== FFM ADDRESSING — is the wrapper free where it touches native memory? ====== platform valhalla sum (identical across all three) 6929623 -bare long index median= 61840.0 ns [min 59069.0 .. max 128839.0] n=51 -RowRange bounds (wrapper hoisted) median= 60149.0 ns [min 56823.0 .. max 74673.0] n=51 +bare long index median= 59216.0 ns [min 59058.0 .. max 112785.0] n=51 +RowRange bounds (wrapper hoisted) median= 56995.0 ns [min 56663.0 .. max 126870.0] n=51 per-element wrapper: bytes allocated 3.00 MiB ... per element 48.00 B -Ordinal built per element median= 94346.0 ns [min 91210.0 .. max 133533.0] n=51 +Ordinal built per element median= 91383.0 ns [min 91100.0 .. max 120947.0] n=51 -== THE THESIS ? 65,536 entities, three representations ======================= +== THE THESIS — 65,536 entities, three representations ======================= platform valhalla rows 65536 question count(class==7 AND value>100) and sum(value) @@ -72,20 +72,20 @@ answer (identical across all paths) 2173 rows, sum 499246 selectivity 3.32% == heap cost =============================================================== -(1) native ? Java bytes allocated (warm) 816 B per query, for the fluent chain itself -(1) native ? Java objects per row 0 -(1) native ? native lane bytes 1.00 MiB (u64 id + u32 class + i32 value) -(1) native ? mask bytes 8.0 KiB (1 bit per row, packed) -(2)/(3) hydrate 65536 Row ? allocated 2.50 MiB +(1) native — Java bytes allocated (warm) 816 B per query, for the fluent chain itself +(1) native — Java objects per row 0 +(1) native — native lane bytes 1.00 MiB (u64 id + u32 class + i32 value) +(1) native — mask bytes 8.0 KiB (1 bit per row, packed) +(2)/(3) hydrate 65536 Row — allocated 2.50 MiB ... per row 40.00 B array flatness NOT-FLAT ratio vs native lane bytes 2.50x retained heap (APPROX, gc-delta) 2.75 MiB == time to answer the question ============================================= -(1) native one crossing, fused plan median= 15885.0 ns [min 15694.0 .. max 32647.0] n=51 -(2/3) hydrate 65536 Row objects median= 1187577.0 ns [min 1044870.0 .. max 2002465.0] n=51 -(2/3) scan the materialised objects median= 74699.0 ns [min 63609.0 .. max 129188.0] n=51 -(2/3) hydrate THEN scan (honest total) median= 3267205.0 ns [min 1374933.0 .. max 9370919.0] n=51 +(1) native one crossing, fused plan median= 20603.0 ns [min 15179.0 .. max 59260.0] n=51 +(2/3) hydrate 65536 Row objects median= 1086237.0 ns [min 1010540.0 .. max 4481968.0] n=51 +(2/3) scan the materialised objects median= 80753.0 ns [min 75090.0 .. max 140418.0] n=51 +(2/3) hydrate THEN scan (honest total) median=25828809.0 ns [min 6057980.0 .. max 39247669.0] n=51 lab complete. diff --git a/valhalla-lab/results/valhalla-nofieldflat.txt b/valhalla-lab/results/valhalla-nofieldflat.txt index 7af9d7b..592d4d9 100644 --- a/valhalla-lab/results/valhalla-nofieldflat.txt +++ b/valhalla-lab/results/valhalla-nofieldflat.txt @@ -3,10 +3,10 @@ lance-graph-java :: valhalla lab platform valhalla java.vm.version 27-jep401ea3+1-1 java.vendor.version - -jvm args [--enable-native-access=ALL-UNNAMED, -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so, --enable-preview, --add-exports=java.base/jdk.internal.vm.annotation=ALL-UNNAMED, --add-exports=java.base/jdk.internal.value=ALL-UNNAMED, -XX:+UnlockDiagnosticVMOptions, -XX:-UseFieldFlattening] +jvm args [--enable-native-access=ALL-UNNAMED, -Dstdout.encoding=UTF-8, -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so, --enable-preview, --add-exports=java.base/jdk.internal.vm.annotation=ALL-UNNAMED, --add-exports=java.base/jdk.internal.value=ALL-UNNAMED, -XX:+UnlockDiagnosticVMOptions, -XX:-UseFieldFlattening] -== (a) SEMANTIC TRUTH ? is identity observable? ============================== -platform valhalla ? value-record vocabulary; null-restricted non-atomic arrays; @NullRestricted fields +== (a) SEMANTIC TRUTH — is identity observable? ============================== +platform valhalla — value-record vocabulary; null-restricted non-atomic arrays; @NullRestricted fields LaneId.class.isValue() true Ordinal.class.isValue() true MaskId.class.isValue() true @@ -24,7 +24,7 @@ array flatness FLAT array slot accepts null false synchronized(x) legality COMPILE ERROR under Valhalla (required: a type with identity) -== FLATTENING CLIFF ? which payload shapes does the VM flatten? ============== +== FLATTENING CLIFF — which payload shapes does the VM flatten? ============== platform valhalla note payload = declared field bytes, ignoring any header LaneId 1 int payload= 4 B array=FLAT @@ -33,38 +33,38 @@ MaskId 1 long payload= 8 B array=FLAT RowRange 2 long payload=16 B array=NOT-FLAT Row 1 long + 2 int payload=16 B array=NOT-FLAT -== (b)/(c) REPRESENTATION ? allocation, arrays, fields, arguments ============ +== (b)/(c) REPRESENTATION — allocation, arrays, fields, arguments ============ platform valhalla allocation instrument baseline 0 B N (operations per measurement) 1000000 -construct N LaneId, store into array 2.75 MiB - ... per LaneId 2.89 B -construct N LaneId, never escaping 8.22 MiB - ... per LaneId 8.61 B +construct N LaneId, store into array 2.58 MiB + ... per LaneId 2.71 B +construct N LaneId, never escaping 6.68 MiB + ... per LaneId 7.00 B LaneId[1024] flatness FLAT -allocate+fill LaneId[N] (array + elements) 6.58 MiB - ... per element 6.90 B +allocate+fill LaneId[N] (array + elements) 6.40 MiB + ... per element 6.71 B bare LaneId[N] with no elements stored 3.81 MiB ... per slot 4.00 B Descriptor kind value class with two @NullRestricted value fields Descriptor fields null-restricted true construct N Descriptor (2 wrappers each) 76.29 MiB ... per Descriptor 80.00 B -pass 2 wrappers through 3 call levels 11.14 MiB - ... per call 11.68 B -read 65,536 LaneId from array median= 4959.0 ns [min 4870.0 .. max 5886.0] n=51 +pass 2 wrappers through 3 call levels 11.85 MiB + ... per call 12.43 B +read 65,536 LaneId from array median= 4952.0 ns [min 4878.0 .. max 6129.0] n=51 native runtime lance-graph native runtime: abi 0.1, simd ndarray::simd avx512, profile release, library /home/user/lance-graph-java/target/release/liblgj_abi.so -== FFM ADDRESSING ? is the wrapper free where it touches native memory? ====== +== FFM ADDRESSING — is the wrapper free where it touches native memory? ====== platform valhalla sum (identical across all three) 6929623 -bare long index median= 59187.0 ns [min 59075.0 .. max 123125.0] n=51 -RowRange bounds (wrapper hoisted) median= 56980.0 ns [min 56652.0 .. max 73287.0] n=51 +bare long index median= 59291.0 ns [min 59072.0 .. max 127512.0] n=51 +RowRange bounds (wrapper hoisted) median= 56959.0 ns [min 56598.0 .. max 88827.0] n=51 per-element wrapper: bytes allocated 1.50 MiB ... per element 24.00 B -Ordinal built per element median= 49851.0 ns [min 49727.0 .. max 66813.0] n=51 +Ordinal built per element median= 53651.0 ns [min 49983.0 .. max 89971.0] n=51 -== THE THESIS ? 65,536 entities, three representations ======================= +== THE THESIS — 65,536 entities, three representations ======================= platform valhalla rows 65536 question count(class==7 AND value>100) and sum(value) @@ -72,20 +72,20 @@ answer (identical across all paths) 2173 rows, sum 499246 selectivity 3.32% == heap cost =============================================================== -(1) native ? Java bytes allocated (warm) 816 B per query, for the fluent chain itself -(1) native ? Java objects per row 0 -(1) native ? native lane bytes 1.00 MiB (u64 id + u32 class + i32 value) -(1) native ? mask bytes 8.0 KiB (1 bit per row, packed) -(2)/(3) hydrate 65536 Row ? allocated 2.50 MiB - ... per row 40.01 B +(1) native — Java bytes allocated (warm) 816 B per query, for the fluent chain itself +(1) native — Java objects per row 0 +(1) native — native lane bytes 1.00 MiB (u64 id + u32 class + i32 value) +(1) native — mask bytes 8.0 KiB (1 bit per row, packed) +(2)/(3) hydrate 65536 Row — allocated 2.50 MiB + ... per row 40.00 B array flatness NOT-FLAT ratio vs native lane bytes 2.50x retained heap (APPROX, gc-delta) 2.75 MiB == time to answer the question ============================================= -(1) native one crossing, fused plan median= 15964.0 ns [min 15779.0 .. max 40503.0] n=51 -(2/3) hydrate 65536 Row objects median= 789127.0 ns [min 628024.0 .. max 4599451.0] n=51 -(2/3) scan the materialised objects median= 78249.0 ns [min 75327.0 .. max 111755.0] n=51 -(2/3) hydrate THEN scan (honest total) median= 1122018.0 ns [min 812442.0 .. max 1695253.0] n=51 +(1) native one crossing, fused plan median= 13900.0 ns [min 13785.0 .. max 32163.0] n=51 +(2/3) hydrate 65536 Row objects median= 685073.0 ns [min 615948.0 .. max 5630226.0] n=51 +(2/3) scan the materialised objects median= 89883.0 ns [min 86979.0 .. max 142069.0] n=51 +(2/3) hydrate THEN scan (honest total) median= 872592.0 ns [min 804643.0 .. max 1338372.0] n=51 lab complete. diff --git a/valhalla-lab/results/valhalla-noflat.txt b/valhalla-lab/results/valhalla-noflat.txt index 5ab9312..4475065 100644 --- a/valhalla-lab/results/valhalla-noflat.txt +++ b/valhalla-lab/results/valhalla-noflat.txt @@ -3,10 +3,10 @@ lance-graph-java :: valhalla lab platform valhalla java.vm.version 27-jep401ea3+1-1 java.vendor.version - -jvm args [--enable-native-access=ALL-UNNAMED, -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so, --enable-preview, --add-exports=java.base/jdk.internal.vm.annotation=ALL-UNNAMED, --add-exports=java.base/jdk.internal.value=ALL-UNNAMED, -XX:+UnlockDiagnosticVMOptions, -XX:-UseArrayFlattening, -XX:-UseFieldFlattening] +jvm args [--enable-native-access=ALL-UNNAMED, -Dstdout.encoding=UTF-8, -Dlgj.library=/home/user/lance-graph-java/target/release/liblgj_abi.so, --enable-preview, --add-exports=java.base/jdk.internal.vm.annotation=ALL-UNNAMED, --add-exports=java.base/jdk.internal.value=ALL-UNNAMED, -XX:+UnlockDiagnosticVMOptions, -XX:-UseArrayFlattening, -XX:-UseFieldFlattening] -== (a) SEMANTIC TRUTH ? is identity observable? ============================== -platform valhalla ? value-record vocabulary; null-restricted non-atomic arrays; @NullRestricted fields +== (a) SEMANTIC TRUTH — is identity observable? ============================== +platform valhalla — value-record vocabulary; null-restricted non-atomic arrays; @NullRestricted fields LaneId.class.isValue() true Ordinal.class.isValue() true MaskId.class.isValue() true @@ -24,7 +24,7 @@ array flatness NOT-FLAT array slot accepts null false synchronized(x) legality COMPILE ERROR under Valhalla (required: a type with identity) -== FLATTENING CLIFF ? which payload shapes does the VM flatten? ============== +== FLATTENING CLIFF — which payload shapes does the VM flatten? ============== platform valhalla note payload = declared field bytes, ignoring any header LaneId 1 int payload= 4 B array=NOT-FLAT @@ -33,14 +33,14 @@ MaskId 1 long payload= 8 B array=NOT-FLAT RowRange 2 long payload=16 B array=NOT-FLAT Row 1 long + 2 int payload=16 B array=NOT-FLAT -== (b)/(c) REPRESENTATION ? allocation, arrays, fields, arguments ============ +== (b)/(c) REPRESENTATION — allocation, arrays, fields, arguments ============ platform valhalla allocation instrument baseline 0 B N (operations per measurement) 1000000 construct N LaneId, store into array 15.26 MiB ... per LaneId 16.00 B -construct N LaneId, never escaping 4.71 MiB - ... per LaneId 4.94 B +construct N LaneId, never escaping 4.70 MiB + ... per LaneId 4.93 B LaneId[1024] flatness NOT-FLAT allocate+fill LaneId[N] (array + elements) 19.07 MiB ... per element 20.00 B @@ -50,21 +50,21 @@ Descriptor kind value class with two @NullRestricte Descriptor fields null-restricted true construct N Descriptor (2 wrappers each) 53.41 MiB ... per Descriptor 56.00 B -pass 2 wrappers through 3 call levels 8.65 MiB - ... per call 9.07 B -read 65,536 LaneId from array median= 43102.0 ns [min 41523.0 .. max 77929.0] n=51 +pass 2 wrappers through 3 call levels 9.41 MiB + ... per call 9.86 B +read 65,536 LaneId from array median= 41431.0 ns [min 41208.0 .. max 59606.0] n=51 native runtime lance-graph native runtime: abi 0.1, simd ndarray::simd avx512, profile release, library /home/user/lance-graph-java/target/release/liblgj_abi.so -== FFM ADDRESSING ? is the wrapper free where it touches native memory? ====== +== FFM ADDRESSING — is the wrapper free where it touches native memory? ====== platform valhalla sum (identical across all three) 6929623 -bare long index median= 59218.0 ns [min 59090.0 .. max 150920.0] n=51 -RowRange bounds (wrapper hoisted) median= 56978.0 ns [min 56635.0 .. max 83751.0] n=51 +bare long index median= 59150.0 ns [min 59081.0 .. max 122825.0] n=51 +RowRange bounds (wrapper hoisted) median= 56919.0 ns [min 56634.0 .. max 79142.0] n=51 per-element wrapper: bytes allocated 1.00 MiB ... per element 16.00 B -Ordinal built per element median= 49935.0 ns [min 49722.0 .. max 83957.0] n=51 +Ordinal built per element median= 49921.0 ns [min 49739.0 .. max 69259.0] n=51 -== THE THESIS ? 65,536 entities, three representations ======================= +== THE THESIS — 65,536 entities, three representations ======================= platform valhalla rows 65536 question count(class==7 AND value>100) and sum(value) @@ -72,20 +72,20 @@ answer (identical across all paths) 2173 rows, sum 499246 selectivity 3.32% == heap cost =============================================================== -(1) native ? Java bytes allocated (warm) 816 B per query, for the fluent chain itself -(1) native ? Java objects per row 0 -(1) native ? native lane bytes 1.00 MiB (u64 id + u32 class + i32 value) -(1) native ? mask bytes 8.0 KiB (1 bit per row, packed) -(2)/(3) hydrate 65536 Row ? allocated 2.00 MiB - ... per row 32.00 B +(1) native — Java bytes allocated (warm) 816 B per query, for the fluent chain itself +(1) native — Java objects per row 0 +(1) native — native lane bytes 1.00 MiB (u64 id + u32 class + i32 value) +(1) native — mask bytes 8.0 KiB (1 bit per row, packed) +(2)/(3) hydrate 65536 Row — allocated 2.00 MiB + ... per row 32.01 B array flatness NOT-FLAT ratio vs native lane bytes 2.00x retained heap (APPROX, gc-delta) 2.25 MiB == time to answer the question ============================================= -(1) native one crossing, fused plan median= 15435.0 ns [min 15335.0 .. max 30493.0] n=51 -(2/3) hydrate 65536 Row objects median= 857050.0 ns [min 533019.0 .. max 2422972.0] n=51 -(2/3) scan the materialised objects median= 124600.0 ns [min 112855.0 .. max 180439.0] n=51 -(2/3) hydrate THEN scan (honest total) median= 1631916.0 ns [min 1499609.0 .. max 2069662.0] n=51 +(1) native one crossing, fused plan median= 16049.0 ns [min 15609.0 .. max 32010.0] n=51 +(2/3) hydrate 65536 Row objects median= 578187.0 ns [min 543201.0 .. max 1011453.0] n=51 +(2/3) scan the materialised objects median= 119838.0 ns [min 111905.0 .. max 211651.0] n=51 +(2/3) hydrate THEN scan (honest total) median= 1635251.0 ns [min 709125.0 .. max 6428085.0] n=51 lab complete.