diff --git a/.grok/skills/improve-docs/references/STYLE.md b/.grok/skills/improve-docs/references/STYLE.md index 4ee3f18..caf408d 100644 --- a/.grok/skills/improve-docs/references/STYLE.md +++ b/.grok/skills/improve-docs/references/STYLE.md @@ -6,7 +6,8 @@ Keep it simple. Prefer delete and clarify over decorate. | Say | Never say | |-----|-----------| -| **payload size** (256 B, 4 KiB) for the published filter | fixture; five leftover type names as if they were different shapes | +| **payload size** (256 B, 4 KiB) for the published size filter | fixture; five leftover type names as if they were different shapes | +| **pattern** as **1P1C**, **4P4C**, **1P4C**, **4P1C** | SPSC, MPMC on the published filter | | **data type** only as the catalog id (`size_256`, `size_4096`) | fixture, fixtures, fixtureKey, `dataset.fixtures` | The published axis is how many bytes each queued item is. Experiment 13 showed that 256 B and 4 KiB tell different stories; 512 B / 1 KiB / 2 KiB do not. Use **payload size** in README, Dashboard copy, and findings. Catalog ids stay `type_id` values (`size_256`, `size_4096`). Older folders may still say `message` / `document` (same lengths). Do not keep `fixture*` as an internal alias. Old `configs.json` may still contain `fixtures`; read it as a fallback, write `data_types`. @@ -29,7 +30,7 @@ Do not rank a library on payload size. Every queue in a cell moves the same byte ## Content style - **One idea per paragraph.** Prefer tables for role/path matrices **on the site / Dashboard**, not by fattening the root README. -- **User terms:** payload size (256 B, 4 KiB), mode (bytes/stream), ops/s, latency, Pareto, baseline. +- **User terms:** payload size (256 B, 4 KiB), pattern (1P1C, 4P4C), ops/s, latency, Pareto, baseline. Do not say SPSC or MPMC on the dashboard filter. - **Never say “fixture”.** The published sample is a **payload size**. The catalog id is a **data type** (`size_256`, `size_4096`). Say that in docs, Dashboard copy, skills, comments, identifiers, and JSON keys. Do not keep `fixture*` as an internal alias “until later.” - **Avoid in user copy:** fixture, median size (we do not measure payload size as a result), harness (prefer benchmark runner), unexplained IQR/P95. - **Honesty line** when ranks appear: within one language; cross-lang directional — prefer **one** place (e.g. Statistics / Method), not a second essay block on README. diff --git a/README.md b/README.md index 51dc79a..f1542d1 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ Every language writes the same columns (nanoseconds). Domain mapping: | `TimeEnq` | Enqueue ns | | `TimeDeq` | Dequeue ns | | `TimeHandoff` | Handoff ns | -| `Pattern` | `bytes` = **SPSC**, `stream` = **MPMC** (not I/O) | +| `Pattern` | `bytes` = **1P1C**, `4p4c` = **4P4C** (`stream` is leftover 2P2C, not I/O) | | `CpuTimeNs` | Process CPU time (spin vs block) | See [Metrics](https://leo-gan.github.io/queue-benchmark/analysis/METRICS/) and [architecture](https://leo-gan.github.io/queue-benchmark/analysis/architecture/). diff --git a/config/benchmark_config.yaml b/config/benchmark_config.yaml index a81603f..6ab1691 100644 --- a/config/benchmark_config.yaml +++ b/config/benchmark_config.yaml @@ -67,15 +67,15 @@ test_data: description: "4 KiB payload (copy cost starts to matter)" supports_circular: false -# Pattern axis (CSV StringOrStream). Logged names are serializer ABI leftovers: -# bytes = SPSC, stream = MPMC. Docs and dashboard say SPSC / MPMC, never "stream API". +# Pattern axis (CSV StringOrStream). Logged names are serializer ABI leftovers. +# Docs and dashboard say 1P1C / 2P2C / 1P4C / 4P1C / 4P4C, never "stream API". io_modes: - name: bytes - aliases: [string, buffer, spsc] - description: "SPSC: single producer, single consumer" + aliases: [string, buffer, spsc, 1p1c] + description: "1P1C: one producer, one consumer" - name: stream - aliases: [stream, mpmc] - description: "MPMC: 2 producers, 2 consumers (when the library supports it)" + aliases: [stream, mpmc, 2p2c] + description: "2P2C: two producers, two consumers (when the library supports it)" - name: 1p4c aliases: [1p4c] description: "1 producer, 4 consumers" diff --git a/config/library/README.md b/config/library/README.md index 6a22369..1b336ec 100644 --- a/config/library/README.md +++ b/config/library/README.md @@ -4,7 +4,7 @@ Named **run configs** select the measurement matrix: - `types` (axis W): `type_id` + `type_config` (payload shape) - `data_type_instance_count` (axis C): items moved per repetition -- `execution.io_modes`: `bytes` = **SPSC**, `stream` = **MPMC** (legacy name; not I/O) +- `execution.io_modes`: `bytes` = **1P1C**, `4p4c` = **4P4C** (CSV leftover `stream` is 2P2C; not I/O) Published type ids: `size_256` (256 B) · `size_4096` (4 KiB). Older experiment folders still use `message` / `document` (same lengths). @@ -14,8 +14,8 @@ Catalog: `schemas/data_catalog_v2.yaml`. | File | Purpose | |------|---------| -| `smoke.yaml` | CI / quick sanity (`size_256`, n=1, SPSC) | -| `default.yaml` | Publication matrix (256 B and 4 KiB × [100, 1000], SPSC+MPMC) | +| `smoke.yaml` | CI / quick sanity (`size_256`, n=1, 1P1C) | +| `default.yaml` | Publication matrix (256 B and 4 KiB × [100, 1000], 1P1C and 4P4C) | ## Usage diff --git a/config/library/default.yaml b/config/library/default.yaml index 82a3bc9..9c6ef29 100644 --- a/config/library/default.yaml +++ b/config/library/default.yaml @@ -1,6 +1,6 @@ # Run config: default (publication matrix) id: default -description: "Two payload sizes (256 B, 4 KiB) × [100, 1000] items; SPSC, 2P2C, 1P4C, 4P1C, 4P4C" +description: "Two payload sizes (256 B, 4 KiB) × [100, 1000] items; 1P1C and 4P4C" data_model_version: 2 types: @@ -16,8 +16,9 @@ compression: execution: mode: full - # bytes = SPSC; stream = MPMC (CSV ABI name; not a stream API) - io_modes: [bytes, stream, 1p4c, 4p1c, 4p4c] + # bytes = 1P1C (CSV leftover name). 4p4c = four producers and four consumers. + # 2P2C / 1P4C / 4P1C stay on experiment 3, not the published matrix. + io_modes: [bytes, 4p4c] budget: soft_seconds_per_10_serializers: 60 diff --git a/config/library/smoke.yaml b/config/library/smoke.yaml index 6305864..3118990 100644 --- a/config/library/smoke.yaml +++ b/config/library/smoke.yaml @@ -1,6 +1,6 @@ # Run config: smoke (CI / fast path) id: smoke -description: "Minimal matrix — 256-byte payload, single instance, SPSC only" +description: "Minimal matrix — 256-byte payload, single instance, 1P1C only" data_model_version: 2 types: diff --git a/dashboard/index.html b/dashboard/index.html index d1409bc..f197d1c 100644 --- a/dashboard/index.html +++ b/dashboard/index.html @@ -105,9 +105,9 @@
- Pattern: + Pattern:
-
@@ -156,7 +156,7 @@

Pareto = not both slower and more CPU-hungry · - Pattern = SPSC vs MPMC · + Pattern = 1P1C or 4P4C · Samples = which trials after warmup (default drops outliers) · Comparison rules · Benchmarks · @@ -302,7 +302,7 @@

Compare

- Pattern + Pattern
@@ -317,7 +317,7 @@

Compare

- Pattern + Pattern
diff --git a/dashboard/main.js b/dashboard/main.js index 9315439..93ac787 100644 --- a/dashboard/main.js +++ b/dashboard/main.js @@ -1687,9 +1687,9 @@ function applyFilterPolicyToAllGroups({ refreshSelectors = false } = {}) { if (modeOptions.includes(wantMode)) { state.currentMode = wantMode; } else { - // Prefer SPSC (bytes) when nothing is selected yet. Insertion order in - // the stats file often starts at 1p4c, which hid the usual first view. - const prefer = ['bytes', 'stream', '1p4c', '4p1c', '4p4c']; + // Prefer 1P1C when nothing is selected yet. Insertion order in the + // stats file often starts at 1p4c, which hid the usual first view. + const prefer = ['bytes', '4p4c', 'stream', '1p4c', '4p1c']; state.currentMode = prefer.find((m) => modeOptions.includes(m)) || modeOptions[0] || ''; } @@ -2545,8 +2545,8 @@ function normalizeMode(mode) { } function modeDisplayLabel(norm) { - if (norm === 'bytes' || norm === 'spsc') return 'SPSC'; - if (norm === 'stream' || norm === 'mpmc') return 'MPMC (2P2C)'; + if (norm === 'bytes' || norm === 'spsc' || norm === '1p1c') return '1P1C'; + if (norm === 'stream' || norm === 'mpmc' || norm === '2p2c') return '2P2C'; if (/^\d+p\d+c$/.test(String(norm || ''))) return String(norm).toUpperCase(); return norm || '—'; } @@ -2589,7 +2589,7 @@ function summarizeStreamHonesty(groups) { function updateStreamHonestyChip() { const chip = document.getElementById('stream-honesty-chip'); if (!chip) return; - // Pattern=stream means MPMC, not stream I/O. Do not show serializer honesty. + // Pattern=stream means 2P2C, not stream I/O. Do not show serializer honesty. chip.hidden = true; chip.textContent = ''; chip.removeAttribute('title'); diff --git a/dashboard/public/data/available_runs.json b/dashboard/public/data/available_runs.json index 5e7ab73..5b52e20 100644 --- a/dashboard/public/data/available_runs.json +++ b/dashboard/public/data/available_runs.json @@ -1,5 +1,6 @@ { "csharp": [ + "2026-09-01-162810", "2026-09-01-153914", "2026-09-01-143923", "2026-09-01-131724", @@ -17,6 +18,7 @@ "2026-08-31-141025" ], "rust": [ + "2026-09-01-162819", "2026-09-01-153934", "2026-09-01-144004", "2026-09-01-131801", @@ -35,6 +37,7 @@ "2026-08-31-141008" ], "python": [ + "2026-09-01-162815", "2026-09-01-153922", "2026-09-01-143936", "2026-09-01-131735", @@ -54,6 +57,7 @@ "2026-08-31-140959" ], "javascript": [ + "2026-09-01-162823", "2026-09-01-153947", "2026-09-01-144029", "2026-09-01-131823", @@ -72,6 +76,7 @@ "2026-08-31-141008" ], "c": [ + "2026-09-01-162822", "2026-09-01-153943", "2026-09-01-144022", "2026-09-01-131817", diff --git a/dashboard/public/data/c_latest.json.gz b/dashboard/public/data/c_latest.json.gz index 05efbca..859aa8e 100644 Binary files a/dashboard/public/data/c_latest.json.gz and b/dashboard/public/data/c_latest.json.gz differ diff --git a/dashboard/public/data/csharp_latest.json.gz b/dashboard/public/data/csharp_latest.json.gz index 9bd19dc..7141a43 100644 Binary files a/dashboard/public/data/csharp_latest.json.gz and b/dashboard/public/data/csharp_latest.json.gz differ diff --git a/dashboard/public/data/javascript_latest.json.gz b/dashboard/public/data/javascript_latest.json.gz index 2e43353..3486736 100644 Binary files a/dashboard/public/data/javascript_latest.json.gz and b/dashboard/public/data/javascript_latest.json.gz differ diff --git a/dashboard/public/data/python_latest.json.gz b/dashboard/public/data/python_latest.json.gz index b14f1f2..ee0d4cd 100644 Binary files a/dashboard/public/data/python_latest.json.gz and b/dashboard/public/data/python_latest.json.gz differ diff --git a/dashboard/public/data/rust_latest.json.gz b/dashboard/public/data/rust_latest.json.gz index 20490e0..3e6ee26 100644 Binary files a/dashboard/public/data/rust_latest.json.gz and b/dashboard/public/data/rust_latest.json.gz differ diff --git a/dashboard/public/data/stats_c_latest.json.gz b/dashboard/public/data/stats_c_latest.json.gz index 280ceb6..2cbde6e 100644 Binary files a/dashboard/public/data/stats_c_latest.json.gz and b/dashboard/public/data/stats_c_latest.json.gz differ diff --git a/dashboard/public/data/stats_csharp_latest.json.gz b/dashboard/public/data/stats_csharp_latest.json.gz index 535e1e7..b1ca586 100644 Binary files a/dashboard/public/data/stats_csharp_latest.json.gz and b/dashboard/public/data/stats_csharp_latest.json.gz differ diff --git a/dashboard/public/data/stats_javascript_latest.json.gz b/dashboard/public/data/stats_javascript_latest.json.gz index 5737a5e..feed659 100644 Binary files a/dashboard/public/data/stats_javascript_latest.json.gz and b/dashboard/public/data/stats_javascript_latest.json.gz differ diff --git a/dashboard/public/data/stats_python_latest.json.gz b/dashboard/public/data/stats_python_latest.json.gz index c99873f..ea0e175 100644 Binary files a/dashboard/public/data/stats_python_latest.json.gz and b/dashboard/public/data/stats_python_latest.json.gz differ diff --git a/dashboard/public/data/stats_rust_latest.json.gz b/dashboard/public/data/stats_rust_latest.json.gz index 9e0f080..268f48a 100644 Binary files a/dashboard/public/data/stats_rust_latest.json.gz and b/dashboard/public/data/stats_rust_latest.json.gz differ diff --git a/docs/analysis/BENCHMARK_DESIGN.md b/docs/analysis/BENCHMARK_DESIGN.md index 1526857..20fed8e 100644 --- a/docs/analysis/BENCHMARK_DESIGN.md +++ b/docs/analysis/BENCHMARK_DESIGN.md @@ -34,13 +34,13 @@ priority, blocking vs spin vs yield, SPSC vs MPMC. | CSV value | Say this | What actually ran | |-----------|----------|-------------------| -| `bytes` | **SPSC** | One producer, one consumer | -| `stream` | **MPMC** | Two producers, two consumers | -| `1p4c` / `4p1c` / `4p4c` | Named P×C | Default full matrix + experiment 3 | +| `bytes` | **1P1C** | One producer, one consumer | +| `stream` | **2P2C** | Two producers, two consumers (experiment / old logs) | +| `1p4c` / `4p1c` / `4p4c` | **1P4C** / **4P1C** / **4P4C** | Experiment 3; 4P4C is also on the default matrix | There is no stream I/O in this suite. In-process queues move already-built -payloads. If a library cannot do MPMC, **skip the cell** — do not fake it -with a mutex around an SPSC structure. +payloads. If a library cannot run with more than one producer or consumer, +**skip the cell** — do not fake it with a mutex around a 1P1C structure. ## What we time @@ -75,7 +75,7 @@ Same run modes as [Modes](modes.md): smoke / all-single / full / research. |------|-----| | Languages | C, C#, JavaScript, Python, Rust | | Categories | T and A on the default matrix; P/S/D opt-in (see [categories](queue_categories.md)) | -| Pattern | SPSC, 2P2C, 1P4C, 4P1C, 4P4C (skip if the library cannot) | +| Pattern | 1P1C and 4P4C (skip 4P4C if the library cannot) | | Payloads | 256 B (`size_256`) and 4 KiB (`size_4096`) | | Experiments | [01](../experiments/01-spsc-handoff/)–[12](../experiments/12-durable-local/) | @@ -94,7 +94,7 @@ Same run modes as [Modes](modes.md): smoke / all-single / full / research. ### Category A -Same SPSC/MPMC cells as T, but the workers are async tasks. Extra tests: +Same 1P1C / 4P4C cells as T, but the workers are async tasks. Extra tests: experiment 7 (many waiters), 8 (bounded async), 9 (cancel). Do not rank an A library against a T library. @@ -125,7 +125,7 @@ is multi-socket and the run says so. 1. Pick a **language**. 2. Pick a **category** (T or A) in the dashboard Category control. -3. Pick a **pattern** (SPSC or MPMC) and a **payload**. +3. Pick a **pattern** (1P1C or 4P4C) and a **payload size**. 4. Compare families inside that slice. There is no overall score. A queue that wins T1 can lose T2 or T4. diff --git a/docs/analysis/COMPARISON_RULES.md b/docs/analysis/COMPARISON_RULES.md index 12c799d..aa10baa 100644 --- a/docs/analysis/COMPARISON_RULES.md +++ b/docs/analysis/COMPARISON_RULES.md @@ -5,8 +5,8 @@ a chart that looks decisive and is wrong. ## Never -1. **Never publish one “fastest queue” number.** A library that wins SPSC - handoff of a 256-byte ticket can lose at 4 KiB, under 4 producers, or +1. **Never publish one “fastest queue” number.** A library that wins 1P1C + handoff of a 256-byte ticket can lose at 4 KiB, under 4P4C, or when the queue is bounded. Report by category × pattern × payload. 2. **Never rank across languages.** A C ring and a Python deque are not @@ -25,14 +25,14 @@ a chart that looks decisive and is wrong. concurrency. It does not hand a payload from a producer to a consumer. It lives under Other, not A. -6. **Never invent MPMC.** If the library cannot run two producers and two - consumers, skip the cell. Do not wrap an SPSC structure in a mutex and - log it as MPMC. +6. **Never invent a multi-worker cell.** If the library cannot run four + producers and four consumers, skip 4P4C. Do not wrap a 1P1C structure + in a mutex and log it as 4P4C. ## Always - Compare **inside one language and one communication category**. -- Say **SPSC / MPMC** in prose. The CSV still says `bytes` / `stream`. +- Say **1P1C / 4P4C** in prose. The CSV still says `bytes` / `4p4c`. - A failed fidelity check is an error, not a speed win. - Warmup index 0 stays in the raw CSV; analysis drops it. diff --git a/docs/analysis/METRICS.md b/docs/analysis/METRICS.md index 0230082..61ec49a 100644 --- a/docs/analysis/METRICS.md +++ b/docs/analysis/METRICS.md @@ -24,7 +24,7 @@ Do **not** invent `msgs_per_cpu_sec` from wall time. | `Size` / `SizeGzip` / `SizeZstd` / `median_size_bytes` | Serializer leftovers. Payload bytes are the **data type**, not a result. | **Removed.** New CSVs and stats do not write these. Old logs may still contain `Size`; analysis ignores it. | | `mean_fidelity` | `FidelityScore` = fraction of items that arrived in order. | **Gate**, not a score. If `< 1`, the row is invalid for ranking. Typical value is `1.0`. | | `mean_memory_peak_bytes` | Process RSS (`getrusage` / `PeakWorkingSet64` / `process.memoryUsage().rss`). | Provenance. Usually the process, not the queue. Do not run a “most compact” contest on it. | -| `StreamMode` / honesty | Serializer “native vs adapted stream I/O”. There is no stream I/O in this suite. `Pattern=stream` means **MPMC**. | Do not show an honesty column. | +| `StreamMode` / honesty | Serializer “native vs adapted stream I/O”. There is no stream I/O in this suite. `Pattern=stream` means **2P2C**. | Do not show an honesty column. | | `OpPerSecEnq` / `Deq` / `Handoff` in the CSV | Convenience copies of `1e9 / Time*`. | Analysis recomputes from times. Do not treat CSV ops as a second clock. | ## Identity (not metrics) @@ -33,8 +33,8 @@ Do **not** invent `msgs_per_cpu_sec` from wall time. `DataTypeInstanceCount`, `TypeConfigHash`, `Repetitions`, `RepetitionIndex`, `RunOrder`, `SchedulePosition`, `NativeKind` (implementation family label). -`Pattern`: `bytes` = SPSC, `stream` = MPMC 2P2C, plus named `1p4c` / `4p1c` / -`4p4c`. +`Pattern`: `bytes` = 1P1C, `stream` = 2P2C (old logs / experiment 3), plus +named `1p4c` / `4p1c` / `4p4c`. The published matrix is 1P1C and 4P4C. ## Derived stats (not CSV columns) diff --git a/docs/analysis/architecture.md b/docs/analysis/architecture.md index babbdef..03a92f3 100644 --- a/docs/analysis/architecture.md +++ b/docs/analysis/architecture.md @@ -71,14 +71,15 @@ serializer-benchmark columns so historical logs keep loading. | `TimeEnq` | Enqueue ns | | `TimeDeq` | Dequeue ns | | `TimeHandoff` | Handoff ns | -| `Pattern` | `bytes` = **SPSC**, `stream` = **MPMC** (not I/O) | +| `Pattern` | `bytes` = **1P1C**, `4p4c` = **4P4C** (`stream` is leftover 2P2C, not I/O) | | `CpuTimeNs` | Process CPU time (spin vs block) | ## Patterns -- **SPSC** (CSV `bytes`): one producer, one consumer. Every library must implement this. -- **MPMC** (CSV `stream`): two producers, two consumers. Libraries that cannot do MPMC - skip the cell rather than fake it. +- **1P1C** (CSV `bytes`): one producer, one consumer. Every library must implement this. +- **4P4C** (CSV `4p4c`): four producers, four consumers. Libraries that cannot + run that cell skip it rather than fake it. -The word *stream* is a leftover ABI name. The dashboard and docs say SPSC / MPMC. +The word *stream* is a leftover ABI name for 2P2C. The dashboard and docs say +1P1C / 4P4C. See [Benchmark design](BENCHMARK_DESIGN.md). diff --git a/docs/analysis/modes.md b/docs/analysis/modes.md index fc43763..7551d18 100644 --- a/docs/analysis/modes.md +++ b/docs/analysis/modes.md @@ -5,7 +5,7 @@ hard-code repetition counts. | Mode | Reps | Matrix | Use | |------|------|--------|-----| -| `smoke` | 2 | `config/library/smoke.yaml` (256 B, n=1, SPSC) | CI, “does it run?” | +| `smoke` | 2 | `config/library/smoke.yaml` (256 B, n=1, 1P1C) | CI, “does it run?” | | `all-single` | 10 | default matrix | Quick full pass | | `full` | 100 | default matrix | Publication | | `research` | 500 | default matrix | High-power stats | diff --git a/docs/analysis/queue_categories.md b/docs/analysis/queue_categories.md index 98a480c..367ae43 100644 --- a/docs/analysis/queue_categories.md +++ b/docs/analysis/queue_categories.md @@ -61,13 +61,15 @@ These are **properties** that can apply inside T or A: - bounded vs unbounded (backpressure) - FIFO vs priority - blocking vs spin vs yield -- SPSC vs MPMC (workload / pattern) +- 1P1C vs 4P4C (workload / pattern) ## Patterns | Say | CSV `Pattern` | Work | |-----|---------------|------| -| SPSC | `bytes` | 1 producer, 1 consumer | -| MPMC | `stream` | 2 producers, 2 consumers | +| 1P1C | `bytes` | 1 producer, 1 consumer | +| 4P4C | `4p4c` | 4 producers, 4 consumers | +| 2P2C | `stream` | 2 producers, 2 consumers (old logs / experiment 3) | +| 1P4C / 4P1C | `1p4c` / `4p1c` | Experiment 3 only | -Logged values `bytes` / `stream` are pattern tags, not I/O. +Logged values `bytes` / `stream` / `4p4c` are pattern tags, not I/O. diff --git a/docs/c/index.md b/docs/c/index.md index 43cd105..d0ea11e 100644 --- a/docs/c/index.md +++ b/docs/c/index.md @@ -11,7 +11,7 @@ |----------|----------|---------------|---------|-------| | `mutex-queue` | locked | T (thread) | harness | pthread mutex + ring | | `lfqueue` | concurrent | T (thread) | lfqueue (vendored) | Lock-free MPMC FIFO | -| `spsc-ring` | spsc | T (thread) | harness | Single-producer ring; MPMC skipped | +| `spsc-ring` | spsc | T (thread) | harness | Single-producer ring; 1P1C only | | `steal-deque` | work-stealing | T (thread) | harness | Owner-push / steal-from-top | | `pipe-ipc` | concurrent | P (process) | harness | Opt-in pipe + fork | | `shared-ring` | spsc | S (shared) | harness | Opt-in mmap + fork ring | diff --git a/docs/experiments/index.md b/docs/experiments/index.md index 5d8d7d3..760eaa4 100644 --- a/docs/experiments/index.md +++ b/docs/experiments/index.md @@ -4,8 +4,8 @@ One question per folder under `experiments/`. Read [PLAN.md](../../experiments/P | # | Question | |---|----------| -| [01-spsc-handoff](../../experiments/01-spsc-handoff/) | SPSC handoff of a small message | -| [02-payload-size](../../experiments/02-payload-size/) | Same SPSC at 4 KiB | +| [01-spsc-handoff](../../experiments/01-spsc-handoff/) | 1P1C handoff of a small message | +| [02-payload-size](../../experiments/02-payload-size/) | Same 1P1C at 4 KiB | | [03-contention](../../experiments/03-contention/) | 1P4C / 4P1C / 4P4C | | [04-backpressure](../../experiments/04-backpressure/) | Bounded queue, slow consumer | | [05-wakeup](../../experiments/05-wakeup/) | Empty-queue wake | diff --git a/docs/internal/CATEGORY_BENCHMARK_PLAN.md b/docs/internal/CATEGORY_BENCHMARK_PLAN.md index e3680c1..63187ca 100644 --- a/docs/internal/CATEGORY_BENCHMARK_PLAN.md +++ b/docs/internal/CATEGORY_BENCHMARK_PLAN.md @@ -21,10 +21,11 @@ CSV `Pattern` (formerly `StringOrStream`): | Logged value | Queue meaning | |--------------|---------------| -| `bytes` | SPSC (1 producer, 1 consumer) | -| `stream` | MPMC (today: 2 producers, 2 consumers) | +| `bytes` | 1P1C (1 producer, 1 consumer) | +| `4p4c` | 4P4C (published multi-worker cell) | +| `stream` | 2P2C (old logs / experiment 3) | -The dashboard and methodology must say **SPSC / MPMC**, never “stream API.” +The dashboard and methodology must say **1P1C / 4P4C**, never “stream API” and never SPSC / MPMC on the published filter. ## Communication categories @@ -55,9 +56,9 @@ Async channels (`asyncio.Queue`, `Channel`, `tokio::mpsc`) live in **A**. ## Tests -Always-on axes: language, category, family, pattern (`bytes` SPSC, -`stream` 2P2C, plus `1p4c` / `4p1c` / `4p4c` on the default matrix), -bound, payload, mode (smoke / all-single / full). +Always-on axes: language, category, family, pattern (`bytes` 1P1C and +`4p4c` 4P4C on the default matrix; `1p4c` / `4p1c` / `stream` on +experiment 3), bound, payload, mode (smoke / all-single / full). Primary metrics: completed handoffs/s, enqueue ns, dequeue ns, end-to-end handoff, p50/p99/`total_p999_ns`, peak RSS, `msgs_per_cpu_sec` when diff --git a/docs/internal/DESIGN.md b/docs/internal/DESIGN.md index bc7d7eb..cd6bf55 100644 --- a/docs/internal/DESIGN.md +++ b/docs/internal/DESIGN.md @@ -26,7 +26,7 @@ This document is the source of truth for the stacked PRs. Domain words change | `TimeDeser` | Dequeue (consume) ns | | `TimeSerAndDeser` | Handoff / round-trip ns | | `TestDataName` | Data type id (`message`, `document`, …) | - | `StringOrStream` | Pattern: **SPSC** (logged as `bytes`) or **MPMC** (logged as `stream`) | + | `StringOrStream` | Pattern: **1P1C** (logged as `bytes`) or **4P4C** (logged as `4p4c`) | | `FidelityScore` | 1.0 if every item arrived in order; else < 1 | Keeping names lets us reuse the analysis package and dashboard pipeline diff --git a/docs/internal/IMPLEMENTATION_RUN.md b/docs/internal/IMPLEMENTATION_RUN.md index b970750..4f0da78 100644 --- a/docs/internal/IMPLEMENTATION_RUN.md +++ b/docs/internal/IMPLEMENTATION_RUN.md @@ -16,7 +16,7 @@ items. Category **N** (localhost broker) stays out of this lab, as designed. | True two-process **P** | Same-process `multiprocessing.Queue` pickle | Parent + child `Process` producers/consumers (`run_cross_process`) | | True two-process **S** | Same-process mapped `Array` | Parent maps `Array`/`Value`; child process produce + consume | | Work-stealing family | None | Python `steal-deque` (owner-push / steal-from-top) | -| Default matrix P×C | SPSC + 2P2C only | `bytes`, `stream`, `1p4c`, `4p1c`, `4p4c` | +| Default matrix P×C | 1P1C + 4P4C | `bytes`, `4p4c` (2P2C / 1P4C / 4P1C stay on experiment 3) | | Wakeup / burst / cancel | Python only | Python, Rust, C#, C, JavaScript | | p99.9 | Described, not first-class | `report_percentiles` includes 99.9 → `total_p999_ns` (label is `p999`, not `int(99.9)=99`) | | Messages / CPU-second | Described, not first-class | Optional CSV `CpuTimeNs`; stats `msgs_per_cpu_sec` (high importance) | @@ -52,18 +52,17 @@ broker). That is a system bench, not a data-structure bench. - This is a locked Chase-Lev *shape*, not a published lock-free steal algorithm. It exists so the family has a first member. -### 3. Default full matrix includes 1P4C / 4P1C / 4P4C +### 3. Default full matrix is 1P1C and 4P4C `config/library/default.yaml`: ```yaml -io_modes: [bytes, stream, 1p4c, 4p1c, 4p4c] +io_modes: [bytes, 4p4c] ``` -CSV `Pattern` values stay `bytes` = SPSC, `stream` = 2P2C. -Named patterns are first-class cells. Libraries that cannot do MPMC skip -those cells (existing `_can_run` / SPSC-only rules). A full bench is -about 2.5× the old SPSC+2P2C matrix. +CSV `Pattern` values stay `bytes` = 1P1C. `4p4c` is four producers and +four consumers. Libraries that cannot run with more than one producer or +consumer skip 4P4C. 2P2C, 1P4C, and 4P1C stay on experiment 3. ### 4. Wakeup / burst / cancel in every language diff --git a/docs/javascript/index.md b/docs/javascript/index.md index 4ab811b..b722bff 100644 --- a/docs/javascript/index.md +++ b/docs/javascript/index.md @@ -9,9 +9,9 @@ | Log name | Category | Communication | Package | Notes | |----------|----------|---------------|---------|-------| -| `Array` | locked | T (thread) | builtin | `push` / `shift` SPSC baseline | -| `denque` | locked | T (thread) | denque | O(1) circular deque; SPSC only | -| `yocto-queue` | locked | T (thread) | yocto-queue | O(1) linked-list FIFO; SPSC only | +| `Array` | locked | T (thread) | builtin | `push` / `shift` 1P1C baseline | +| `denque` | locked | T (thread) | denque | O(1) circular deque; 1P1C only | +| `yocto-queue` | locked | T (thread) | yocto-queue | O(1) linked-list FIFO; 1P1C only | | `fastq` | concurrent | T (thread) | fastq | In-process job queue | | `p-queue` | scheduler | — | p-queue | Concurrency limiter, not a handoff queue | | `steal-deque` | work-stealing | T (thread) | harness | Owner-push / steal-from-top | diff --git a/docs/python/index.md b/docs/python/index.md index 6a6998e..ef69078 100644 --- a/docs/python/index.md +++ b/docs/python/index.md @@ -14,15 +14,15 @@ | `queue.SimpleQueue` | concurrent | T (thread) | stdlib | Unbounded C-accelerated MPMC; backpressure cells skipped | | `asyncio.Queue` | async | A (async) | stdlib | Event-loop queue | | `janus` | async | A (async) | janus | Async face only; not the hybrid thread↔async path | -| `spsc-ring` | spsc | T (thread) | harness | Single-producer ring; MPMC skipped | +| `spsc-ring` | spsc | T (thread) | harness | Single-producer ring; 1P1C only | | `steal-deque` | work-stealing | T (thread) | harness | Owner-push / steal-from-top | | `multiprocessing.Queue` | concurrent | P (process) | stdlib | Opt-in two-process IPC | | `multiprocessing.SimpleQueue` | concurrent | P (process) | stdlib | Opt-in unbounded IPC pipe | | `shared-ring` | spsc | S (shared) | harness | Opt-in two-process mapped ring | | `sqlite-queue` | durable | D (durable) | stdlib | Opt-in SQLite queue | -SPSC is one producer / one consumer. MPMC is two producers / two consumers -(CSV still logs `bytes` / `stream`). Compare T libraries separately from A. +The published patterns are **1P1C** and **4P4C**. CSV still logs `bytes` +for 1P1C. Compare T libraries separately from A. See [Benchmark design](../analysis/BENCHMARK_DESIGN.md). Celery, RQ, Redis, Kafka, and the other broker/task libraries from the diff --git a/docs/rust/index.md b/docs/rust/index.md index 3ce9973..ec71412 100644 --- a/docs/rust/index.md +++ b/docs/rust/index.md @@ -9,7 +9,7 @@ | Log name | Category | Communication | Crate | Notes | |----------|----------|---------------|-------|-------| -| `std-mpsc` | concurrent | T (thread) | std | MPSC only; MPMC cells skipped | +| `std-mpsc` | concurrent | T (thread) | std | One consumer; skips 4P4C | | `crossbeam-channel` | concurrent | T (thread) | crossbeam-channel | Unbounded MPMC | | `flume` | concurrent | T (thread) | flume | Unbounded MPMC; sync face only | | `tokio-mpsc` | async | A (async) | tokio | Unbounded async MPSC | diff --git a/experiments/PLAN.md b/experiments/PLAN.md index 2046063..0e62c38 100644 --- a/experiments/PLAN.md +++ b/experiments/PLAN.md @@ -5,8 +5,8 @@ by relative handoff time vs the fastest on that sample. Do not crown a winner. | # | Folder | Status | Question | |---|--------|--------|----------| -| 1 | [01-spsc-handoff](01-spsc-handoff/) | Ready | SPSC handoff of a small message | -| 2 | [02-payload-size](02-payload-size/) | Ready | Same SPSC, 4 KiB payload | +| 1 | [01-spsc-handoff](01-spsc-handoff/) | Ready | 1P1C handoff of a small message | +| 2 | [02-payload-size](02-payload-size/) | Ready | Same 1P1C, 4 KiB payload | | 3 | [03-contention](03-contention/) | Ready | 1P4C / 4P1C / 4P4C | | 4 | [04-backpressure](04-backpressure/) | Ready | Bounded queue, slow consumer | | 5 | [05-wakeup](05-wakeup/) | Ready | Empty-queue wake latency | @@ -17,7 +17,7 @@ by relative handoff time vs the fastest on that sample. Do not crown a winner. | 10 | [10-process-ipc](10-process-ipc/) | Ready | Category P vs in-process | | 11 | [11-shared-memory](11-shared-memory/) | Ready | Category S ring | | 12 | [12-durable-local](12-durable-local/) | Ready | Category D sqlite | -| 13 | [13-payload-size-sweep](13-payload-size-sweep/) | Ready | Which payload sizes change SPSC ranking | +| 13 | [13-payload-size-sweep](13-payload-size-sweep/) | Ready | Which payload sizes change 1P1C ranking | T1=01, T2=02, T3=03, T4=04, T5=05, T6=06, A2=07, A3=08, A4=09. P/S/D have Python runners only. Do not plot P/S/D next to T. @@ -51,3 +51,10 @@ See the size-dimension follow-up on this branch. Combined page: `experiments/13-payload-size-sweep/results.md`. PR: https://github.com/leo-gan/queue-benchmark/pull/32 + +## After the pattern-dimension collapse + +The published pattern filter is **1P1C** and **4P4C**. Those are the two +cells that change first place. 2P2C, 1P4C, and 4P1C stay on experiment 3. +The dashboard never says SPSC or MPMC for this filter. CSV `bytes` is still +1P1C. Eight-worker cells are not on the default matrix. diff --git a/experiments/README.md b/experiments/README.md index 0906f5a..8176c7b 100644 --- a/experiments/README.md +++ b/experiments/README.md @@ -7,8 +7,8 @@ A laboratory notebook of narrow questions this benchmark can run. Each experiment is one question. Edit that folder’s **`experiment.yaml`**. Each language run is a subfolder. Combined numbers live in `results.json`. -- Experiment 1: [`01-spsc-handoff/`](01-spsc-handoff/) — SPSC handoff of a small message -- Experiment 2: [`02-payload-size/`](02-payload-size/) — same SPSC at 4 KiB +- Experiment 1: [`01-spsc-handoff/`](01-spsc-handoff/) — 1P1C handoff of a small message +- Experiment 2: [`02-payload-size/`](02-payload-size/) — same 1P1C at 4 KiB - Experiment 3: [`03-contention/`](03-contention/) — 1P4C / 4P1C / 4P4C - Experiment 4: [`04-backpressure/`](04-backpressure/) — bounded queue, slow consumer - Experiment 5: [`05-wakeup/`](05-wakeup/) — empty-queue wake