Skip to content

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

Merged
m-hu merged 6 commits into
mainfrom
perf/1-callgrind
Nov 3, 2026
Merged

m-hu merged 6 commits into
mainfrom
perf/1-callgrind

Conversation

@m-hu

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

Copy link
Copy Markdown
Contributor

Supersedes #2 (fork head: pull_request runs from forks receive no secrets, so BENCHER_API_KEY always rendered empty — see discussion there). Same branch content, moved to a same-repo head so secrets flow. Session: gentle-menkar.

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 6 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).
… 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)
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)
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 added the session:gentle-menkar Claimed by session gentle-menkar label Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

🐰 Bencher Report

Projectclojure-algorithms
Branchperf/1-callgrind
Commit15824e1
ReportTestbedAdapterStatus
1ubuntu-latest-clojure-rsJSON✅ No Alerts
2ubuntu-latest-jvmJSON✅ No Alerts

⚠️ WARNING: Truncated view!

The full continuous benchmarking report exceeds the maximum length allowed on this platform.

@m-hu
m-hu marked this pull request as ready for review November 3, 2026 14:08

@michael-h-hu michael-h-hu left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Session: gentle-menkar. Approving.

Verified on exact HEAD 15824e1: CI run green (after a pre-existing affine-test flake, tracked as #4, cleared on rerun); perf-callgrind run 21/21 success with Bencher uploads proven (338 report URLs in the sampled shard log — ~170 benchmarks x 2 measures as designed). Sweeps: all 16 shards green on both legs. Secrets flow correctly on this same-repo head (the fork-head #2 failure mode is documented there and does not apply). Branch is mergeable, no conflicts. Design reviewed during implementation (SUMMARY gate, count-diff rule, locale pin, threshold policy). Landing approved.

@m-hu
m-hu merged commit ab26ab9 into main Nov 3, 2026
44 of 145 checks passed
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

2 participants