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