Skip to content

feat: add mantis-seqlock — lock-free sequence lock#20

Merged
Milerius merged 18 commits intomainfrom
worktree-feat+mantis-seqlock
Apr 4, 2026
Merged

feat: add mantis-seqlock — lock-free sequence lock#20
Milerius merged 18 commits intomainfrom
worktree-feat+mantis-seqlock

Conversation

@Milerius
Copy link
Copy Markdown
Owner

@Milerius Milerius commented Apr 4, 2026

Summary

  • New mantis-seqlock crate: lock-free single-writer multiple-reader sequence lock
  • Follows mantis-queue patterns: no_std, #![deny(unsafe_code)] on root, unsafe in raw/ submodule, CopyPolicy strategy pattern
  • 3 optimizations: SIMD copy (NEON/SSE2), platform-specific fence (compiler_fence on x86, hardware fence on ARM), data prefetch

Benchmark Results (Apple M4 Pro, ARM64)

Operation mantis rigtorp (C++) amanieu (Rust)
Write u64 1.69 ns 1.88 ns 2.56 ns
Write 64B 2.41 ns 1.95 ns 3.64 ns
Write 128B 4.38 ns 2.22 ns 5.45 ns
Read u64 583 ps 746 ps 648 ps
Read 64B 2.27 ns 2.88 ns 13.1 ns
Read 128B 4.12 ns 4.49 ns 33.0 ns

Faster than rigtorp (C++ HFT gold standard) on all reads while being portable-correct on ARM64.

What's included

  • SeqLock<T, C> with T: Copy, C: CopyPolicy<T> (default + SIMD presets)
  • store(&mut self, val) — single-writer enforced by borrow checker
  • load(&self) -> T — lock-free reader with retry on contention
  • version(&self) -> usize — sequence number for change detection
  • Multi-threaded torn-read detection test (4 readers × 500K writes, zero torn reads)
  • Layout assertions in mantis-layout
  • Criterion benchmarks with rigtorp C++ and amanieu Rust contenders
  • Kani proofs (4) + Bolero property tests (4) in mantis-verify
  • CI pipeline updated: no_std test, bench workflow, report script

Test plan

  • cargo +nightly test -p mantis-seqlock --lib — 9 unit tests
  • cargo +nightly test -p mantis-seqlock --test torn_read — multi-threaded correctness
  • cargo +nightly test -p mantis-seqlock --no-default-features — no_std
  • cargo +nightly clippy -p mantis-seqlock -- -D warnings — clean
  • cargo +nightly bench --bench seqlock — benchmarks run
  • CI passes on Linux (ubuntu-latest)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a new lock-free sequence lock crate with a public SeqLock type and convenient aliases; workspace dependency and layout assertions added.
  • Benchmarks

    • Added seqlock benchmarks (including optional C/C++ contenders), FFI hooks, and CI-side reporting and consolidated report generation updates.
  • Tests

    • Added unit, multithreaded torn-read, property-based tests, and formal proofs validating seqlock behaviour.
  • CI

    • Expanded CI to build/test/benchmark, publish seqlock artifacts, and include seqlock in verification runs.
  • Documentation

    • Updated README and progress docs to list and describe the new sequence lock.

Sztergbaum Roman and others added 14 commits April 4, 2026 18:02
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ce bracketing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…re fence

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…sum payload

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix torn_read.rs clippy warnings (expect_used, print_stderr).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ed read

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Vendor rigtorp/Seqlock.h (MIT) to crates/bench/cpp/
- Add seqlock_bench_contender.cpp with BenchMsg64 C FFI wrapper
- Add bench-seqlock-contenders (amanieu seqlock 0.2) and
  bench-seqlock-contenders-cpp features to mantis-bench
- Add seqlock_ffi module to mantis-bench library for C++ FFI symbols
  (required so native link flags propagate through the rlib to the bench)
- Wire contender functions into criterion_group in seqlock.rs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…fetch optimization

- Replace read_volatile with CopyPolicy::copy_out (enables NEON/SSE2 wide loads)
- Use compiler_fence on x86_64 (TSO), hardware fence on ARM64 only
- Prefetch data cache line before sequence counter read
- All tests pass including torn-read detection
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 4, 2026

📝 Walkthrough

Walkthrough

Adds a new workspace crate mantis-seqlock (lock-free sequence lock) with implementation, tests, verification proofs, benchmarks (Rust + C++ FFI contenders), CI/workflow and reporting integration, layout assertions, and documentation updates.

Changes

Cohort / File(s) Summary
SeqLock crate
crates/seqlock/Cargo.toml, crates/seqlock/src/lib.rs, crates/seqlock/src/raw/...
New mantis-seqlock crate providing SeqLock, SeqLockDefault, optional SeqLockSimd, internal raw::seqlock implementation and unsafe internals isolation.
Core implementation & tests
crates/seqlock/src/raw/seqlock.rs, crates/seqlock/tests/torn_read.rs
Lock-free SeqLock<T,C> implementation (single-writer/multi-reader), API methods (new, store, load, version), Send/Sync impls, and multithreaded torn-read test.
Verification
crates/verify/Cargo.toml, crates/verify/src/lib.rs, crates/verify/src/seqlock_proofs.rs, crates/verify/src/seqlock_props.rs
Added workspace dependency for seqlock; exposed and added Kani proofs and Bolero property tests for seqlock behavior; made proof/props modules public.
Benchmarks (Rust)
crates/bench/Cargo.toml, crates/bench/benches/seqlock.rs, crates/bench/src/lib.rs, crates/bench/src/seqlock_ffi.rs, crates/bench/build.rs
New Criterion seqlock bench target with write/read (contended/uncontended) groups, feature-gated contender benches, Rust FFI bindings, and build integration for C++ contender artifact.
Benchmarks (C++)
crates/bench/cpp/rigtorp_seqlock.h, crates/bench/cpp/seqlock_bench_contender.cpp
Added header-only Rigtorp Seqlock<T> template and C-exported functions to read/write 64/128-byte messages for C++ contender benches.
CI & Reporting
.github/workflows/bench.yml, .github/workflows/ci.yml, .github/scripts/bench-report.sh, .github/scripts/criterion-to-json.sh
Integrated seqlock bench into workflow runs and artifact uploads, merged JSONs into consolidated report, extended bench-report.sh to render grouped suites (fixed-point + optional seqlock), and adjusted criterion-to-json selection logic.
Workspace & consumers
Cargo.toml, crates/layout/Cargo.toml, crates/layout/src/lib.rs, crates/verify/Cargo.toml
Added workspace entries for mantis-seqlock; layout crate adds alignment/size assertions for SeqLock<u64>; verify crate depends on seqlock.
Docs & README
README.md, CLAUDE.md, docs/PROGRESS.md
Registered mantis-seqlock in README and PROGRESS, documented features/status, and updated CI/test command examples in CLAUDE.md.
Bench reporting script
.github/scripts/bench-report.sh
CLI extended to accept optional seqlock JSON paths; replaced fixed renderer with grouped renderer; emits "not available" messages when suite JSONs are missing and renders optional "Sequence Lock (mantis-seqlock)" section when provided.

Sequence Diagram(s)

sequenceDiagram
  participant GH as GitHub Actions
  participant Runner as Runner (Linux/macOS)
  participant Cargo as cargo +nightly bench --bench seqlock
  participant Art as Artifacts (bench JSON)
  participant Script as .github/scripts/bench-report.sh
  participant Report as report.md

  GH->>Runner: start bench job (linux/mac)
  Runner->>Cargo: run seqlock bench
  Cargo->>Art: emit target/bench-report-seqlock.json
  Runner->>GH: upload bench artifact
  GH->>Script: invoke bench-report.sh with linux/mac JSONs
  Script->>Art: read spsc/fixed/(optional) seqlock JSONs
  Script->>Report: merge JSONs and render grouped sections
  Script->>GH: output consolidated report.md
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hopped through fences, counters, and code,

readers steady while writers strode,
no torn carrots, no broken thread—
seqlock sings in lab and bed,
benches hum and proofs bestowed.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add mantis-seqlock — lock-free sequence lock' accurately describes the main change: adding a new lock-free sequence lock crate to the Mantis SDK with clear, specific terminology.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-feat+mantis-seqlock

Comment @coderabbitai help to get the list of available commands and usage tips.

@Milerius Milerius requested a review from brohamgoham April 4, 2026 19:48
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
.github/workflows/ci.yml (1)

126-126: Consider adding Miri coverage for mantis-seqlock.

The Miri job currently only tests mantis-queue. Since mantis-seqlock also contains unsafe code in its raw/ module (sequence lock with atomics and volatile reads), it would benefit from Miri's strict provenance and alignment checks.

Suggested addition to Miri job
-      - run: cargo +nightly miri test -p mantis-queue --all-features
+      - run: cargo +nightly miri test -p mantis-queue -p mantis-seqlock --all-features
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml at line 126, Miri job only runs "cargo +nightly
miri test -p mantis-queue --all-features" and omits mantis-seqlock; update that
command to include mantis-seqlock (e.g., add "-p mantis-seqlock" or run miri
across the workspace) so the Miri step also tests the unsafe code in the
mantis-seqlock raw/ module.
crates/verify/src/lib.rs (1)

9-10: Consider keeping verification modules private unless externally consumed.

Making these modules public expands the crate API surface. If they are only internal harnesses, prefer mod to avoid unnecessary public-contract maintenance.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/verify/src/lib.rs` around lines 9 - 10, The two modules declared as
public, seqlock_proofs and seqlock_props, expand the crate's public API but
appear to be internal; change their declarations from `pub mod seqlock_proofs;`
and `pub mod seqlock_props;` to private `mod seqlock_proofs;` and `mod
seqlock_props;`, and then run a workspace-wide build/test to ensure nothing
external depends on them—if other crates or tests need access, keep `pub` only
where actually consumed and consider re-exporting a stable API instead of
exposing internal harness modules.
crates/seqlock/src/raw/seqlock.rs (2)

29-33: Consider using a newtype for the sequence counter.

The sequence counter uses raw AtomicUsize rather than a dedicated newtype like SeqNum(usize). A newtype would:

  • Make the even/odd semantics explicit in the type system
  • Prevent accidental misuse of raw sequence values
  • Align with the coding guidelines for this repository

This would also apply to the version() return type at line 88.

Example newtype approach
/// Sequence number for SeqLock. Even = consistent, odd = write in progress.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct SeqNum(usize);

impl SeqNum {
    #[inline]
    pub const fn is_even(self) -> bool { self.0 & 1 == 0 }
    #[inline]
    pub const fn as_usize(self) -> usize { self.0 }
}

Based on learnings: "Use newtypes over primitives (e.g., SeqNum(u64) instead of raw u64)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/seqlock/src/raw/seqlock.rs` around lines 29 - 33, Introduce a SeqNum
newtype and replace the raw AtomicUsize usage in SeqLock so the sequence counter
is strongly typed: create a SeqNum(usize) (repr(transparent)) with helpers like
is_even() and as_usize(), change the seq field from CachePadded<AtomicUsize> to
CachePadded<Atomic<SeqNum>> (or keep AtomicUsize but wrap loads/stores via
SeqNum conversions) and update all uses accordingly; update the SeqLock struct
declaration (seq field) and the version() method return type to return SeqNum
instead of usize, and ensure all comparisons and bit tests use
SeqNum.is_even()/as_usize() helpers to preserve even/odd semantics.

107-111: Enhance SAFETY comment to include failure mode.

The SAFETY comment documents the invariant but could be more complete per repository guidelines. Consider adding the explicit failure mode:

-        // SAFETY: Single-writer guaranteed by &mut self. No concurrent writes.
-        // UnsafeCell allows interior mutation. MaybeUninit accepts any bit pattern.
+        // SAFETY:
+        // - Invariant: Single-writer access guaranteed by &mut self borrow.
+        // - Guarantee: UnsafeCell allows interior mutation; MaybeUninit accepts any T.
+        // - Failure mode: If called concurrently (impossible via borrow checker),
+        //   data races would cause UB.

As per coding guidelines: "Every unsafe block must include a // SAFETY: comment documenting the invariant, guarantee, and failure mode".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/seqlock/src/raw/seqlock.rs` around lines 107 - 111, Update the
existing SAFETY comment for the unsafe block that writes into self.data (the
block containing core::ptr::write(self.data.get().cast::<T>(), val)) to
explicitly state the invariant, the guarantee, and the failure mode: document
that a single-writer is guaranteed by &mut self, UnsafeCell permits interior
mutation, MaybeUninit accepts any bit pattern, and if these invariants are
violated it can lead to data races, undefined behavior, memory corruption, or UB
(e.g., concurrent writers or invalid drop of uninitialized memory); include a
brief sentence describing these failure consequences so the comment follows the
repository guideline requiring invariant, guarantee, and failure mode.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/scripts/bench-report.sh:
- Around line 285-299: The seqlock block is calling render_fixed_platform which
produces fixed-point-specific fallback text; change these calls to a generic
renderer (or extend render_fixed_platform to accept a suite label) so the
heading and fallback copy are correct for seqlock. Specifically, replace the
calls using linux_seqlock and macos_seqlock (currently render_fixed_platform
"$linux_seqlock" "Linux" and render_fixed_platform "$macos_seqlock" "macOS")
with a generic function like render_suite_platform (or add a new parameter to
render_fixed_platform) and pass the suite name "Seqlock" so the renderer prints
neutral/fitting fallback text under the seqlock heading.

In `@crates/seqlock/src/raw/seqlock.rs`:
- Around line 152-168: The code calls MaybeUninit::assume_init() unconditionally
after CopyPolicy::copy_out, which can produce UB if a concurrent writer produced
a torn/invalid bit pattern for T; change the read path in SeqLock so that you
copy bytes into a MaybeUninit buffer (using C::copy_out) but do not call
assume_init() until after validating seq1 == seq2 (i.e., after
seqlock_read_barrier() and reloading seq2); only then convert the initialized
bytes to T. Alternatively, if you cannot defer initialization, tighten the
generic bounds on SeqLock/T (e.g., require a marker trait like
“AllBitsValid”/Pod/Zeroable) so the implementation is only used for types where
every bit pattern is valid.

---

Nitpick comments:
In @.github/workflows/ci.yml:
- Line 126: Miri job only runs "cargo +nightly miri test -p mantis-queue
--all-features" and omits mantis-seqlock; update that command to include
mantis-seqlock (e.g., add "-p mantis-seqlock" or run miri across the workspace)
so the Miri step also tests the unsafe code in the mantis-seqlock raw/ module.

In `@crates/seqlock/src/raw/seqlock.rs`:
- Around line 29-33: Introduce a SeqNum newtype and replace the raw AtomicUsize
usage in SeqLock so the sequence counter is strongly typed: create a
SeqNum(usize) (repr(transparent)) with helpers like is_even() and as_usize(),
change the seq field from CachePadded<AtomicUsize> to
CachePadded<Atomic<SeqNum>> (or keep AtomicUsize but wrap loads/stores via
SeqNum conversions) and update all uses accordingly; update the SeqLock struct
declaration (seq field) and the version() method return type to return SeqNum
instead of usize, and ensure all comparisons and bit tests use
SeqNum.is_even()/as_usize() helpers to preserve even/odd semantics.
- Around line 107-111: Update the existing SAFETY comment for the unsafe block
that writes into self.data (the block containing
core::ptr::write(self.data.get().cast::<T>(), val)) to explicitly state the
invariant, the guarantee, and the failure mode: document that a single-writer is
guaranteed by &mut self, UnsafeCell permits interior mutation, MaybeUninit
accepts any bit pattern, and if these invariants are violated it can lead to
data races, undefined behavior, memory corruption, or UB (e.g., concurrent
writers or invalid drop of uninitialized memory); include a brief sentence
describing these failure consequences so the comment follows the repository
guideline requiring invariant, guarantee, and failure mode.

In `@crates/verify/src/lib.rs`:
- Around line 9-10: The two modules declared as public, seqlock_proofs and
seqlock_props, expand the crate's public API but appear to be internal; change
their declarations from `pub mod seqlock_proofs;` and `pub mod seqlock_props;`
to private `mod seqlock_proofs;` and `mod seqlock_props;`, and then run a
workspace-wide build/test to ensure nothing external depends on them—if other
crates or tests need access, keep `pub` only where actually consumed and
consider re-exporting a stable API instead of exposing internal harness modules.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 731be26b-96b5-41bb-a67f-9a8528bcc03a

📥 Commits

Reviewing files that changed from the base of the PR and between ea32348 and 3df55d8.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (25)
  • .github/scripts/bench-report.sh
  • .github/workflows/bench.yml
  • .github/workflows/ci.yml
  • CLAUDE.md
  • Cargo.toml
  • README.md
  • crates/bench/Cargo.toml
  • crates/bench/benches/seqlock.rs
  • crates/bench/build.rs
  • crates/bench/cpp/rigtorp_seqlock.h
  • crates/bench/cpp/seqlock_bench_contender.cpp
  • crates/bench/src/lib.rs
  • crates/bench/src/seqlock_ffi.rs
  • crates/layout/Cargo.toml
  • crates/layout/src/lib.rs
  • crates/seqlock/Cargo.toml
  • crates/seqlock/src/lib.rs
  • crates/seqlock/src/raw/mod.rs
  • crates/seqlock/src/raw/seqlock.rs
  • crates/seqlock/tests/torn_read.rs
  • crates/verify/Cargo.toml
  • crates/verify/src/lib.rs
  • crates/verify/src/seqlock_proofs.rs
  • crates/verify/src/seqlock_props.rs
  • docs/PROGRESS.md

Comment thread .github/scripts/bench-report.sh
Comment thread crates/seqlock/src/raw/seqlock.rs
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
.github/scripts/bench-report.sh (1)

8-13: Prefer named flags to avoid ambiguous optional positional pairs.

The new positional contract is brittle: a 4-arg call is ambiguous (seqlock-only vs fixed-only), which can silently route data to the wrong section. Consider switching to explicit flags (--linux-seqlock, --linux-fixed, etc.) for forward-safe parsing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/scripts/bench-report.sh around lines 8 - 13, The script currently
relies on brittle positional arguments (linux_json, macos_json, linux_seqlock,
macos_seqlock, linux_fixed, macos_fixed) which makes 4-arg calls ambiguous;
change argument parsing to accept two required positional args for linux_json
and macos_json and parse all optional inputs via named flags (e.g.,
--linux-seqlock, --macos-seqlock, --linux-fixed, --macos-fixed) using a
getopt/getopts or while-shift loop, assign values to the existing variables
(linux_seqlock, macos_seqlock, linux_fixed, macos_fixed), validate required args
and print the usage string when missing/invalid flags, and preserve current
behavior downstream by only setting those variables when their flags are
provided.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.github/scripts/bench-report.sh:
- Around line 8-13: The script currently relies on brittle positional arguments
(linux_json, macos_json, linux_seqlock, macos_seqlock, linux_fixed, macos_fixed)
which makes 4-arg calls ambiguous; change argument parsing to accept two
required positional args for linux_json and macos_json and parse all optional
inputs via named flags (e.g., --linux-seqlock, --macos-seqlock, --linux-fixed,
--macos-fixed) using a getopt/getopts or while-shift loop, assign values to the
existing variables (linux_seqlock, macos_seqlock, linux_fixed, macos_fixed),
validate required args and print the usage string when missing/invalid flags,
and preserve current behavior downstream by only setting those variables when
their flags are provided.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 11e66f57-fb6f-43d4-8225-f1877cbaced0

📥 Commits

Reviewing files that changed from the base of the PR and between 3df55d8 and f0bbf14.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • .github/scripts/bench-report.sh
  • .github/workflows/ci.yml
  • Cargo.toml
  • crates/seqlock/src/raw/seqlock.rs
✅ Files skipped from review due to trivial changes (2)
  • Cargo.toml
  • .github/workflows/ci.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/seqlock/src/raw/seqlock.rs

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 4, 2026

Benchmark Report

Commit: a4f82a9dec19f0fe4157249445edf5b790eb5b1e

Linux

CPU: AMD EPYC 7763 64-Core Processor | Arch: x86_64 | Compiler: rustc 1.96.0-nightly (2972b5e59 2026-04-03)

Latency (ns/op, lower is better)

Single Push+Pop

Element crossbeam drogalis-cpp mantis/copy mantis/general mantis/inline rigtorp-cpp rtrb
msg48 22.94 10.15 9.39 🏆 31.66 31.72 9.91 21.31
msg64 7.67 6.68 3.73 🏆 16.67 16.74 6.91 6.9
u64 5.31 4.38 2.5 🏆 - 3.75 3.75 3.49

Burst 100

Element crossbeam drogalis-cpp mantis/copy mantis/inline rigtorp-cpp rtrb
msg48 3009.66 1805.89 214.78 🏆 2591.55 1833.77 2363.46
msg64 1035.17 774.27 202.18 🏆 1464.31 856 757.31
u64 836.6 421.31 168.56 🏆 343.6 387.69 312.88

Burst 1000

Element crossbeam drogalis-cpp mantis/copy mantis/inline rigtorp-cpp rtrb
msg48 30022.67 18228.5 2139.36 🏆 25962.74 17954.59 23709.8
msg64 11552.63 7835.28 2359.25 🏆 14288.98 8286.89 7743.65
u64 8014.54 3793.51 1574.89 🏆 3432.7 3461.97 3017.32

Batch 100

Element mantis/copy
msg48 72.47 🏆
u64 16.13 🏆

Batch 1000

Element mantis/copy
msg48 1069.74 🏆
u64 100.28 🏆

Full Drain

Element mantis/inline
u64 2028.82 🏆
Instructions per Op (lower is better)
Full results (all fields)
Workload ns/op p50 p99 cycles insns bmiss l1d llc
spsc/inline/single_item/u64 3.75 3.7 3.9 11.9 - - - -
spsc/inline/single_item/msg48 31.72 31.7 32 133.8 - - - -
spsc/inline/single_item/msg64 16.74 16.7 17 103.6 - - - -
spsc/inline/burst_100/u64 343.6 342.9 352.7 1455.8 - - - -
spsc/inline/burst_100/msg48 2591.55 2589.8 2610.9 10832.7 - - - -
spsc/inline/burst_100/msg64 1464.31 1463.5 1472.1 4440.8 - - - -
spsc/inline/burst_1000/u64 3432.7 3425.6 3525.8 11396.8 - - - -
spsc/inline/burst_1000/msg48 25962.74 25952.6 26106.1 86043.4 - - - -
spsc/inline/burst_1000/msg64 14288.98 14272.8 14532 49546 - - - -
spsc/inline/full_drain/u64 2028.82 2023.2 2127.4 7293.1 - - - -
copy/single/u64 2.5 2.5 2.5 9.5 - - - -
copy/single/msg48 9.39 9.4 9.5 34.8 - - - -
copy/single/msg64 3.73 3.7 3.8 12 - - - -
general/single/msg48 31.66 31.6 32.1 107.1 - - - -
general/single/msg64 16.67 16.7 16.9 57.8 - - - -
copy/burst/100/u64 168.56 168.2 175.9 694.8 - - - -
copy/burst/100/msg48 214.78 214.1 224 681.6 - - - -
copy/burst/100/msg64 202.18 201.7 207.7 621.3 - - - -
copy/burst/1000/u64 1574.89 1572.7 1606.3 4957.7 - - - -
copy/burst/1000/msg48 2139.36 2138.4 2167.2 7950.7 - - - -
copy/burst/1000/msg64 2359.25 2355.8 2399.6 9318 - - - -
copy/batch/100/u64 16.13 16.1 16.3 45.3 - - - -
copy/batch/100/msg48 72.47 71.8 77.4 270.8 - - - -
copy/batch/1000/u64 100.28 100.1 103.6 305.1 - - - -
copy/batch/1000/msg48 1069.74 1069.1 1090 3941.5 - - - -
spsc/rtrb/single_item/u64 3.49 3.5 3.6 10.6 - - - -
spsc/rtrb/single_item/msg48 21.31 20.7 23.4 82.7 - - - -
spsc/rtrb/single_item/msg64 6.9 6.4 8.3 19.2 - - - -
spsc/rtrb/burst_100/u64 312.88 309.6 360.9 1228.6 - - - -
spsc/rtrb/burst_100/msg48 2363.46 2395 2538 9397.3 - - - -
spsc/rtrb/burst_100/msg64 757.31 823.9 938.3 2619.8 - - - -
spsc/rtrb/burst_1000/u64 3017.32 3012.7 3081.3 9379.7 - - - -
spsc/rtrb/burst_1000/msg48 23709.8 23962.5 25565.5 75676.7 - - - -
spsc/rtrb/burst_1000/msg64 7743.65 7088.3 9398.7 24939 - - - -
spsc/crossbeam/single_item/u64 5.31 5.3 5.5 21.3 - - - -
spsc/crossbeam/single_item/msg48 22.94 22.9 23 64.5 - - - -
spsc/crossbeam/single_item/msg64 7.67 7.7 7.9 25 - - - -
spsc/crossbeam/burst_100/u64 836.6 836.4 839.6 2683 - - - -
spsc/crossbeam/burst_100/msg48 3009.66 3008.5 3031.8 9351.2 - - - -
spsc/crossbeam/burst_100/msg64 1035.17 1033.5 1064.1 3725 - - - -
spsc/crossbeam/burst_1000/u64 8014.54 8007.8 8105.1 29275.6 - - - -
spsc/crossbeam/burst_1000/msg48 30022.67 30007 30154.2 108418.1 - - - -
spsc/crossbeam/burst_1000/msg64 11552.63 11544.1 11638.5 36115.5 - - - -
spsc/rigtorp/single_item/u64 3.75 3.7 3.9 11.9 - - - -
spsc/rigtorp/single_item/msg48 9.91 9.9 10.2 39 - - - -
spsc/rigtorp/single_item/msg64 6.91 7.2 8.1 19.4 - - - -
spsc/rigtorp/burst_100/u64 387.69 387.1 394 1179.3 - - - -
spsc/rigtorp/burst_100/msg48 1833.77 1787.7 2059.6 5920 - - - -
spsc/rigtorp/burst_100/msg64 856 889.4 1313.5 2936.1 - - - -
spsc/rigtorp/burst_1000/u64 3461.97 3458 3514.7 11546.8 - - - -
spsc/rigtorp/burst_1000/msg48 17954.59 17355.4 20443 68676.1 - - - -
spsc/rigtorp/burst_1000/msg64 8286.89 6919.5 12480.5 25450.8 - - - -
spsc/drogalis/single_item/u64 4.38 4.4 4.4 15.3 - - - -
spsc/drogalis/single_item/msg48 10.15 10.2 10.5 40.5 - - - -
spsc/drogalis/single_item/msg64 6.68 6.9 7.3 19 - - - -
spsc/drogalis/burst_100/u64 421.31 420.3 435.8 1337.6 - - - -
spsc/drogalis/burst_100/msg48 1805.89 1757.7 2027.8 5950.5 - - - -
spsc/drogalis/burst_100/msg64 774.27 829.7 966 2588 - - - -
spsc/drogalis/burst_1000/u64 3793.51 3787 3909.8 13288.5 - - - -
spsc/drogalis/burst_1000/msg48 18228.5 17625 20554.6 70412.3 - - - -
spsc/drogalis/burst_1000/msg64 7835.28 7170.8 9622.9 25244.9 - - - -
macOS

CPU: Apple M1 (Virtual) | Arch: aarch64 | Compiler: rustc 1.96.0-nightly (2972b5e59 2026-04-03)

Latency (ns/op, lower is better)

Single Push+Pop

Element crossbeam drogalis-cpp mantis/copy mantis/general mantis/inline rigtorp-cpp rtrb
msg48 11.31 10.15 8.33 🏆 9.93 9.94 10.25 8.95
msg64 11.55 9.83 8.26 🏆 10.66 10.67 10.22 9.96
u64 8.85 8.69 6.77 🏆 - 7.84 8.88 7

Burst 100

Element crossbeam drogalis-cpp mantis/copy mantis/inline rigtorp-cpp rtrb
msg48 1821.19 1842.56 338.7 🏆 530.19 1840.63 735.91
msg64 1884.61 1788.19 409.38 🏆 644.77 1850.86 802.98
u64 1741.55 1515.02 277.52 263.29 🏆 1623.13 627.44

Burst 1000

Element crossbeam drogalis-cpp mantis/copy mantis/inline rigtorp-cpp rtrb
msg48 18866.59 19075.64 4419.74 🏆 5503.64 18974.45 7463.01
msg64 19397.16 32992.56 4906.63 🏆 6880 33658.59 22387.49
u64 17595.3 15158.37 3629.02 2527.02 🏆 16283.45 6555.95

Batch 100

Element mantis/copy
msg48 90.67 🏆
u64 25.99 🏆

Batch 1000

Element mantis/copy
msg48 774 🏆
u64 131.5 🏆

Full Drain

Element mantis/inline
u64 4004.06 🏆
Instructions per Op (lower is better)
Full results (all fields)
Workload ns/op p50 p99 cycles insns bmiss l1d llc
spsc/inline/single_item/u64 7.84 7.9 8.2 0.3 - - - -
spsc/inline/single_item/msg48 9.94 10 10.3 0.4 - - - -
spsc/inline/single_item/msg64 10.67 10.7 11.2 0.4 - - - -
spsc/inline/burst_100/u64 263.29 262.3 294.7 9.1 - - - -
spsc/inline/burst_100/msg48 530.19 532.6 546.3 18.8 - - - -
spsc/inline/burst_100/msg64 644.77 642.7 705.5 25.6 - - - -
spsc/inline/burst_1000/u64 2527.02 2541.4 2637.5 102.3 - - - -
spsc/inline/burst_1000/msg48 5503.64 5512.7 5702.6 235.8 - - - -
spsc/inline/burst_1000/msg64 6880 6932.2 7143.6 228.5 - - - -
spsc/inline/full_drain/u64 4004.06 4025.7 4131.9 142.5 - - - -
copy/single/u64 6.77 6.8 7 0.2 - - - -
copy/single/msg48 8.33 8.4 8.7 0.3 - - - -
copy/single/msg64 8.26 8.3 8.6 0.3 - - - -
general/single/msg48 9.93 10 10.2 0.4 - - - -
general/single/msg64 10.66 10.7 11.1 0.4 - - - -
copy/burst/100/u64 277.52 276.6 287.8 10.2 - - - -
copy/burst/100/msg48 338.7 340.4 348.8 13.9 - - - -
copy/burst/100/msg64 409.38 411.3 422.1 12.6 - - - -
copy/burst/1000/u64 3629.02 3653.6 3772.8 121.6 - - - -
copy/burst/1000/msg48 4419.74 4421.1 4635.8 165.2 - - - -
copy/burst/1000/msg64 4906.63 4933.2 5047.6 195.5 - - - -
copy/batch/100/u64 25.99 26.1 26.8 0.8 - - - -
copy/batch/100/msg48 90.67 89.6 102.6 2.5 - - - -
copy/batch/1000/u64 131.5 131.3 139.8 4.5 - - - -
copy/batch/1000/msg48 774 778.8 826.9 23.8 - - - -
spsc/rtrb/single_item/u64 7 7 7.2 0.2 - - - -
spsc/rtrb/single_item/msg48 8.95 9 9.3 0.3 - - - -
spsc/rtrb/single_item/msg64 9.96 10 10.4 0.4 - - - -
spsc/rtrb/burst_100/u64 627.44 629.9 646.1 24.7 - - - -
spsc/rtrb/burst_100/msg48 735.91 737.3 763.9 21.7 - - - -
spsc/rtrb/burst_100/msg64 802.98 804.7 828.8 24.5 - - - -
spsc/rtrb/burst_1000/u64 6555.95 6581.6 6875 212.2 - - - -
spsc/rtrb/burst_1000/msg48 7463.01 7480.1 7711.1 259.6 - - - -
spsc/rtrb/burst_1000/msg64 22387.49 22609.3 23504 977.2 - - - -
spsc/crossbeam/single_item/u64 8.85 8.9 9.2 0.3 - - - -
spsc/crossbeam/single_item/msg48 11.31 11.2 12.1 0.5 - - - -
spsc/crossbeam/single_item/msg64 11.55 11.6 11.9 0.3 - - - -
spsc/crossbeam/burst_100/u64 1741.55 1753.4 1816.8 56.9 - - - -
spsc/crossbeam/burst_100/msg48 1821.19 1832.5 1882.5 60.8 - - - -
spsc/crossbeam/burst_100/msg64 1884.61 1898.2 1954.2 64.1 - - - -
spsc/crossbeam/burst_1000/u64 17595.3 17675.6 18116.1 667.9 - - - -
spsc/crossbeam/burst_1000/msg48 18866.59 18985 19546 751.4 - - - -
spsc/crossbeam/burst_1000/msg64 19397.16 19470 19934.4 776.4 - - - -
spsc/rigtorp/single_item/u64 8.88 8.9 9.1 0.3 - - - -
spsc/rigtorp/single_item/msg48 10.25 10.3 10.6 0.4 - - - -
spsc/rigtorp/single_item/msg64 10.22 10.3 10.5 0.4 - - - -
spsc/rigtorp/burst_100/u64 1623.13 1630.5 1669.8 50.7 - - - -
spsc/rigtorp/burst_100/msg48 1840.63 1847.8 1891.1 61.5 - - - -
spsc/rigtorp/burst_100/msg64 1850.86 1861 1915.6 62.7 - - - -
spsc/rigtorp/burst_1000/u64 16283.45 16363.7 16769.3 595.4 - - - -
spsc/rigtorp/burst_1000/msg48 18974.45 19077.3 19513.3 752.9 - - - -
spsc/rigtorp/burst_1000/msg64 33658.59 33847.9 34785.7 1253.5 - - - -
spsc/drogalis/single_item/u64 8.69 8.7 9 0.3 - - - -
spsc/drogalis/single_item/msg48 10.15 10.2 10.5 0.4 - - - -
spsc/drogalis/single_item/msg64 9.83 9.9 10.1 0.4 - - - -
spsc/drogalis/burst_100/u64 1515.02 1521.5 1564.1 46 - - - -
spsc/drogalis/burst_100/msg48 1842.56 1852.6 1899.9 61.8 - - - -
spsc/drogalis/burst_100/msg64 1788.19 1796.8 1858.2 58.9 - - - -
spsc/drogalis/burst_1000/u64 15158.37 15273.7 15675.9 532.7 - - - -
spsc/drogalis/burst_1000/msg48 19075.64 19177 19613.2 767 - - - -
spsc/drogalis/burst_1000/msg64 32992.56 33170.1 34229.2 1206.1 - - - -

Sequence Lock (mantis-seqlock)

Linux

CPU: AMD EPYC 7763 64-Core Processor | Arch: x86_64 | Compiler: rustc 1.96.0-nightly (2972b5e59 2026-04-03)

checked_add

Variant ns/op
FixedI64_6_ 1.25
raw_i64 1.25

checked_div

Variant ns/op
trunc 4.99
round 5.61

checked_mul_trunc

Variant ns/op
D=2 4.09
D=8 4.35
D=6 4.36
D=4 4.52

display

Variant ns/op
FixedI64_6_ 46.91

mul_round_vs_trunc

Variant ns/op
trunc 4.35
round 4.82

parse

Variant ns/op
short 19.06
integer_only 25.05
full_precision 34.67

rescale

Variant ns/op
D2_to_D8_widen 0.63
D6_to_D2_trunc 0.92
macOS

CPU: Apple M1 (Virtual) | Arch: arm64 | Compiler: rustc 1.96.0-nightly (2972b5e59 2026-04-03)

checked_add

Variant ns/op
raw_i64 1.59
FixedI64_6_ 1.68

checked_div

Variant ns/op
trunc 3.27
round 4.44

checked_mul_trunc

Variant ns/op
D=4 1.97
D=6 1.97
D=8 1.98
D=2 2.09

display

Variant ns/op
FixedI64_6_ 33.56

mul_round_vs_trunc

Variant ns/op
trunc 1.97
round 2.34

parse

Variant ns/op
short 11.78
integer_only 16.93
full_precision 24.87

rescale

Variant ns/op
D6_to_D2_trunc 0.5
D2_to_D8_widen 0.51

Fixed-Point Arithmetic (mantis-fixed)

Linux

CPU: AMD EPYC 7763 64-Core Processor | Arch: x86_64 | Compiler: rustc 1.96.0-nightly (2972b5e59 2026-04-03)

checked_add

Variant ns/op
FixedI64_6_ 1.25
raw_i64 1.25

checked_div

Variant ns/op
trunc 4.99
round 5.61

checked_mul_trunc

Variant ns/op
D=2 4.09
D=8 4.35
D=6 4.36
D=4 4.52

display

Variant ns/op
FixedI64_6_ 46.91

mul_round_vs_trunc

Variant ns/op
trunc 4.35
round 4.82

parse

Variant ns/op
short 19.06
integer_only 25.05
full_precision 34.67

rescale

Variant ns/op
D2_to_D8_widen 0.63
D6_to_D2_trunc 0.92
macOS

CPU: Apple M1 (Virtual) | Arch: arm64 | Compiler: rustc 1.96.0-nightly (2972b5e59 2026-04-03)

checked_add

Variant ns/op
raw_i64 1.59
FixedI64_6_ 1.68

checked_div

Variant ns/op
trunc 3.27
round 4.44

checked_mul_trunc

Variant ns/op
D=4 1.97
D=6 1.97
D=8 1.98
D=2 2.09

display

Variant ns/op
FixedI64_6_ 33.56

mul_round_vs_trunc

Variant ns/op
trunc 1.97
round 2.34

parse

Variant ns/op
short 11.78
integer_only 16.93
full_precision 24.87

rescale

Variant ns/op
D6_to_D2_trunc 0.5
D2_to_D8_widen 0.51

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/scripts/criterion-to-json.sh:
- Around line 27-33: The prefix glob in the first for-loop misses the common
two-level layout causing matched_ests to remain empty and trigger the broad
fallback; update the glob list in that loop to include the pattern
"${bench_name}*/*/new/estimates.json" (in addition to the existing
"${bench_name}*/new/estimates.json" and "${bench_name}*/*/*/new/estimates.json")
so the loop that appends to matched_ests correctly captures two-level paths
before the fallback; ensure you modify the for-loop that iterates over est and
references matched_ests and bench_name to include the new pattern.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d25a4151-ff02-4074-b291-cba39659be8f

📥 Commits

Reviewing files that changed from the base of the PR and between f0bbf14 and 63060a3.

📒 Files selected for processing (1)
  • .github/scripts/criterion-to-json.sh

Comment on lines +27 to +33
for est in "$criterion_dir"/${bench_name}*/new/estimates.json "$criterion_dir"/${bench_name}*/*/*/new/estimates.json; do
matched_ests+=("$est")
done
# Fallback: if no prefix-matched results, read everything (backwards compatibility)
if [ ${#matched_ests[@]} -eq 0 ]; then
for est in "$criterion_dir"/*/new/estimates.json "$criterion_dir"/*/*/new/estimates.json; do
matched_ests+=("$est")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Prefix glob misses Criterion’s two-level path and can silently fall back to broad matching.

At Line 27, the prefix glob includes .../${bench_name}*/*/*/new/estimates.json but not the common .../${bench_name}*/*/new/estimates.json layout (which your own examples at Lines 42-43 use). That can cause matched_ests to stay empty and trigger the broad fallback at Line 31, reintroducing mixed-result risk.

Suggested fix
 shopt -s nullglob
 matched_ests=()
-for est in "$criterion_dir"/${bench_name}*/new/estimates.json "$criterion_dir"/${bench_name}*/*/*/new/estimates.json; do
+for est in \
+  "$criterion_dir"/"${bench_name}"*/new/estimates.json \
+  "$criterion_dir"/"${bench_name}"*/*/new/estimates.json \
+  "$criterion_dir"/"${bench_name}"*/*/*/new/estimates.json; do
   matched_ests+=("$est")
 done
 # Fallback: if no prefix-matched results, read everything (backwards compatibility)
 if [ ${`#matched_ests`[@]} -eq 0 ]; then
+  echo "No prefix-matched Criterion results for '${bench_name}', falling back to all estimates." >&2
   for est in "$criterion_dir"/*/new/estimates.json "$criterion_dir"/*/*/new/estimates.json; do
     matched_ests+=("$est")
   done
 fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for est in "$criterion_dir"/${bench_name}*/new/estimates.json "$criterion_dir"/${bench_name}*/*/*/new/estimates.json; do
matched_ests+=("$est")
done
# Fallback: if no prefix-matched results, read everything (backwards compatibility)
if [ ${#matched_ests[@]} -eq 0 ]; then
for est in "$criterion_dir"/*/new/estimates.json "$criterion_dir"/*/*/new/estimates.json; do
matched_ests+=("$est")
for est in \
"$criterion_dir"/"${bench_name}"*/new/estimates.json \
"$criterion_dir"/"${bench_name}"*/*/new/estimates.json \
"$criterion_dir"/"${bench_name}"*/*/*/new/estimates.json; do
matched_ests+=("$est")
done
# Fallback: if no prefix-matched results, read everything (backwards compatibility)
if [ ${`#matched_ests`[@]} -eq 0 ]; then
echo "No prefix-matched Criterion results for '${bench_name}', falling back to all estimates." >&2
for est in "$criterion_dir"/*/new/estimates.json "$criterion_dir"/*/*/new/estimates.json; do
matched_ests+=("$est")
done
fi
🧰 Tools
🪛 Shellcheck (0.11.0)

[info] 27-27: Quote expansions in this for loop glob to prevent wordsplitting, e.g. "$dir"/*.txt .

(SC2231)


[info] 27-27: Quote expansions in this for loop glob to prevent wordsplitting, e.g. "$dir"/*.txt .

(SC2231)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/scripts/criterion-to-json.sh around lines 27 - 33, The prefix glob
in the first for-loop misses the common two-level layout causing matched_ests to
remain empty and trigger the broad fallback; update the glob list in that loop
to include the pattern "${bench_name}*/*/new/estimates.json" (in addition to the
existing "${bench_name}*/new/estimates.json" and
"${bench_name}*/*/*/new/estimates.json") so the loop that appends to
matched_ests correctly captures two-level paths before the fallback; ensure you
modify the for-loop that iterates over est and references matched_ests and
bench_name to include the new pattern.

@Milerius Milerius merged commit bcdb366 into main Apr 4, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant