An open API service indexing awesome lists of open source software.

https://github.com/opensvm/bmssp-benchmark-game

bounded multi-source shortest paths benches in Rust, C, C++, Nim, Crystal, Kotlin (JAR), Elixir (.exs), Erlang (.erl).
https://github.com/opensvm/bmssp-benchmark-game

benchmark benchmark-game bmssp bounded-multi-source-shortest-paths c cpp crystal elixir erlang graph-algorithms graphs graphs-theory kotlin nim rust

Last synced: 7 months ago
JSON representation

bounded multi-source shortest paths benches in Rust, C, C++, Nim, Crystal, Kotlin (JAR), Elixir (.exs), Erlang (.erl).

Awesome Lists containing this project

README

          

# bmssp benchmark game

Implementation of the algorithm in multiple languages to compare performance in a standard GitHub Actions environment.

Languages currently wired in this repo: Rust, C, C++, Nim, Crystal, Kotlin (JAR), Elixir (.exs), Erlang (.erl).

## Benchmark snapshot

| impl | lang | graph | n | m | k | B | seed | threads | time_ns | popped | edges_scanned | heap_pushes | B_prime | mem_bytes |
|---------------|--------|-------|------|------|---|----|------|---------|---------|--------|---------------|-------------|---------|-----------|
| rust-bmssp | Rust | grid | 2500 | 9800 | 4 | 50 | 1 | 1 | 741251 | 868 | 3423 | 1047 | 50 | 241824 |
| c-bmssp | C | grid | 2500 | 9800 | 4 | 50 | 1 | 1 | 99065 | 1289 | 5119 | 1565 | 50 | 176800 |
| cpp-bmssp | C++ | grid | 2500 | 9800 | 4 | 50 | 1 | 1 | 117480 | 1064 | 4224 | 1261 | 50 | 176800 |
| kotlin-bmssp | Kotlin | grid | 2500 | 9800 | 4 | 50 | 1 | 1 | 5308820 | 1102 | 4386 | 1309 | 50 | 196800 |
| elixir-bmssp | Elixir | grid | 2500 | 9800 | 4 | 50 | 1 | 1 | 5410039 | 870 | 3447 | 1047 | 50 | 196800 |
| erlang-bmssp | Erlang | grid | 2500 | 9800 | 4 | 50 | 1 | 1 | 1155739 | 691 | 2701 | 818 | 50 | 196800 |

Rust implementation of **bounded multi-source shortest paths** (multi-source Dijkstra cut off at `B`).

## Run

```bash
cargo test
cargo bench -p bmssp
python3 bench/runner.py --release --out results

# fast iteration
python3 bench/runner.py --quick --out results-dev --timeout-seconds 20 --jobs 2

# 1000x larger graphs (heavy):
python3 bench/runner.py --params bench/params_1000x.yaml --release --jobs 4 --timeout-seconds 600 --out results
```

### One-time setup scripts

- Linux/macOS:
- Run `scripts/install_deps.sh --yes` to auto-install Rust, Python deps, build tools, Crystal/shards, Nim.
- Use `--check-only` to only report missing items.
- Windows:
- Open PowerShell (as Administrator recommended) and run `scripts/Install-Dependencies.ps1 -Yes`.
- Add `-CheckOnly` to only report.

## Complexity

Let `U = { v | d(v) < B }` and `E(U)` be edges scanned from `U`.

- Time: `O((|E(U)| + |U|) log |U|)` with binary heap; worst-case `O((m+n) log n)`.
- Space: `Θ(n + m)` graph + `Θ(n)` distances/flags + heap bounded by frontier size.

Use `Graph::memory_estimate_bytes()` to get a byte estimate at runtime.

Here’s the no-BS, self-contained write-up you asked for. It includes the theory, proofs at the right granularity, complexity, comparisons against the usual suspects, and **charts** (model-based, not empirical—use them to reason about trends, not absolutes).

---

# Bounded Multi-Source Shortest Paths (BMSSP)

## What problem it solves

Given a directed graph $G=(V,E)$ with non-negative weights $w:E\to \mathbb{R}_{\ge 0}$, a **set of sources** $S\subseteq V$ with initial offsets $d_0(s)$ (usually 0), and a **distance bound** $B$, compute:

* For every vertex $v$ with true shortest-path distance $d(v) < B$, the exact distance $d(v)$.
* The **explored set** $U=\{v\in V\mid d(v) Multi-source is trivial: seed multiple entries. All standard Dijkstra proofs still apply.

---

## Correctness (sketch you can actually use)

**Invariant** (Dijkstra): When a node $u$ is popped, $\text{dist}[u]=d(u)$.
Proof carries over because (1) weights $\ge 0$, (2) heap order guarantees we never pop a label larger than any alternate path to that node, (3) early exit only **reduces** the set of popped nodes; it never allows popping out of order.

**Boundary lemma.** Let $U$ be nodes popped before exit. Then
$B' = \min\{\hat d(x)\mid x\in V\setminus U\}$ — the smallest tentative distance still in the heap (or discovered and $\ge B$).
So $B' \ge B$ and is the next tight phase boundary if you continue search.

---

## Complexity

Let $U=\{v\mid d(v) Want empirical plots? Run the provided Rust bench across varying $B$, $k=|S|$, $n,m$, and (optionally) a bucketed queue variant. I can script `cargo bench` + CSV + gnuplot for you.

### Crystal implementation

Crystal port lives in `impls/crystal` and follows the same CLI and JSON contract.

Build locally (if Crystal is installed):

```bash
cd impls/crystal
shards build --release
./bin/bmssp_cr --graph grid --rows 20 --cols 20 --k 8 --B 50 --seed 1 --trials 2 --json
```

The bench runner will auto-detect Crystal (`crystal` + `shards` in PATH) and include its results.

---

## Implementation notes that actually matter

* Keep `dist` as `u64`. Use `saturating_add` to avoid overflow during relaxations.
* Don’t attempt “decrease-key”; just push duplicates and skip stale pops. It’s faster with Rust’s `BinaryHeap`.
* Track `B'` while relaxing edges that would cross the bound—this is your next-phase threshold.
* Multi-source is just seeding many `(node, offset)` entries. If you repeat with larger $B$, reuse `dist` as a warm-start and continue; it preserves optimality because labels are monotone.
* Memory: adjacency dominates. The rest is a few `Vec`s of size $n$ and the heap/frontier.

---

## Complexity re-stated (so you don’t scroll back)

* **Time:** $\boxed{ \mathcal{O}\big((|E(U)|+|U|)\log |U|\big) }$
Worst case: $\mathcal{O}((m+n)\log n)$.

* **Space:** $\boxed{ \Theta(n+m) }$ total; working state $\Theta(n)$ + heap $O(|U|)$.

If you want byte-accurate numbers, the Rust crate includes a `memory_estimate_bytes()` helper; for real allocations, measure with `heaptrack`/`massif` or Linux cgroups.

---

## When to prefer other queues

* **Small integer weights** (e.g., 0–255): Dial/radix will beat binary heaps. BMSSP still applies—just stop at $B$—but use buckets.
* **Parallel hardware**: Δ-stepping buckets **plus** bound $B$ works well; you get level-synchronous waves clipped at $B$.
* **Heuristic-rich domains**: A\* with an admissible heuristic can explore **far less than $U$** for the same $B$.

---

## How to produce *real* comparison charts (bench recipe)

* Use the Rust crate I gave you (`cargo bench -p bmssp`) and vary:

* $n,m$ (grid, ER random, power-law)
* number of sources $k$
* bound $B$
* Implement a second queue:

* Dial (if your weights are bounded ints)
* or a bucketed Δ-stepping variant (single-thread first)
* Capture: push counts, relax counts, popped vertices, wall-clock (release), and peak RSS.
* Plot **time vs |U|**, **time vs B**, **pushes per edge**; the trends will match the charts above, with real constants.

---

## TL;DR

* If $B$ is small relative to typical distances, **BMSSP saves you real work**, exactly as the $f\log(fn)$ model predicts.
* If weights are tiny integers, **use buckets** (Dial) and still bound by $B$.
* Otherwise, bounded Dijkstra with a binary heap is the **clean default**—simple, exact, and fast enough.

If you want this rewritten around the **recursive/pivoted BMSSP** from your screenshot (the one with `FindPivots`, batching, etc.), send the full section of the paper and I’ll extend the Rust crate to that variant and give you *measured* charts next.