Skip to content

perf-callgrind: JVM vs clojure-rs per-namespace sweep with Bencher - #2

Closed
m-hu wants to merge 6 commits into
hhh-pa:mainfrom
m-hu:perf/1-callgrind
Closed

m-hu wants to merge 6 commits into
hhh-pa:mainfrom
m-hu:perf/1-callgrind

Conversation

@m-hu

@m-hu m-hu commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1. Branch: m-hu:perf/1-callgrind. Spec: docs/1/specification.md (on this branch).

Problems

  • The algorithms repo has no performance signal: JVM-vs-clojure-rs
    comparisons do not exist here, while the snippet repo has had a
    deterministic callgrind sweep with Bencher overlays since T1211.
  • Four test-isolation bugs hid in the suite, invisible to the
    whole-suite runners: three namespaces use clojure.set /
    clojure.java.io without requiring them (passing only by ambient
    load order), and one test depends on stack exhaustion
    ((thrown? Throwable (power 2 -1)) with an impl that recurses
    past zero forever).

Root causes

  • No sweep tooling or workflow exists in this repo (ci.yml runs
    correctness only).
  • Per-namespace standalone runs (required for per-benchmark
    measurement) expose load-order dependence: karger-test,
    xor-cipher-test, and disjoint-set-test fail with
    ClassNotFoundException / Syntax error compiling on a fresh
    runtime on both legs (verified JVM fails identically — repo
    bugs, not port gaps).
  • (power 2 -1) never terminates by design; the JVM surfaces
    this as a catchable StackOverflowError, while clojure-rs
    aborts the process (SIGABRT, exit 134 — port gap tracked at
    Unbounded recursion aborts the process instead of throwing a catchable StackOverflowError clojure#220).

Solutions (why each resolves its problem)

  • perf/driver.clj: one driver for both legs — require the ns,
    clojure.test/run-tests, print one
    SUMMARY {:test :pass :fail :error} line. Same workload both
    legs; no System/exit (unemulated natively) — the gate reads
    stdout.
  • scripts/ci/go (algoperf): vendored recipe-identical measure
    core (valgrind flags, JVM -Xint+EpsilonGC, LD_PRELOAD shim
    with byte-identical C source, BMF shape) with namespace
    discovery and the SUMMARY gate instead of full-stdout
    comparison (namespaces print nondeterministically).
  • perf-callgrind.yml: resolve-rs (floats clojure-rs HEAD) +
    correctness gate + shard(8) x runtime(2) sweep + per-runtime
    merge + JVM-vs-native count-diff gate. ci.yml untouched.
  • Test-isolation fixes (spec §non-goals amended — the sweep
    itself surfaced them): three missing :requires, one
    fail-fast negative-exponent guard.
  • JVM locale pin (-Duser.language/country): comma-decimal
    hosts failed 13 namespaces via format-based assertions and
    would perturb Ir counts.
  • Split timeouts (--timeout 1800 native / --callgrind-timeout
    600 + skip-degrade): the slowest namespace needs ~11min on
    the native leg, which must not flake the correctness gate.

No regressions

  • ci.yml, deps.edn, build.clj untouched; the sweep is a new
    workflow file plus additive perf/ + scripts/ + docs/.
  • The four src//test/ fixes keep full-suite semantics: the
    requires are no-ops where already loaded, and the power guard
    throws a Throwable exactly where the test asserts one
    (whole-suite clojure -M:test still the gate's precondition).
  • Recipe fidelity: the C shim is md5-identical to T1211's, and
    JVM flags/parsers/BMF are line ports (only discovery, gating,
    and naming adapted — all covered by self-tests).

Test Plan

  • gofmt -l clean, go vet ./..., go test ./... green
    (cmd/algoperf, internal/algocore, internal/measure).
  • algoperf measure --self-test: 26/26 checks (SUMMARY,
    callgrind, shim, EpsilonGC, GNU-time parsers; templates;
    leg/skip classifiers).
  • python3 -c yaml.safe_load on the workflow + action;
    git diff --check clean.
  • Native e2e (--only, 3 namespaces): gate passes, report +
    BMF + summary outputs verified by shape.
  • JVM e2e incl. EpsilonGC alloc path (alloc_min=59430912B
    on one namespace).
  • --merge verified (dedupe + counts + summary render).
  • Full 1351-namespace sweeps, timed phase, both legs:
    1351 ok / 0 failed each; cross-leg counts match except
    the 2 documented random-graph namespaces (karger,
    random-graph-generator), whose :pass variance is
    normalized in report-compare (proven the only varying
    ones over the full 1351x2 data).
  • CI (Linux) remains the proof for the valgrind/shim paths
    (no valgrind on this host) — same as T1211.

Housekeeping notes

  • Branch commits predate the AI-Tool: trailer convention on
    this repo (pushed; not rewritten per policy) — attribution
    rides this PR body into the squash instead. New commits on
    this branch will carry the trailer.
  • Draft until the admin step lands (below).

Admin needed (repo admin)

  • Create Bencher project clojure-algorithms, set
    BENCHER_API_KEY secret (Bencher steps fail without it).
  • Optionally require the new checks in branch protection
    after first green.

Session: gentle-menkar.

AI-Tool: Muse Code 1.4.14-R7309.1 (Meta Muse Spark)

m-hu added 4 commits November 3, 2026 02:14
Per-namespace callgrind sweep over all 1351 test namespaces,
both runtimes, Bencher overlay. Key adaptations: SUMMARY-line
gate instead of stdout diff, vendored measure recipe with
native discovery, new perf-callgrind.yml (ci.yml untouched).
Perf-callgrind sweep for JVM vs clojure-rs (mirrors snippet
T1211): per-namespace driver with SUMMARY gate, algoperf measure
tool (ported recipe core: valgrind flags, JVM -Xint+EpsilonGC,
LD_PRELOAD shim, BMF), perf-callgrind.yml (shard x runtime
matrix, Bencher, merge + cross-leg count diff).

The first full sweep surfaced 4 test-isolation bugs, fixed here:
3 missing :requires (passed only by ambient load order) and a
fail-fast negative-exponent guard (replaces stack-exhaustion
dependence; port gap tracked at hhh-pa/clojure#220).
…sions, count-diff jq)

- gate job renamed (was test: collides with ci.yml's check name)
- JVM leg pins temurin 21.0.13 (deterministic Bencher history)
- conservative action majors (v4/v5)
- report-compare jq: single-object counts map (verified locally)
- JVM template pins -Duser.language/country (comma-decimal
  hosts failed 13 namespaces; also stabilizes Ir)
- report-compare normalizes :pass for the 2 random-graph
  namespaces (proven the only varying ones over full
  1351x2 sweeps)
@m-hu m-hu added the session:gentle-menkar Claimed by session gentle-menkar label Nov 3, 2026
CI round 1 evidence (all 8 native shards harvested):
- matrix-exponentiation exceeds both legs' budgets (native
  timed run >1800s on CI; JVM callgrind OOMs the 2g Epsilon
  heap, reproduced locally). perf/sweep_skips.json excludes
  it with a reviewed reason (reported, never silent).
- shim cross-check compares :test/:fail/:error + :pass>0:
  the 2 random-graph namespaces tally different assertion
  counts per run, which flaked the exact comparison.
- JVM callgrind OOM now reports the heap hint (was cryptic
  'exit 3').

AI-Tool: Muse Code 1.4.14-R7309.1 (Meta Muse Spark)
@m-hu

m-hu commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

Round-2 push (4c2d268) from CI round-1 evidence (all 8 native shard artifacts harvested; JVM legs still running then):

  • perf/sweep_skips.json excludes matrix-exponentiation-test with a reviewed reason: native timed run >1800s on CI, JVM callgrind OOMs the 2g Epsilon heap (reproduced locally). Skips are recorded in the report, never silent.
  • Shim cross-check now compares :test/:fail/:error + :pass>0 — the 2 random-graph namespaces flaked the exact tally comparison.
  • JVM callgrind OOM reports the heap hint instead of cryptic exit 3.

Round-1 native sweep itself: 1350/1351 ok with full callgrind data (167/169 per shard typical), 7 callgrind timeouts degrading to skips as designed, 1 transient missing shim marker (cheap-progress, single occurrence — watching for recurrence). Remaining red is expected to be Bencher auth only.

Session: gentle-menkar.

AI-Tool: Muse Code 1.4.14-R7309.1 (Meta Muse Spark)

Round-2 JVM shard 3 failed its callgrind phase with OOM (same
exit-3 signature as matrix-exp). Measured locally: 7.4g heap
used; the next-heaviest namespace needs 0.67g, so the 2g
budget stays and problem-056 joins the skip-list with the
measured reason.

AI-Tool: Muse Code 1.4.14-R7309.1 (Meta Muse Spark)
@m-hu

m-hu commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

Round-3 push (15824e1): round-2 post-mortem complete — 15/16 legs fail ONLY on the Bencher step (missing secret, expected); the one real sweep failure is JVM shard 3's problem-056.sol1-test, which OOMs the 2g EpsilonGC heap (measured 7.4g locally; next-heaviest namespace is 0.67g, so the budget stays and the namespace joins sweep_skips.json with the measured reason).

Round-2 sweep health from artifacts: all native shards 0 sweep failures with full callgrind data; all JVM shards 0 sweep failures except problem-056.

Session: gentle-menkar.

AI-Tool: Muse Code 1.4.14-R7309.1 (Meta Muse Spark)

@m-hu

m-hu commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

Round-3 terminal state (15824e1, run 41579900347): all 16 legs fail ONLY on the Bencher step (missing BENCHER_API_KEY — the known admin blocker). Everything else validated, including the report jobs' exact commands replayed locally against all 16 round-3 artifacts:

  • Merge: 1351 results -> 1349 benchmarks (2 skips), 0 failures, both legs; matches jq -s add union on both legs.
  • Count-diff: JVM vs native test counts MATCH with the 2 documented random-graph exceptions.
  • Callgrind coverage: JVM 1342/1349 benchmarks with both measures (7 timeout-skips by design), native 1341/1349 (8 skips).

No code changes needed. Remaining: create the Bencher project + secret (admin checklist on this PR), then re-run to green and land.

Session: gentle-menkar.

AI-Tool: Muse Code 1.4.14-R7309.1 (Meta Muse Spark)

@m-hu

m-hu commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

Admin step landed: BENCHER_API_KEY secret set, and the token + clojure-algorithms project verified with a probe submission (report on throwaway branch ci-probe). Re-ran the 16 failed legs of run 41579900347 — Bencher steps should report now, then merge + count-diff jobs run.

Session: gentle-menkar.

AI-Tool: Muse Code 1.4.14-R7309.1 (Meta Muse Spark)

@m-hu

m-hu commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

Session: gentle-menkar. Closing as superseded by #3.

Root cause of the red Bencher steps (all rounds, including after the secret existed): this PR head is on the m-hu fork, and pull_request-triggered runs from forks receive no secrets — so BENCHER_API_KEY always rendered as empty and bencher run died on --key <KEY> validation. The sweeps themselves were green in every round (all 16 shards uploaded results); only the report step failed.

Fix: same branch content pushed to a same-repo head (hhh-pa/clojure-algorithms:perf/1-callgrind), where secrets flow to pull_request runs. Continuing in #3.

@m-hu m-hu closed this Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

session:gentle-menkar Claimed by session gentle-menkar

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf-callgrind: JVM vs clojure-rs per-namespace sweep with Bencher

1 participant