diff --git a/.github/ISSUE_TEMPLATE/review_request.md b/.github/ISSUE_TEMPLATE/review_request.md
index 487b892..c0e4294 100644
--- a/.github/ISSUE_TEMPLATE/review_request.md
+++ b/.github/ISSUE_TEMPLATE/review_request.md
@@ -5,8 +5,7 @@ title: "[review] : "
labels: review
---
-Thanks for reviewing Quant Systems Lab. This project actively wants **adversarial** criticism —
-the goal is to find what is wrong, not to confirm what looks right. See
+Thanks for reviewing Quant Systems Lab. This project actively wants **adversarial** criticism, the goal is to find what is wrong, not to confirm what looks right. See
[`docs/review_request.md`](../../docs/review_request.md) for the areas where review is most
valuable, and note that **no external review has happened yet** (every claim is self-certified).
@@ -20,7 +19,7 @@ external review process / other. -->
## What is wrong or unclear
-
+
## Evidence / reproduction (optional)
@@ -32,7 +31,6 @@ external review process / other. -->
---
-Context: this repo is a deterministic exchange **simulator** and a portfolio systems project —
-**not** a production exchange, and it makes no latency, throughput, or profitability claims.
+Context: this repo is a deterministic exchange **simulator** and a portfolio systems project, **not** a production exchange, and it makes no latency, throughput, or profitability claims.
Accepted and rejected feedback is recorded in
[`docs/review_feedback.md`](../../docs/review_feedback.md).
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index ca60829..336d3f7 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,10 +1,10 @@
## Milestone
-M__ —
+M__,
## Summary
-<2–5 sentences>
+<2-5 sentences>
## Definition of Done
diff --git a/.gitignore b/.gitignore
index e4f4260..cbb7492 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,6 @@ results/*.json
results/*.csv
results/*.sqlite
-# Agent working files — never committed
+# Agent working files, never committed
.agents/
AGENTS.md
diff --git a/AGENTS.md b/AGENTS.md
index 444799e..abd917d 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,4 +1,4 @@
-# AGENTS.md — Quant Systems Lab
+# AGENTS.md. Quant Systems Lab
> **Project memory for Codex. Auto-loaded every session. Keep it current.**
> Starting or resuming work? Read this file → then `PROGRESS.md` → then `MILESTONES.md` → then `HANDOFF.md` → then run `/resume`.
@@ -29,7 +29,7 @@ The repo should look like disciplined human engineering, not a tutorial dump, no
---
-## Golden rules — non-negotiable
+## Golden rules, non-negotiable
1. **Never commit or push to `main`.** All work happens on a feature branch.
2. **One milestone = one feature branch = one squash-merge PR.**
@@ -68,7 +68,7 @@ The repo should look like disciplined human engineering, not a tutorial dump, no
---
-## Operating model — AI-first, human-in-the-loop
+## Operating model. AI-first, human-in-the-loop
Codex does:
@@ -91,11 +91,11 @@ The human does:
Resumability is anchored in:
-1. `CLAUDE.md` — canonical Claude Code project memory.
-2. `AGENTS.md` — Codex-facing mirror/adapter. Keep synchronized with `CLAUDE.md`.
-3. `MILESTONES.md` — ordered roadmap and milestone definitions.
-4. `PROGRESS.md` — live state and resume anchor.
-5. `HANDOFF.md` — operator manual tying the files together.
+1. `CLAUDE.md`, canonical Claude Code project memory.
+2. `AGENTS.md`. Codex-facing mirror/adapter. Keep synchronized with `CLAUDE.md`.
+3. `MILESTONES.md`, ordered roadmap and milestone definitions.
+4. `PROGRESS.md`, live state and resume anchor.
+5. `HANDOFF.md`, operator manual tying the files together.
6. Git history and PR state.
`AGENTS.md` and `CLAUDE.md` must agree on workflow rules, command lists, roadmap state,
@@ -163,7 +163,7 @@ Known constraints:
from a bare-metal Apple M2 (aarch64) Fedora Asahi host: real
`cycles`/`instructions`/`branches`/`branch-misses`, but `cache-references`/`cache-misses` are
unsupported by the Apple Silicon PMU. Issue #90's residual is the cache-counter set specifically,
- which needs a PMU microarchitecture that exposes it (x86_64, or an ARM server core) — bare metal
+ which needs a PMU microarchitecture that exposes it (x86_64, or an ARM server core), bare metal
alone is not enough. Do not relabel it "full PMU evidence" or "constrained Docker validation". The
`perf record` hot-symbol report (`results/perf_report_linux.txt`) is a **software cpu-clock
sampling** profile, not PMU evidence.
@@ -177,14 +177,14 @@ optional add-on. For every substantive task, actively reach for the relevant ser
falling back to ad-hoc shell:
- `codescene` Code Health review on every file you touch, and `analyze_change_set` before opening
- or updating any PR — a gate, not a nicety.
+ or updating any PR, a gate, not a nicety.
- `git` / `github` for diffs, blame, history, and PR / issue / branch / commit / milestone review.
- `qsl-results` (SQLite) to record and query benchmark history (storage / NUMA / false-sharing /
perf / recovery) instead of re-parsing committed text artifacts.
- `codex` for an independent second-opinion review of any non-trivial change before a milestone is
finished.
- `context7` for current docs of any library / framework / CLI / API before relying on its
- behavior — do not answer from memory.
+ behavior, do not answer from memory.
- `sequential_thinking` to structure multi-step refactor or risk planning.
- `filesystem` for repository read / edit / search and the project docs (ADRs, MILESTONES.md,
PROGRESS.md, HANDOFF.md, review docs).
@@ -196,28 +196,28 @@ rules). Never invent an MCP server or tool that is not configured.
Local Codex client MCP servers currently configured:
-- `codescene` — use for repository-health analysis, file Code Health review, branch/change-set
+- `codescene`, use for repository-health analysis, file Code Health review, branch/change-set
review, and pre-commit Code Health safeguards.
-- `playwright` — use for browser automation and rendered web-flow verification when a task needs a
+- `playwright`, use for browser automation and rendered web-flow verification when a task needs a
real browser-level check.
-- `filesystem` — available for MCP filesystem access scoped to this repository; normal shell/git
+- `filesystem`, available for MCP filesystem access scoped to this repository; normal shell/git
file operations remain acceptable for ordinary repo edits.
-- `sequential_thinking` — use for complex multi-step planning, especially when refactor sequencing
+- `sequential_thinking`, use for complex multi-step planning, especially when refactor sequencing
or risk tradeoffs need explicit structure.
-- `memory` — use only for durable project-memory facts that should survive sessions; do not store
+- `memory`, use only for durable project-memory facts that should survive sessions; do not store
secrets or speculative notes.
-- `docker` — use for Docker/container lifecycle checks and Linux-container verification when a
+- `docker`, use for Docker/container lifecycle checks and Linux-container verification when a
milestone calls for containerized validation.
-- `context7` — use for current library/tool documentation when exact external API behavior matters.
-- `node_repl` — use when JavaScript/browser-plugin workflows require the persistent Node-backed
+- `context7`, use for current library/tool documentation when exact external API behavior matters.
+- `node_repl`, use when JavaScript/browser-plugin workflows require the persistent Node-backed
kernel.
-- `codex` — Codex CLI exposed as an MCP server (`codex mcp-server`); use for an independent
+- `codex`. Codex CLI exposed as an MCP server (`codex mcp-server`); use for an independent
second-opinion review of non-trivial changes before finishing a milestone.
-- `git` — structured git access (diffs, blame, log, branch) over this repo for artifact
+- `git`, structured git access (diffs, blame, log, branch) over this repo for artifact
provenance, milestone audits, and release preparation.
-- `github` — official GitHub MCP server for PR review, issue/milestone tracking, branch and commit
+- `github`, official GitHub MCP server for PR review, issue/milestone tracking, branch and commit
inspection, and release notes (needs a one-time OAuth via `/mcp`; the `gh` CLI is the fallback).
-- `qsl-results` — SQLite store of structured benchmark history (storage/NUMA/false-sharing/perf/
+- `qsl-results`. SQLite store of structured benchmark history (storage/NUMA/false-sharing/perf/
recovery) so results are queryable instead of re-parsed from committed text artifacts.
Postgres and Perplexity MCP servers are intentionally not configured. Do not assume database or
@@ -372,37 +372,37 @@ Avoid unnecessary dependencies. A small systems repo with clear primitives beats
Keep this synchronized with the Makefile.
-- `make configure` — configure dev build
-- `make build` — build dev preset
-- `make test` — run CTest
-- `make check` — format check + build + tests
-- `make fmt` — apply clang-format
-- `make tidy` — clang-tidy target if available
-- `make bench` — run benchmark suite
-- `make bench-diff` — run differential harness benchmarks
-- `make bench-allocator` — run M28 allocator experiment
-- `make bench-storage` — run M32 storage experiment
-- `make bench-recovery` — run M46 recovery benchmarking (full-replay restart vs book rebuild)
-- `make perf-stat` — run Linux `perf stat` workflow where supported
-- `make perf-record` — run Linux `perf record/report` workflow where supported
-- `make flamegraph` — render a Linux `perf` call-graph flamegraph (SVG) where supported
-- `make numa-study` — run Linux CPU-affinity / scheduler-migration / NUMA-locality study where supported
-- `make false-sharing-study` — run benchmark-only packed-vs-padded SPSC cursor contention study
-- `make profile-io` — run Linux syscall/socket-path profiling where supported
-- `make socket-load` — Linux multi-client TCP connection-scaling load experiment
-- `make asan` — build/run sanitizer preset
-- `make tsan` — build/run ThreadSanitizer concurrency tests
-- `make concurrency-stress` — opt-in repeated concurrency validation loop
-- `make socket-stress` — UDP socket-buffer / burst-loss experiment
-- `make crash-recovery` — SIGKILL crash / torn-tail recovery validation for the event log
-- `make dpdk-check` — run the M48 non-mutating DPDK environment support check
-- `make nic-offload-check` — run the M49 non-mutating NIC offload/timestamping capability check
-- `make demo` — run local replay + TCP gateway demo
-- `make check-fixtures` — regenerate and verify differential fixtures
-- `make check-manifest` — verify fixture provenance manifest
-- `make determinism` — verify fixture determinism across compilers where supported
-- `make divergence-demo` — exercise the shrinker on an injected divergence
-- `make clean` — remove build artifacts
+- `make configure`, configure dev build
+- `make build`, build dev preset
+- `make test`, run CTest
+- `make check`, format check + build + tests
+- `make fmt`, apply clang-format
+- `make tidy`, clang-tidy target if available
+- `make bench`, run benchmark suite
+- `make bench-diff`, run differential harness benchmarks
+- `make bench-allocator`, run M28 allocator experiment
+- `make bench-storage`, run M32 storage experiment
+- `make bench-recovery`, run M46 recovery benchmarking (full-replay restart vs book rebuild)
+- `make perf-stat`, run Linux `perf stat` workflow where supported
+- `make perf-record`, run Linux `perf record/report` workflow where supported
+- `make flamegraph`, render a Linux `perf` call-graph flamegraph (SVG) where supported
+- `make numa-study`, run Linux CPU-affinity / scheduler-migration / NUMA-locality study where supported
+- `make false-sharing-study`, run benchmark-only packed-vs-padded SPSC cursor contention study
+- `make profile-io`, run Linux syscall/socket-path profiling where supported
+- `make socket-load`. Linux multi-client TCP connection-scaling load experiment
+- `make asan`, build/run sanitizer preset
+- `make tsan`, build/run ThreadSanitizer concurrency tests
+- `make concurrency-stress`, opt-in repeated concurrency validation loop
+- `make socket-stress`. UDP socket-buffer / burst-loss experiment
+- `make crash-recovery`. SIGKILL crash / torn-tail recovery validation for the event log
+- `make dpdk-check`, run the M48 non-mutating DPDK environment support check
+- `make nic-offload-check`, run the M49 non-mutating NIC offload/timestamping capability check
+- `make demo`, run local replay + TCP gateway demo
+- `make check-fixtures`, regenerate and verify differential fixtures
+- `make check-manifest`, verify fixture provenance manifest
+- `make determinism`, verify fixture determinism across compilers where supported
+- `make divergence-demo`, exercise the shrinker on an injected divergence
+- `make clean`, remove build artifacts
Run `make check` before every PR.
@@ -890,7 +890,7 @@ The project fails if it becomes:
---
-# Jane Street Hong Kong December–February Internship Addendum
+# Jane Street Hong Kong December-February Internship Addendum
This section is additive project context. Do not delete or weaken any earlier Quant Systems Lab instructions. The repo remains a C++20 exchange-systems project. This addendum sharpens the project for Jane Street Software Engineering and Linux Engineering internship applications.
@@ -898,16 +898,16 @@ This section is additive project context. Do not delete or weaken any earlier Qu
Primary target:
-1. **Software Engineer Internship, December–February — Hong Kong**
+1. **Software Engineer Internship, December-February. Hong Kong**
Secondary target:
-2. **Linux Engineer Internship, December–February — Hong Kong**
+2. **Linux Engineer Internship, December-February. Hong Kong**
Lower-priority optional targets:
-3. **Strategy and Product Internship, December–February — Hong Kong**
-4. **IT Operations Engineer Internship, December–February — Hong Kong**
+3. **Strategy and Product Internship, December-February. Hong Kong**
+4. **IT Operations Engineer Internship, December-February. Hong Kong**
Do not optimize the repo for IT Operations. IT Ops is a weaker signal for the user's stated goal. The repo should optimize for elite technical software/systems roles.
@@ -973,7 +973,7 @@ The same repo must support two résumé framings.
Project title:
```text
-Quant Systems Lab — C++20 Exchange Simulator + OCaml Replay Verifier
+Quant Systems Lab. C++20 Exchange Simulator + OCaml Replay Verifier
```
Resume bullets:
@@ -989,7 +989,7 @@ Resume bullets:
Project title:
```text
-Quant Systems Lab — Linux Systems + Exchange Infrastructure Simulator
+Quant Systems Lab. Linux Systems + Exchange Infrastructure Simulator
```
Resume bullets:
@@ -1087,21 +1087,21 @@ Do not build a dashboard before the engine is real. Do not build trading strateg
## Additive M15-M20 technical roadmap replacing old optional application polish
-The prior optional `M15 — Jane Street application polish` milestone is removed. Do not implement recruiter-facing polish as the next milestone. The project should now continue with technical depth:
+The prior optional `M15. Jane Street application polish` milestone is removed. Do not implement recruiter-facing polish as the next milestone. The project should now continue with technical depth:
-1. **M15 — Export normalized command streams + final snapshots**
+1. **M15. Export normalized command streams + final snapshots**
- Export complete command streams, engine events, rejections, symbol registration order, and final per-symbol snapshots.
- This gives the OCaml side enough information to replay independently.
-2. **M16 — Independent OCaml replay engine**
+2. **M16. Independent OCaml replay engine**
- OCaml replays the command stream immutably and computes its own final snapshot.
- It must not merely inspect the C++ event log.
-3. **M17 — Differential replay tests: C++ vs OCaml snapshot equality**
+3. **M17. Differential replay tests: C++ vs OCaml snapshot equality**
- CI compares C++ exported snapshots against OCaml-computed snapshots.
-4. **M18 — Property-based command generator**
+4. **M18. Property-based command generator**
- Generate seeded randomized command streams covering valid, invalid, duplicate, reused, IOC, market, cancel, modify, and multi-symbol cases.
-5. **M19 — Shrinker + minimal failing fixture exporter**
+5. **M19. Shrinker + minimal failing fixture exporter**
- Reduce failing generated streams to small, replayable counterexamples.
-6. **M20 — Final docs: differential testing architecture**
+6. **M20. Final docs: differential testing architecture**
- Document the architecture, fixture schemas, property generator, shrinker, and exact limits.
### Strategic reason
@@ -1154,8 +1154,7 @@ The correct claim after this arc is:
> "correctness-first deterministic exchange-systems lab with measured concurrency, allocator,
> bare-metal partial-PMU Linux perf, and socket-profiling evidence."
-Real hardware PMU evidence now exists on a bare-metal Apple M2 (aarch64) Fedora Asahi host —
-`cycles`/`instructions`/`branches`/`branch-misses` are genuine counters. Do not claim *full* PMU
+Real hardware PMU evidence now exists on a bare-metal Apple M2 (aarch64) Fedora Asahi host, `cycles`/`instructions`/`branches`/`branch-misses` are genuine counters. Do not claim *full* PMU
evidence (the Apple Silicon PMU does not expose `cache-references`/`cache-misses`), and do not call
the current artifacts "constrained Docker validation" either: they are **partial hardware PMU
evidence**. Issue #90's residual is the cache-counter set, which needs a PMU microarchitecture that
@@ -1204,12 +1203,12 @@ M29 currently means:
M29 now means **partial hardware PMU evidence**: the committed artifacts were regenerated on a
bare-metal Apple M2 (aarch64) Fedora Asahi host (`systemd-detect-virt` reports `none`), where
`perf stat` reads genuine `cycles`/`instructions`/`branches`/`branch-misses` counters off the Apple
-Avalanche P-core PMU. They are no longer "constrained Docker validation" — but they are not *full*
+Avalanche P-core PMU. They are no longer "constrained Docker validation", but they are not *full*
PMU evidence either, because the Apple Silicon PMU does not expose `cache-references`/`cache-misses`.
Issue #90 now tracks only that residual: a *full* counter set (including cache events) requires a
PMU microarchitecture that exposes those events to Linux (e.g. an x86_64 Intel/AMD host, or an ARM
-server core such as Graviton/Ampere) — not "more bare metal." Treat this as: problem identified ->
+server core such as Graviton/Ampere), not "more bare metal." Treat this as: problem identified ->
limitation documented -> follow-up issue created -> acceptance criteria defined. This is intentional
engineering transparency, not a repo deficiency.
@@ -1249,7 +1248,7 @@ realistic deterministic synthetic flow model, and portable threaded TCP serving.
docs-only roadmap audit branch updates future systems-engineering scope; it does not rewrite
completed milestone history.
-Original roadmap after M35 shifted +7 and this audit extends it to **M43–M49**: M43 NUMA/CPU
+Original roadmap after M35 shifted +7 and this audit extends it to **M43-M49**: M43 NUMA/CPU
affinity and scheduler-migration study; M44 ingress queue memory-ordering and false-sharing study;
M45 exchange-grade persistence prototype; M46 recovery benchmarking; M47 contiguous order-book
storage and cache-locality study; M48 DPDK research/prototype; M49 NIC offload and low-latency
@@ -1265,12 +1264,12 @@ resume-anchor/PMU sweep (PR #129), a perf call-graph flamegraph + `make flamegra
superseding the auto-closed #130, closing #32), and the FIX-like text protocol adapter (PR #131,
closing #29), with the version bump on the release PR.
-Since `v0.2.1`, a post-v0.2.1 hardening + perf wave (PRs #135–#146) is merged to `main` and
+Since `v0.2.1`, a post-v0.2.1 hardening + perf wave (PRs #135, #146) is merged to `main` and
unreleased, being cut as **`v0.2.2`**. It came out of a 4-round adversarial bug hunt (converged
5→2→1→0 confirmed) plus flamegraph-guided optimization. Security/robustness: out-of-domain enum
-rejection in the replay/protocol decoders (#136); network hardening — EINTR retry, accept fairness,
+rejection in the replay/protocol decoders (#136); network hardening. EINTR retry, accept fairness,
connection cap, UDP send-error tracking, transient-accept survival, and threaded/epoll fd-exhaustion
-handling (#137, #140, #143); CLI arg validation (#141); a **real UBSan abort gate** — the `asan`
+handling (#137, #140, #143); CLI arg validation (#141); a **real UBSan abort gate**, the `asan`
preset now sets `-fno-sanitize-recover=undefined`, since UBSan previously ran in recover mode and
exited 0, so pure-UBSan defects passed CI green (#142); OCaml `diff_report` per-fixture robustness
(#144). Perf (measured back-to-back A/B): `try_emplace` for baseline price levels (~+5%, #138) and
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0f9dcb0..cbf2d23 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,7 +12,7 @@ _Nothing yet._
A security/robustness **hardening** wave plus two measured order-book **performance** wins, driven by
a multi-round adversarial bug hunt (converged 5→2→1→0 confirmed bugs) and flamegraph-guided
optimization. Same honesty bar: a deterministic C++20 exchange simulator and cross-language
-differential-testing harness — **not** a production exchange, no real-market connectivity, no latency
+differential-testing harness, **not** a production exchange, no real-market connectivity, no latency
or profitability claims, not formal verification. Determinism preserved throughout (fixtures
byte-identical across g++/clang++ and vs the committed copies; the OCaml differential passes).
`make check`/`make asan` 270/270.
@@ -50,25 +50,25 @@ byte-identical across g++/clang++ and vs the committed copies; the OCaml differe
`try_emplace` avoids that on the steady-state common path. Measured back-to-back A/B on the
`qsl-bench profile` workload: **~+5%**.
- **Order-index hash load-factor cap (#145).** The `OrderId → Locator` index is the busiest structure
- on the engine hot path (1–4 point lookups per op). Capping its `max_load_factor` at 0.25 shortens
- probe chains. Measured A/B: **~+18.6%**. Determinism is unaffected — the index is never iterated
+ on the engine hot path (1-4 point lookups per op). Capping its `max_load_factor` at 0.25 shortens
+ probe chains. Measured A/B: **~+18.6%**. Determinism is unaffected, the index is never iterated
for output.
- **Flamegraph regenerated (#135, #139, #146)** against the new code, now a dense (~20k-sample),
fully-symbolized frame-pointer profile with zero `[unknown]` frames.
## [0.2.1] - 2026-06-21
-Two backlog items — reprioritized by the maintainer and delivered — plus a resume-anchor and
+Two backlog items, reprioritized by the maintainer and delivered, plus a resume-anchor and
perf-evidence consistency sweep. Same honesty bar as prior releases: a deterministic C++20 exchange
-simulator and cross-language differential-testing harness — **not** a production exchange, no
+simulator and cross-language differential-testing harness, **not** a production exchange, no
real-market connectivity, no latency or profitability claims, and not formal verification.
### Added
- **FIX-like text protocol adapter (#29).** A human-readable `tag=value` (SOH-framed) codec
(`include/qsl/protocol/fix.hpp`, `src/protocol/fix.cpp`) over the **same internal message structs**
- as the binary codec, with genuine FIX framing — BeginString (8) / BodyLength (9) / MsgType (35) /
- … / mod-256 CheckSum (10) — for the client→gateway order path: NewOrderSingle (`35=D`) → `NewOrder`
+ as the binary codec, with genuine FIX framing. BeginString (8) / BodyLength (9) / MsgType (35) /
+ … / mod-256 CheckSum (10), for the client→gateway order path: NewOrderSingle (`35=D`) → `NewOrder`
and OrderCancelRequest (`35=F`) → `CancelOrder`. Decoding is total, deterministic, and `noexcept`
(fixed field table, `std::from_chars`, `std::string_view`; no heap on the decode path) and reports
every malformed input through a `FixError` taxonomy mirroring the binary codec's `DecodeError`.
@@ -78,26 +78,26 @@ real-market connectivity, no latency or profitability claims, and not formal ver
(documented simplifications, never floating-point price).
- **`make flamegraph` (#32).** Renders a Linux `perf` call-graph flamegraph
(`results/flamegraph.svg` + a provenance/classification `results/flamegraph.txt`) from the
- benchmark harness via `scripts/flamegraph.py` — a dependency-free (stdlib-only) stackcollapse + SVG
+ benchmark harness via `scripts/flamegraph.py`, a dependency-free (stdlib-only) stackcollapse + SVG
renderer (deterministic; unit-tested in `tests/shell/test_flamegraph.sh`), so the artifact is
reproducible from the repo without vendoring the Perl FlameGraph toolkit. The committed artifact is
- a software cpu-clock sampling **hot-symbol profile** from the bare-metal Fedora Asahi host — not a
+ a software cpu-clock sampling **hot-symbol profile** from the bare-metal Fedora Asahi host, not a
latency/throughput claim; full hardware cache-PMU evidence stays in issue #90.
### Changed
- Synced the `/resume` anchors and perf-evidence wording to the released `v0.2.0` state and narrowed
- an overstated Apple **Blizzard** (E-core) PMU claim — those rows read `` because the
+ an overstated Apple **Blizzard** (E-core) PMU claim, those rows read `` because the
single-threaded benchmark stays on the Avalanche P-cores (Codex follow-up to PRs #127/#128):
`PROGRESS.md`, `AGENTS.md`/`CLAUDE.md` agreement, and `docs/perf_analysis.md`.
## [0.2.0] - 2026-06-21
-Quant Systems Lab v0.2.0 — the Phase III/IV systems arc (M24–M49: a bounded SPSC queue and threaded
+Quant Systems Lab v0.2.0, the Phase III/IV systems arc (M24-M49: a bounded SPSC queue and threaded
pipeline, ThreadSanitizer coverage, allocator and order-book storage experiments, Linux
perf/socket/NUMA studies, an epoll gateway prototype, event-log persistence/recovery, and DPDK/NIC
research) plus a **bare-metal Linux evidence refresh**. Same honesty bar as v0.1.0: a deterministic
-C++20 exchange simulator and cross-language differential-testing harness — **not** a production
+C++20 exchange simulator and cross-language differential-testing harness, **not** a production
exchange, no real-market connectivity, no latency or profitability claims, and not formal
verification.
@@ -105,8 +105,8 @@ verification.
- v0.2.0 evidence refresh: regenerated every `results/*.txt` on a **bare-metal** Apple M2 (aarch64)
Fedora Asahi Linux host (`systemd-detect-virt: none`). `scripts/perf_stat.sh` now classifies three
- ways — full `hardware PMU evidence`, `partial hardware PMU evidence` (real counters, incomplete
- set), or `constrained-environment validation (no hardware PMU access)` — so the perf artifact is
+ ways, full `hardware PMU evidence`, `partial hardware PMU evidence` (real counters, incomplete
+ set), or `constrained-environment validation (no hardware PMU access)`, so the perf artifact is
honestly labeled **partial hardware PMU evidence**: real `cycles`/`instructions`/`branches`/
`branch-misses` off the Apple Avalanche/Blizzard PMUs, with `cache-references`/`cache-misses`
unsupported by the Apple Silicon PMU. Issue #90 is narrowed to the cache-counter set (needs a PMU
@@ -136,13 +136,13 @@ verification.
- Added portable threaded `TcpServer` serving: accepted connections run in per-connection workers
while gateway mutation remains serialized. `TcpServerOptions` now exposes `listen_backlog` and a
`max_accepts` test/embedding hook.
-- M35: `scripts/socket_load.sh` (`make socket-load`, Linux-only) — multi-client TCP
+- M35: `scripts/socket_load.sh` (`make socket-load`, Linux-only), multi-client TCP
connection-scaling load coverage comparing the portable threaded TCP gateway and the epoll gateway under
bounded loopback pressure, with constrained metadata in `results/socket_load_summary.txt`.
-- M43: `scripts/numa_affinity_study.sh` (`make numa-study`, Linux-only) — CPU-affinity /
+- M43: `scripts/numa_affinity_study.sh` (`make numa-study`, Linux-only). CPU-affinity /
scheduler-migration / NUMA-locality study tooling with explicit `full-linux-numa`,
`linux-constrained`, or `unsupported-host` evidence classification.
-- M44: `scripts/run_false_sharing_study.sh` (`make false-sharing-study`) — benchmark-only
+- M44: `scripts/run_false_sharing_study.sh` (`make false-sharing-study`), benchmark-only
packed-vs-padded SPSC queue-cursor contention study with metadata in
`results/false_sharing_study.txt`.
- M47: `OrderBook::Storage::Contiguous`, an opt-in fixed-band direct price-indexed storage mode
@@ -173,10 +173,10 @@ verification.
- M30: optional UDP receive-buffer (`SO_RCVBUF`) sizing on the market-data client, with the
granted size read back via `getsockopt`. `qsl-mdfeed subscribe` gains a `[rcvbuf_bytes]`
argument and `qsl-mdfeed publish` an `[orders]` burst-size argument.
-- M30: `scripts/profile_gateway_io.sh` (`make profile-io`, Linux-only) — profiles the gateway
+- M30: `scripts/profile_gateway_io.sh` (`make profile-io`, Linux-only), profiles the gateway
syscall / kernel-socket path with `strace -f -c` plus procfs rusage (`/proc//{stat,status}`),
distinguishing user-space matching cost from kernel/socket overhead.
-- M30: `scripts/socket_stress.sh` (`make socket-stress`, portable) — UDP burst/gap and
+- M30: `scripts/socket_stress.sh` (`make socket-stress`, portable). UDP burst/gap and
receive-socket-buffer experiment over loopback, run over multiple trials.
### Changed
@@ -207,7 +207,7 @@ verification.
`docs/linux_performance.md`, ADR 0007, `CLAUDE.md`, `results/README.md`, `docs/recruiting_notes.md`,
and the README Limitations section; corrected stale Docker/containerized framing in
`docs/socket_profiling.md` and `docs/pool_backed_storage.md` (artifacts are now bare-metal Apple
- M2 runs); rewrote `docs/release_readiness.md` for the M0–M49 + v0.2.0 state (241 tests, six CI
+ M2 runs); rewrote `docs/release_readiness.md` for the M0-M49 + v0.2.0 state (241 tests, six CI
jobs); added storage/epoll/durability components to `docs/architecture.md`; and clarified that the
independent OCaml replay engine (M16) re-implements matching while the M14 log verifier does not.
- Synchronized project-memory files before repository-health planning (PR #101, PR #102): M35 is
@@ -215,10 +215,10 @@ verification.
completed (see the inserted refactor phase below).
- Inserted a repository-health refactor phase after M35, derived from a CodeScene Code Health
analysis (5 production + 6 test files below 9.0) plus one manually-identified shell-maintainability
- milestone: seven refactor milestones M36–M42 (epoll decomposition; threaded-pipeline stage helpers;
+ milestone: seven refactor milestones M36-M42 (epoll decomposition; threaded-pipeline stage helpers;
shrinker passes; order-book matching parameters; engine test-suite consolidation; session frame
dispatch; shared shell-script helpers). The original networking/persistence roadmap was shifted
- after those refactors; the later systems-roadmap audit extends future scope to M43–M49.
+ after those refactors; the later systems-roadmap audit extends future scope to M43-M49.
- Updated the future systems-engineering roadmap after PR #112: M43 now covers NUMA plus CPU
affinity, scheduler migration, and locality caveats; M44 is expanded in place for ingress
memory-ordering and false-sharing evidence; M47 is inserted for contiguous order-book
@@ -236,10 +236,10 @@ verification.
- M47: expanded `docs/pool_backed_storage.md` and `results/README.md` to describe the contiguous
direct-price-indexed storage mode, its fixed price-domain assumption, affected allocations, and
interpretation limits.
-- M31: added an external-review package — `docs/review_request.md` (an adversarial review
+- M31: added an external-review package, `docs/review_request.md` (an adversarial review
checklist over SPSC memory ordering, backpressure, threaded ownership, event-log integrity under
concurrency, and benchmark/profiling + Linux/socket methodology), `docs/review_feedback.md` (an
- honest, auditable feedback record stating no external review has occurred yet — no fabricated
+ honest, auditable feedback record stating no external review has occurred yet, no fabricated
endorsements), and a `.github/ISSUE_TEMPLATE/review_request.md`. README now states claims are
self-certified and links the request. Distinguishes self-certified vs externally-reviewed claims.
- M30: added `docs/socket_profiling.md` (syscall/rusage + UDP-loss methodology and how to read
@@ -260,14 +260,14 @@ verification.
## [0.1.0] - 2026-05-31
-Quant Systems Lab v0.1.0 — a deterministic C++20 exchange simulator and cross-language
+Quant Systems Lab v0.1.0, a deterministic C++20 exchange simulator and cross-language
differential-testing harness, built as a systems-engineering portfolio project. It is **not** a
production exchange, is not connected to real markets, and makes no latency or profitability
claims; the cross-language differential layer is property-based testing, **not** formal
verification. Benchmarks are synthetic microbenchmarks recorded in `results/` and are
hardware/compiler/build-dependent.
-### Post-M22 backlog hardening (GitHub issues #34–#51)
+### Post-M22 backlog hardening (GitHub issues #34, #51)
Differential/property-testing follow-ups, each merged as an individual Codex-reviewed PR:
@@ -285,7 +285,7 @@ Differential/property-testing follow-ups, each merged as an individual Codex-rev
- Differential regression archive (#50).
- Differential-harness performance benchmarks, `results/differential.txt` (#51).
-### Phase II — cross-language differential testing (M15–M20)
+### Phase II, cross-language differential testing (M15-M20)
- Normalized command-stream + final-snapshot fixture export (M15).
- Independent OCaml replay engine that recomputes the final snapshot from the command stream
@@ -298,16 +298,16 @@ Differential/property-testing follow-ups, each merged as an individual Codex-rev
minimal-fixture exporter (M19).
- Differential-testing and property-testing architecture documentation (M20).
-### Core simulator and tooling (M3–M14)
+### Core simulator and tooling (M3-M14)
- OCaml replay verifier checking exported event logs against replay invariants (M14).
- Final architecture/demo/recruiting documentation and `make demo` (M13).
- Hardening: ASan/UBSan, randomized invariant tests, and structure-aware protocol fuzzing in CI
(M12).
- Reproducible benchmark harness writing `results/latest.txt` with full metadata (M11).
-- Append-only event log and deterministic replay/recovery (M7–M8).
+- Append-only event log and deterministic replay/recovery (M7-M8).
- Price-time-priority matching engine, deterministic risk checks, and a market-data publisher
- (M3–M6).
+ (M3-M6).
-(Networking: a loopback TCP order gateway (M9) and UDP market-data feed (M10) — local,
+(Networking: a loopback TCP order gateway (M9) and UDP market-data feed (M10), local,
unauthenticated; see `SECURITY.md`.)
diff --git a/CLAUDE.md b/CLAUDE.md
index 60e126e..cd1a7ae 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,4 +1,4 @@
-# CLAUDE.md — Quant Systems Lab
+# CLAUDE.md. Quant Systems Lab
> **Project memory for Claude Code. Auto-loaded every session. Keep it current.**
> Starting or resuming work? Read this file → then `PROGRESS.md` → then `MILESTONES.md` → then `HANDOFF.md` → then run `/resume`.
@@ -29,7 +29,7 @@ The repo should look like disciplined human engineering, not a tutorial dump, no
---
-## Golden rules — non-negotiable
+## Golden rules, non-negotiable
1. **Never commit or push to `main`.** All work happens on a feature branch.
2. **One milestone = one feature branch = one squash-merge PR.**
@@ -68,7 +68,7 @@ The repo should look like disciplined human engineering, not a tutorial dump, no
---
-## Operating model — AI-first, human-in-the-loop
+## Operating model. AI-first, human-in-the-loop
Claude Code does:
@@ -91,11 +91,11 @@ The human does:
Resumability is anchored in:
-1. `CLAUDE.md` — canonical Claude Code project memory.
-2. `AGENTS.md` — Codex-facing mirror/adapter. Keep synchronized with `CLAUDE.md`.
-3. `MILESTONES.md` — ordered roadmap and milestone definitions.
-4. `PROGRESS.md` — live state and resume anchor.
-5. `HANDOFF.md` — operator manual tying the files together.
+1. `CLAUDE.md`, canonical Claude Code project memory.
+2. `AGENTS.md`. Codex-facing mirror/adapter. Keep synchronized with `CLAUDE.md`.
+3. `MILESTONES.md`, ordered roadmap and milestone definitions.
+4. `PROGRESS.md`, live state and resume anchor.
+5. `HANDOFF.md`, operator manual tying the files together.
6. Git history and PR state.
`AGENTS.md` and `CLAUDE.md` must agree on workflow rules, command lists, roadmap state,
@@ -163,7 +163,7 @@ Known constraints:
from a bare-metal Apple M2 (aarch64) Fedora Asahi host: real
`cycles`/`instructions`/`branches`/`branch-misses`, but `cache-references`/`cache-misses` are
unsupported by the Apple Silicon PMU. Issue #90's residual is the cache-counter set specifically,
- which needs a PMU microarchitecture that exposes it (x86_64, or an ARM server core) — bare metal
+ which needs a PMU microarchitecture that exposes it (x86_64, or an ARM server core), bare metal
alone is not enough. Do not relabel it "full PMU evidence" or "constrained Docker validation". The
`perf record` hot-symbol report (`results/perf_report_linux.txt`) is a **software cpu-clock
sampling** profile, not PMU evidence.
@@ -177,14 +177,14 @@ optional add-on. For every substantive task, actively reach for the relevant ser
falling back to ad-hoc shell:
- `codescene` Code Health review on every file you touch, and `analyze_change_set` before opening
- or updating any PR — a gate, not a nicety.
+ or updating any PR, a gate, not a nicety.
- `git` / `github` for diffs, blame, history, and PR / issue / branch / commit / milestone review.
- `qsl-results` (SQLite) to record and query benchmark history (storage / NUMA / false-sharing /
perf / recovery) instead of re-parsing committed text artifacts.
- `codex` for an independent second-opinion review of any non-trivial change before a milestone is
finished.
- `context7` for current docs of any library / framework / CLI / API before relying on its
- behavior — do not answer from memory.
+ behavior, do not answer from memory.
- `sequential_thinking` to structure multi-step refactor or risk planning.
- `filesystem` for repository read / edit / search and the project docs (ADRs, MILESTONES.md,
PROGRESS.md, HANDOFF.md, review docs).
@@ -196,28 +196,28 @@ rules). Never invent an MCP server or tool that is not configured.
Local Claude Code client MCP servers currently configured:
-- `codescene` — use for repository-health analysis, file Code Health review, branch/change-set
+- `codescene`, use for repository-health analysis, file Code Health review, branch/change-set
review, and pre-commit Code Health safeguards.
-- `playwright` — use for browser automation and rendered web-flow verification when a task needs a
+- `playwright`, use for browser automation and rendered web-flow verification when a task needs a
real browser-level check.
-- `filesystem` — available for MCP filesystem access scoped to this repository; normal shell/git
+- `filesystem`, available for MCP filesystem access scoped to this repository; normal shell/git
file operations remain acceptable for ordinary repo edits.
-- `sequential_thinking` — use for complex multi-step planning, especially when refactor sequencing
+- `sequential_thinking`, use for complex multi-step planning, especially when refactor sequencing
or risk tradeoffs need explicit structure.
-- `memory` — use only for durable project-memory facts that should survive sessions; do not store
+- `memory`, use only for durable project-memory facts that should survive sessions; do not store
secrets or speculative notes.
-- `docker` — use for Docker/container lifecycle checks and Linux-container verification when a
+- `docker`, use for Docker/container lifecycle checks and Linux-container verification when a
milestone calls for containerized validation.
-- `context7` — use for current library/tool documentation when exact external API behavior matters.
-- `node_repl` — use when JavaScript/browser-plugin workflows require the persistent Node-backed
+- `context7`, use for current library/tool documentation when exact external API behavior matters.
+- `node_repl`, use when JavaScript/browser-plugin workflows require the persistent Node-backed
kernel.
-- `codex` — Codex CLI exposed as an MCP server (`codex mcp-server`); use for an independent
+- `codex`. Codex CLI exposed as an MCP server (`codex mcp-server`); use for an independent
second-opinion review of non-trivial changes before finishing a milestone.
-- `git` — structured git access (diffs, blame, log, branch) over this repo for artifact
+- `git`, structured git access (diffs, blame, log, branch) over this repo for artifact
provenance, milestone audits, and release preparation.
-- `github` — official GitHub MCP server for PR review, issue/milestone tracking, branch and commit
+- `github`, official GitHub MCP server for PR review, issue/milestone tracking, branch and commit
inspection, and release notes (needs a one-time OAuth via `/mcp`; the `gh` CLI is the fallback).
-- `qsl-results` — SQLite store of structured benchmark history (storage/NUMA/false-sharing/perf/
+- `qsl-results`. SQLite store of structured benchmark history (storage/NUMA/false-sharing/perf/
recovery) so results are queryable instead of re-parsed from committed text artifacts.
Postgres and Perplexity MCP servers are intentionally not configured. Do not assume database or
@@ -372,37 +372,37 @@ Avoid unnecessary dependencies. A small systems repo with clear primitives beats
Keep this synchronized with the Makefile.
-- `make configure` — configure dev build
-- `make build` — build dev preset
-- `make test` — run CTest
-- `make check` — format check + build + tests
-- `make fmt` — apply clang-format
-- `make tidy` — clang-tidy target if available
-- `make bench` — run benchmark suite
-- `make bench-diff` — run differential harness benchmarks
-- `make bench-allocator` — run M28 allocator experiment
-- `make bench-storage` — run M32 storage experiment
-- `make bench-recovery` — run M46 recovery benchmarking (full-replay restart vs book rebuild)
-- `make perf-stat` — run Linux `perf stat` workflow where supported
-- `make perf-record` — run Linux `perf record/report` workflow where supported
-- `make flamegraph` — render a Linux `perf` call-graph flamegraph (SVG) where supported
-- `make numa-study` — run Linux CPU-affinity / scheduler-migration / NUMA-locality study where supported
-- `make false-sharing-study` — run benchmark-only packed-vs-padded SPSC cursor contention study
-- `make profile-io` — run Linux syscall/socket-path profiling where supported
-- `make socket-load` — Linux multi-client TCP connection-scaling load experiment
-- `make asan` — build/run sanitizer preset
-- `make tsan` — build/run ThreadSanitizer concurrency tests
-- `make concurrency-stress` — opt-in repeated concurrency validation loop
-- `make socket-stress` — UDP socket-buffer / burst-loss experiment
-- `make crash-recovery` — SIGKILL crash / torn-tail recovery validation for the event log
-- `make dpdk-check` — run the M48 non-mutating DPDK environment support check
-- `make nic-offload-check` — run the M49 non-mutating NIC offload/timestamping capability check
-- `make demo` — run local replay + TCP gateway demo
-- `make check-fixtures` — regenerate and verify differential fixtures
-- `make check-manifest` — verify fixture provenance manifest
-- `make determinism` — verify fixture determinism across compilers where supported
-- `make divergence-demo` — exercise the shrinker on an injected divergence
-- `make clean` — remove build artifacts
+- `make configure`, configure dev build
+- `make build`, build dev preset
+- `make test`, run CTest
+- `make check`, format check + build + tests
+- `make fmt`, apply clang-format
+- `make tidy`, clang-tidy target if available
+- `make bench`, run benchmark suite
+- `make bench-diff`, run differential harness benchmarks
+- `make bench-allocator`, run M28 allocator experiment
+- `make bench-storage`, run M32 storage experiment
+- `make bench-recovery`, run M46 recovery benchmarking (full-replay restart vs book rebuild)
+- `make perf-stat`, run Linux `perf stat` workflow where supported
+- `make perf-record`, run Linux `perf record/report` workflow where supported
+- `make flamegraph`, render a Linux `perf` call-graph flamegraph (SVG) where supported
+- `make numa-study`, run Linux CPU-affinity / scheduler-migration / NUMA-locality study where supported
+- `make false-sharing-study`, run benchmark-only packed-vs-padded SPSC cursor contention study
+- `make profile-io`, run Linux syscall/socket-path profiling where supported
+- `make socket-load`. Linux multi-client TCP connection-scaling load experiment
+- `make asan`, build/run sanitizer preset
+- `make tsan`, build/run ThreadSanitizer concurrency tests
+- `make concurrency-stress`, opt-in repeated concurrency validation loop
+- `make socket-stress`. UDP socket-buffer / burst-loss experiment
+- `make crash-recovery`. SIGKILL crash / torn-tail recovery validation for the event log
+- `make dpdk-check`, run the M48 non-mutating DPDK environment support check
+- `make nic-offload-check`, run the M49 non-mutating NIC offload/timestamping capability check
+- `make demo`, run local replay + TCP gateway demo
+- `make check-fixtures`, regenerate and verify differential fixtures
+- `make check-manifest`, verify fixture provenance manifest
+- `make determinism`, verify fixture determinism across compilers where supported
+- `make divergence-demo`, exercise the shrinker on an injected divergence
+- `make clean`, remove build artifacts
Run `make check` before every PR.
@@ -890,7 +890,7 @@ The project fails if it becomes:
---
-# Jane Street Hong Kong December–February Internship Addendum
+# Jane Street Hong Kong December-February Internship Addendum
This section is additive project context. Do not delete or weaken any earlier Quant Systems Lab instructions. The repo remains a C++20 exchange-systems project. This addendum sharpens the project for Jane Street Software Engineering and Linux Engineering internship applications.
@@ -898,16 +898,16 @@ This section is additive project context. Do not delete or weaken any earlier Qu
Primary target:
-1. **Software Engineer Internship, December–February — Hong Kong**
+1. **Software Engineer Internship, December-February. Hong Kong**
Secondary target:
-2. **Linux Engineer Internship, December–February — Hong Kong**
+2. **Linux Engineer Internship, December-February. Hong Kong**
Lower-priority optional targets:
-3. **Strategy and Product Internship, December–February — Hong Kong**
-4. **IT Operations Engineer Internship, December–February — Hong Kong**
+3. **Strategy and Product Internship, December-February. Hong Kong**
+4. **IT Operations Engineer Internship, December-February. Hong Kong**
Do not optimize the repo for IT Operations. IT Ops is a weaker signal for the user's stated goal. The repo should optimize for elite technical software/systems roles.
@@ -973,7 +973,7 @@ The same repo must support two résumé framings.
Project title:
```text
-Quant Systems Lab — C++20 Exchange Simulator + OCaml Replay Verifier
+Quant Systems Lab. C++20 Exchange Simulator + OCaml Replay Verifier
```
Resume bullets:
@@ -989,7 +989,7 @@ Resume bullets:
Project title:
```text
-Quant Systems Lab — Linux Systems + Exchange Infrastructure Simulator
+Quant Systems Lab. Linux Systems + Exchange Infrastructure Simulator
```
Resume bullets:
@@ -1084,23 +1084,23 @@ When a tradeoff exists, optimize in this order:
Do not build a dashboard before the engine is real. Do not build trading strategies. Do not connect to market data APIs. Do not make profitability claims. The market does not care about decorative software.
-## Additive M15–M20 technical roadmap replacing old optional application polish
+## Additive M15-M20 technical roadmap replacing old optional application polish
-The prior optional `M15 — Jane Street application polish` milestone is removed. Do not implement recruiter-facing polish as the next milestone. The project should now continue with technical depth:
+The prior optional `M15. Jane Street application polish` milestone is removed. Do not implement recruiter-facing polish as the next milestone. The project should now continue with technical depth:
-1. **M15 — Export normalized command streams + final snapshots**
+1. **M15. Export normalized command streams + final snapshots**
- Export complete command streams, engine events, rejections, symbol registration order, and final per-symbol snapshots.
- This gives the OCaml side enough information to replay independently.
-2. **M16 — Independent OCaml replay engine**
+2. **M16. Independent OCaml replay engine**
- OCaml replays the command stream immutably and computes its own final snapshot.
- It must not merely inspect the C++ event log.
-3. **M17 — Differential replay tests: C++ vs OCaml snapshot equality**
+3. **M17. Differential replay tests: C++ vs OCaml snapshot equality**
- CI compares C++ exported snapshots against OCaml-computed snapshots.
-4. **M18 — Property-based command generator**
+4. **M18. Property-based command generator**
- Generate seeded randomized command streams covering valid, invalid, duplicate, reused, IOC, market, cancel, modify, and multi-symbol cases.
-5. **M19 — Shrinker + minimal failing fixture exporter**
+5. **M19. Shrinker + minimal failing fixture exporter**
- Reduce failing generated streams to small, replayable counterexamples.
-6. **M20 — Final docs: differential testing architecture**
+6. **M20. Final docs: differential testing architecture**
- Document the architecture, fixture schemas, property generator, shrinker, and exact limits.
### Strategic reason
@@ -1145,15 +1145,14 @@ Phase IV focuses on:
7. advanced concurrency validation.
Do not add dashboards, strategies, market-data APIs, FIX adapters, Docker packaging, or
-aesthetic product work before M24–M49 unless the human explicitly changes priorities.
+aesthetic product work before M24-M49 unless the human explicitly changes priorities.
The correct claim after this arc is:
> "correctness-first deterministic exchange-systems lab with measured concurrency, allocator,
> bare-metal partial-PMU Linux perf, and socket-profiling evidence."
-Real hardware PMU evidence now exists on a bare-metal Apple M2 (aarch64) Fedora Asahi host —
-`cycles`/`instructions`/`branches`/`branch-misses` are genuine counters. Do not claim *full* PMU
+Real hardware PMU evidence now exists on a bare-metal Apple M2 (aarch64) Fedora Asahi host, `cycles`/`instructions`/`branches`/`branch-misses` are genuine counters. Do not claim *full* PMU
evidence (the Apple Silicon PMU does not expose `cache-references`/`cache-misses`), and do not call
the current artifacts "constrained Docker validation" either: they are **partial hardware PMU
evidence**. Issue #90's residual is the cache-counter set, which needs a PMU microarchitecture that
@@ -1193,7 +1192,7 @@ realistic deterministic synthetic flow model, and portable threaded TCP serving.
docs-only roadmap audit branch updates future systems-engineering scope; it does not rewrite
completed milestone history.
-Original roadmap after M35 shifted +7 and this audit extends it to **M43–M49**: M43 NUMA/CPU
+Original roadmap after M35 shifted +7 and this audit extends it to **M43-M49**: M43 NUMA/CPU
affinity and scheduler-migration study; M44 ingress queue memory-ordering and false-sharing study;
M45 exchange-grade persistence prototype; M46 recovery benchmarking; M47 contiguous order-book
storage and cache-locality study; M48 DPDK research/prototype; M49 NIC offload and low-latency
@@ -1209,12 +1208,12 @@ resume-anchor/PMU sweep (PR #129), a perf call-graph flamegraph + `make flamegra
superseding the auto-closed #130, closing #32), and the FIX-like text protocol adapter (PR #131,
closing #29), with the version bump on the release PR.
-Since `v0.2.1`, a post-v0.2.1 hardening + perf wave (PRs #135–#146) is merged to `main` and
+Since `v0.2.1`, a post-v0.2.1 hardening + perf wave (PRs #135, #146) is merged to `main` and
unreleased, being cut as **`v0.2.2`**. It came out of a 4-round adversarial bug hunt (converged
5→2→1→0 confirmed) plus flamegraph-guided optimization. Security/robustness: out-of-domain enum
-rejection in the replay/protocol decoders (#136); network hardening — EINTR retry, accept fairness,
+rejection in the replay/protocol decoders (#136); network hardening. EINTR retry, accept fairness,
connection cap, UDP send-error tracking, transient-accept survival, and threaded/epoll fd-exhaustion
-handling (#137, #140, #143); CLI arg validation (#141); a **real UBSan abort gate** — the `asan`
+handling (#137, #140, #143); CLI arg validation (#141); a **real UBSan abort gate**, the `asan`
preset now sets `-fno-sanitize-recover=undefined`, since UBSan previously ran in recover mode and
exited 0, so pure-UBSan defects passed CI green (#142); OCaml `diff_report` per-fixture robustness
(#144). Perf (measured back-to-back A/B): `try_emplace` for baseline price levels (~+5%, #138) and
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1dd32e0..2aa6f35 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -42,11 +42,19 @@ target_link_libraries(qsl-export-fixture PRIVATE qsl_core qsl_warnings)
add_executable(qsl-export-stream apps/qsl-export-stream/main.cpp)
target_link_libraries(qsl-export-stream PRIVATE qsl_core qsl_warnings)
-# Performance-evidence harness (PERFORMANCE.md). Separate binary: it overrides operator new to count
-# allocations, which must not perturb the qsl-bench numbers. Built Release for honest measurement.
+# Performance-evidence harness (PERFORMANCE.md). Separate binary from qsl-bench so it cannot perturb
+# the qsl-bench numbers. The default target measures pure performance (throughput, latency, cycles).
add_executable(qsl-perfeval apps/qsl-perfeval/main.cpp)
target_link_libraries(qsl-perfeval PRIVATE qsl_core qsl_warnings)
+# Same source, allocation-counting build: it overrides every global operator new to count ALL
+# allocations (plain and over-aligned). Kept a separate target because those overrides add a little
+# work per aligned allocation and would perturb cycle/throughput numbers, so performance is measured
+# with qsl-perfeval and allocations/order with qsl-perfeval-allocs. See PERFORMANCE.md methodology.
+add_executable(qsl-perfeval-allocs apps/qsl-perfeval/main.cpp)
+target_link_libraries(qsl-perfeval-allocs PRIVATE qsl_core qsl_warnings)
+target_compile_definitions(qsl-perfeval-allocs PRIVATE QSL_PERFEVAL_COUNT_ALLOCS)
+
if(QSL_BUILD_BENCHMARKS)
find_package(Threads REQUIRED)
add_executable(qsl-bench apps/qsl-bench/main.cpp benchmarks/bench_order_pool.cpp
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1fba07a..347ade4 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -6,7 +6,7 @@ reviewable.
## Workflow
-- **Never work on `main`.** All work happens on a scoped branch — milestone branches use the
+- **Never work on `main`.** All work happens on a scoped branch, milestone branches use the
prefix that describes the work (`feat/mNN-slug`, `test/mNN-slug`, `docs/mNN-slug`,
`perf/mNN-slug`, or `refactor/mNN-slug`), while backlog issue branches use names such as
`feat/issue-NN-slug` or `fix/issue-NN-slug`. Use one scoped change per branch, merged via a
@@ -44,4 +44,4 @@ reproduced with the relevant opt-in targets, such as `make profile-io`, `make so
## Scope
Keep changes within the project's scope (a deterministic exchange-systems lab). It is not a
-production exchange, a trading strategy, or connected to real markets — see `README.md`.
+production exchange, a trading strategy, or connected to real markets, see `README.md`.
diff --git a/HANDOFF.md b/HANDOFF.md
index 0594dc6..d273564 100644
--- a/HANDOFF.md
+++ b/HANDOFF.md
@@ -1,14 +1,14 @@
-# HANDOFF.md — How to build Quant Systems Lab with Claude Code
+# HANDOFF.md. How to build Quant Systems Lab with Claude Code
This is the operator manual. It tells Claude Code and the human how to build the repo in a resumable, sequential, AI-first workflow.
Project memory files:
-1. `CLAUDE.md` — canonical Claude Code project memory.
-2. `AGENTS.md` — Codex-facing mirror/adapter. Keep synchronized with `CLAUDE.md`.
-3. `MILESTONES.md` — ordered roadmap and milestone definitions.
-4. `PROGRESS.md` — live state and resume anchor.
-5. `HANDOFF.md` — operator manual tying the files together.
+1. `CLAUDE.md`, canonical Claude Code project memory.
+2. `AGENTS.md`. Codex-facing mirror/adapter. Keep synchronized with `CLAUDE.md`.
+3. `MILESTONES.md`, ordered roadmap and milestone definitions.
+4. `PROGRESS.md`, live state and resume anchor.
+5. `HANDOFF.md`, operator manual tying the files together.
Keep all five in the repo root. `AGENTS.md` and `CLAUDE.md` must agree on workflow rules,
command lists, roadmap state, non-overclaiming rules, and benchmark rules.
@@ -17,37 +17,36 @@ command lists, roadmap state, non-overclaiming rules, and benchmark rules.
## Current handoff
The repo's current release is `v0.2.1` (tagged on the release-PR merge commit, marked Latest), after
-`v0.2.0` (ded6e80) and `v0.1.0`. M0–M49 are
+`v0.2.0` (ded6e80) and `v0.1.0`. M0-M49 are
merged. PR #101 (40f9249) and PR #102 (7092423)
synchronized project-memory files after M35. PR #103 (0f2ceb7) inserted the repository-health
-refactor phase **M36–M42** and shifted the original networking/persistence roadmap after those
-refactors. PR #113 extended the future roadmap to **M43–M49**. M36–M42 landed as PR #104
+refactor phase **M36-M42** and shifted the original networking/persistence roadmap after those
+refactors. PR #113 extended the future roadmap to **M43-M49**. M36-M42 landed as PR #104
(0d2b97a), PR #105 (a8c0485), PR #106 (9ccf157), PR #107 (880fbc7), PR #108 (b939730), PR #109
(68061e6), and PR #111 (003504f). PR #112 (2369f84) closed issues #95, #28, and #26. PR #113
-(f3cc4dd) updated the future systems-engineering roadmap and agent guidance. M43–M49 landed as PR
+(f3cc4dd) updated the future systems-engineering roadmap and agent guidance. M43-M49 landed as PR
#114 (29ed491), PR #115 (cd05b37), PR #117 (d10bfb0), PR #118 (aeba72c), PR #119 (93d5062),
PR #123 (c643b62), and PR #124 (d8c16b2), with M45B provenance migration in PR #116 (b9ea27a) and
the M47 storage diagnosis follow-up in PR #122 (548cb68). The Linux host artifact refresh landed as
-PR #125 (d9094df), and the **v0.2.0 release** — a bare-metal Linux evidence refresh, the
-partial-PMU reframe, and a full documentation staleness sweep — landed as PR #127 (ded6e80). The
+PR #125 (d9094df), and the **v0.2.0 release**, a bare-metal Linux evidence refresh, the
+partial-PMU reframe, and a full documentation staleness sweep, landed as PR #127 (ded6e80). The
**v0.2.1 release** then adds two reprioritized backlog items and a consistency sweep: a Codex
resume-anchor/PMU sweep (PR #129), a perf call-graph flamegraph + `make flamegraph` (PR #130,
issue #32), the FIX-like text protocol adapter (PR #131, issue #29), and the version-bump release
-PR — merged in that order, with `v0.2.1` tagged on the release merge commit.
+PR, merged in that order, with `v0.2.1` tagged on the release merge commit.
-Since `v0.2.1`, a **post-v0.2.1 hardening + perf wave (#135–#146) is merged to `main` and
+Since `v0.2.1`, a **post-v0.2.1 hardening + perf wave (#135, #146) is merged to `main` and
unreleased**, being cut as **`v0.2.2`**. It came out of a 4-round adversarial bug hunt (converged
5→2→1→0 confirmed bugs) plus flamegraph-guided optimization. Security/robustness: out-of-domain enum
-rejection in the replay/protocol decoders (#136); network hardening — EINTR retry, accept fairness,
+rejection in the replay/protocol decoders (#136); network hardening. EINTR retry, accept fairness,
connection cap, UDP send-error tracking, transient-accept survival, and threaded/epoll fd-exhaustion
-handling (#137, #140, #143); CLI arg validation (#141); a **real UBSan abort gate** —
-`-fno-sanitize-recover=undefined`, since UBSan previously ran in recover mode and exited 0 (#142);
+handling (#137, #140, #143); CLI arg validation (#141); a **real UBSan abort gate**, `-fno-sanitize-recover=undefined`, since UBSan previously ran in recover mode and exited 0 (#142);
OCaml `diff_report` robustness (#144). Perf (measured A/B): `try_emplace` for baseline price levels
(~+5%, #138) and an order-index hash load-factor cap (~+18.6%, #145), with the flamegraph regenerated
(#135/#139/#146). `make check`/`make asan` 270/270 (the latter now under the real UBSan gate). The
next action is to finish this `v0.2.2` doc/artifact overhaul and cut the tag.
-Background — Linux perf evidence (merged, now bare-metal partial PMU):
+Background. Linux perf evidence (merged, now bare-metal partial PMU):
- Linux-only `make perf-stat` / `make perf-record` tooling exists with metadata-rich artifacts,
dirty-tree/source-digest provenance, a three-way PMU classifier, and CI validation.
@@ -57,7 +56,7 @@ Background — Linux perf evidence (merged, now bare-metal partial PMU):
evidence**, not "constrained Docker validation."
- `cache-references`/`cache-misses` are `` by the Apple Silicon PMU, so it is not
*full* evidence. Issue #90 now tracks that cache-counter set specifically, which needs a PMU
- microarchitecture that exposes it (x86_64, or an ARM server core) — bare metal alone is not enough.
+ microarchitecture that exposes it (x86_64, or an ARM server core), bare metal alone is not enough.
To resume:
@@ -89,29 +88,29 @@ Current state:
- latest synced main baseline: `ded6e80` (PR #127, v0.2.0); the `v0.2.1` baseline is the release-PR
merge commit, after PRs #129/#130/#131
- current active branch, if active: `docs/post-v0.2.1-overhaul` (v0.2.2 prep + doc/artifact sweep)
-- current active status: `v0.2.1` is the latest tag; a post-v0.2.1 hardening + perf wave (#135–#146)
+- current active status: `v0.2.1` is the latest tag; a post-v0.2.1 hardening + perf wave (#135, #146)
is merged to `main` and unreleased, being cut as `v0.2.2` (decoder enum rejection, network/CLI
hardening, a real UBSan abort gate, OCaml diff_report robustness, and two measured order-book perf
- wins — `try_emplace` ~+5% and an index load-factor cap ~+18.6%). `make check` 270/270 and
+ wins, `try_emplace` ~+5% and an index load-factor cap ~+18.6%). `make check` 270/270 and
`make asan` 270/270 (the latter now under the real UBSan gate) on the bare-metal Apple M2 Fedora
Asahi host; every touched file passes the CI CodeScene Code Health gate
- release tag: `v0.2.1` (Latest, tagged on the release-PR merge commit), after `v0.2.0` and `v0.1.0`;
`v0.2.2` prepared on this branch, not yet tagged
-- open follow-up issue: #90 — narrowed to the full cache-counter PMU set; the bare-metal Apple host
+- open follow-up issue: #90, narrowed to the full cache-counter PMU set; the bare-metal Apple host
provides real cycles/instructions/branches/branch-misses but no cache-reference/cache-miss support
- issues #95, #28, and #26 were closed by PR #112; issues #32 and #29 were closed by PR #134 and
PR #131 (now part of `v0.2.1`)
- open review request issue: #94
-- legacy backlog: clear — #32 (flamegraph) and #29 (FIX adapter) shipped in `v0.2.1` (PR #134,
+- legacy backlog: clear, #32 (flamegraph) and #29 (FIX adapter) shipped in `v0.2.1` (PR #134,
PR #131)
### Next milestone
-There is no active milestone. M0–M49 are merged, as are the v0.2.0/v0.2.1 releases and the
-post-v0.2.1 hardening + perf wave (#135–#146, being released as `v0.2.2`). The immediate next action
+There is no active milestone. M0-M49 are merged, as are the v0.2.0/v0.2.1 releases and the
+post-v0.2.1 hardening + perf wave (#135, #146, being released as `v0.2.2`). The immediate next action
is to finish the `v0.2.2` doc/artifact overhaul (this branch) and cut the tag. After that the
highest-value remaining work is non-code and externally gated: issue #94 (independent external
-review — needs a human reviewer) and issue #90 (full cache-counter PMU evidence — needs a PMU
+review, needs a human reviewer) and issue #90 (full cache-counter PMU evidence, needs a PMU
microarchitecture that exposes cache events). Do not invent a new milestone without an explicit
human request.
@@ -125,9 +124,9 @@ evidence, persistence/recovery benchmarking, and late-stage low-latency networki
Current priority order (post-v0.2.1):
-1. Issue #94 — independent external technical review remains the single highest credibility gap
+1. Issue #94, independent external technical review remains the single highest credibility gap
(human-gated; cannot be self-certified).
-2. Issue #90 — full cache-counter PMU evidence. The bare-metal Apple host gives real
+2. Issue #90, full cache-counter PMU evidence. The bare-metal Apple host gives real
cycles/instructions/branches/branch-misses but no cache-reference/cache-miss counters, so this
needs a PMU microarchitecture that exposes cache events (x86_64, or an ARM server core).
3. No low-signal backlog remains: #32 (flamegraph) and #29 (FIX adapter) shipped in `v0.2.1`.
@@ -627,11 +626,11 @@ M0 should create `.github/pull_request_template.md`:
```markdown
## Milestone
-M__ —
+M__,
## Summary
-<2–5 sentences>
+<2-5 sentences>
## Definition of Done
@@ -778,20 +777,20 @@ rather than relying on memory.
Local MCP/tooling model:
-- `codescene` — use for repository-health analysis, file Code Health review, branch/change-set
+- `codescene`, use for repository-health analysis, file Code Health review, branch/change-set
review, and pre-commit Code Health safeguards.
-- `playwright` — use for browser automation and rendered web-flow verification when a task needs a
+- `playwright`, use for browser automation and rendered web-flow verification when a task needs a
real browser-level check.
-- `filesystem` — available for MCP filesystem access scoped to this repository; normal shell/git
+- `filesystem`, available for MCP filesystem access scoped to this repository; normal shell/git
file operations remain acceptable for ordinary repo edits.
-- `sequential_thinking` — use for complex multi-step planning, especially when refactor sequencing
+- `sequential_thinking`, use for complex multi-step planning, especially when refactor sequencing
or risk tradeoffs need explicit structure.
-- `memory` — use only for durable project-memory facts that should survive sessions; do not store
+- `memory`, use only for durable project-memory facts that should survive sessions; do not store
secrets or speculative notes.
-- `docker` — use for Docker/container lifecycle checks and Linux-container verification when a
+- `docker`, use for Docker/container lifecycle checks and Linux-container verification when a
milestone calls for containerized validation.
-- `context7` — use for current library/tool documentation when exact external API behavior matters.
-- `node_repl` — use when JavaScript/browser-plugin workflows require the persistent Node-backed
+- `context7`, use for current library/tool documentation when exact external API behavior matters.
+- `node_repl`, use when JavaScript/browser-plugin workflows require the persistent Node-backed
kernel.
Postgres and Perplexity MCP servers are intentionally not configured. Do not assume database or
@@ -826,14 +825,14 @@ It fails if it becomes:
# Jane Street Internship Targeting Addendum
-This section is additive. Preserve all earlier Quant Systems Lab handoff content. The project remains the same: a deterministic C++20 exchange-systems repo. This addendum adds Jane Street Hong Kong December–February internship targeting context and additional execution requirements.
+This section is additive. Preserve all earlier Quant Systems Lab handoff content. The project remains the same: a deterministic C++20 exchange-systems repo. This addendum adds Jane Street Hong Kong December-February internship targeting context and additional execution requirements.
## Role ranking for this user
Use this project primarily for:
-1. **Jane Street Software Engineer Internship — Hong Kong — December–February**
-2. **Jane Street Linux Engineer Internship — Hong Kong — December–February**
+1. **Jane Street Software Engineer Internship. Hong Kong. December-February**
+2. **Jane Street Linux Engineer Internship. Hong Kong. December-February**
Optional but lower priority:
@@ -881,7 +880,7 @@ Quant Systems Lab should therefore include:
## Additive milestone requirement
-Add **M14 — OCaml replay verifier** after final docs/hardening or as a late-stage dedicated milestone. Do not pull it into early C++ milestones.
+Add **M14. OCaml replay verifier** after final docs/hardening or as a late-stage dedicated milestone. Do not pull it into early C++ milestones.
M14 exists to produce a targeted Jane Street SWE signal without corrupting the main C++ project.
@@ -892,7 +891,7 @@ M14 exists to produce a targeted Jane Street SWE signal without corrupting the m
Use title:
```text
-Quant Systems Lab — C++20 Exchange Simulator + OCaml Replay Verifier
+Quant Systems Lab. C++20 Exchange Simulator + OCaml Replay Verifier
```
Use bullets:
@@ -908,7 +907,7 @@ Use bullets:
Use title:
```text
-Quant Systems Lab — Linux Systems + Exchange Infrastructure Simulator
+Quant Systems Lab. Linux Systems + Exchange Infrastructure Simulator
```
Use bullets:
@@ -979,23 +978,23 @@ Before every PR, check:
8. Is OCaml isolated to the replay verifier, not mixed into the core engine prematurely?
-## Additive M15–M20 technical roadmap replacing old optional application polish
+## Additive M15-M20 technical roadmap replacing old optional application polish
-The prior optional `M15 — Jane Street application polish` milestone is removed. Do not implement recruiter-facing polish as the next milestone. The project should now continue with technical depth:
+The prior optional `M15. Jane Street application polish` milestone is removed. Do not implement recruiter-facing polish as the next milestone. The project should now continue with technical depth:
-1. **M15 — Export normalized command streams + final snapshots**
+1. **M15. Export normalized command streams + final snapshots**
- Export complete command streams, engine events, rejections, symbol registration order, and final per-symbol snapshots.
- This gives the OCaml side enough information to replay independently.
-2. **M16 — Independent OCaml replay engine**
+2. **M16. Independent OCaml replay engine**
- OCaml replays the command stream immutably and computes its own final snapshot.
- It must not merely inspect the C++ event log.
-3. **M17 — Differential replay tests: C++ vs OCaml snapshot equality**
+3. **M17. Differential replay tests: C++ vs OCaml snapshot equality**
- CI compares C++ exported snapshots against OCaml-computed snapshots.
-4. **M18 — Property-based command generator**
+4. **M18. Property-based command generator**
- Generate seeded randomized command streams covering valid, invalid, duplicate, reused, IOC, market, cancel, modify, and multi-symbol cases.
-5. **M19 — Shrinker + minimal failing fixture exporter**
+5. **M19. Shrinker + minimal failing fixture exporter**
- Reduce failing generated streams to small, replayable counterexamples.
-6. **M20 — Final docs: differential testing architecture**
+6. **M20. Final docs: differential testing architecture**
- Document the architecture, fixture schemas, property generator, shrinker, and exact limits.
### Strategic reason
diff --git a/MILESTONES.md b/MILESTONES.md
index 34ac385..eef1e5c 100644
--- a/MILESTONES.md
+++ b/MILESTONES.md
@@ -1,4 +1,4 @@
-# MILESTONES.md — Quant Systems Lab build plan
+# MILESTONES.md. Quant Systems Lab build plan
Sequential, dependency-ordered. **Build them in order.** Each milestone is one feature branch and one squash-merge PR. Do not skip ahead; later milestones assume earlier ones exist.
@@ -17,7 +17,7 @@ Sequential, dependency-ordered. **Build them in order.** Each milestone is one f
---
-## M0 — Scaffold, tooling, CI
+## M0. Scaffold, tooling, CI
- **Branch:** `feat/m00-scaffold`
- **PR title:** `chore: scaffold C++ project, tooling, and CI`
@@ -54,7 +54,7 @@ Sequential, dependency-ordered. **Build them in order.** Each milestone is one f
---
-## M1 — Core exchange domain types and invariants
+## M1. Core exchange domain types and invariants
- **Branch:** `feat/m01-core-domain`
- **PR title:** `feat: core exchange domain types and invariants`
@@ -89,7 +89,7 @@ Sequential, dependency-ordered. **Build them in order.** Each milestone is one f
---
-## M2 — Binary protocol encoding and decoding
+## M2. Binary protocol encoding and decoding
- **Branch:** `feat/m02-binary-protocol`
- **PR title:** `feat: binary protocol encoding and decoding`
@@ -121,7 +121,7 @@ Sequential, dependency-ordered. **Build them in order.** Each milestone is one f
---
-## M3 — Price-time priority order book
+## M3. Price-time priority order book
- **Branch:** `feat/m03-order-book`
- **PR title:** `feat: price-time priority order book`
@@ -156,7 +156,7 @@ Sequential, dependency-ordered. **Build them in order.** Each milestone is one f
---
-## M4 — Multi-symbol matching engine
+## M4. Multi-symbol matching engine
- **Branch:** `feat/m04-matching-engine`
- **PR title:** `feat: multi-symbol matching engine`
@@ -185,7 +185,7 @@ Sequential, dependency-ordered. **Build them in order.** Each milestone is one f
---
-## M5 — Deterministic risk checks and in-process order gateway
+## M5. Deterministic risk checks and in-process order gateway
- **Branch:** `feat/m05-risk-gateway`
- **PR title:** `feat: deterministic risk checks and in-process order gateway`
@@ -218,7 +218,7 @@ Sequential, dependency-ordered. **Build them in order.** Each milestone is one f
---
-## M6 — Market data event publisher
+## M6. Market data event publisher
- **Branch:** `feat/m06-market-data`
- **PR title:** `feat: market data event publisher`
@@ -247,7 +247,7 @@ Sequential, dependency-ordered. **Build them in order.** Each milestone is one f
---
-## M7 — Append-only event log
+## M7. Append-only event log
- **Branch:** `feat/m07-event-log`
- **PR title:** `feat: append-only event log`
@@ -277,7 +277,7 @@ Sequential, dependency-ordered. **Build them in order.** Each milestone is one f
---
-## M8 — Deterministic replay and recovery
+## M8. Deterministic replay and recovery
- **Branch:** `feat/m08-replay-recovery`
- **PR title:** `feat: deterministic replay and recovery`
@@ -305,7 +305,7 @@ Sequential, dependency-ordered. **Build them in order.** Each milestone is one f
---
-## M9 — TCP binary order gateway
+## M9. TCP binary order gateway
- **Branch:** `feat/m09-tcp-gateway`
- **PR title:** `feat: TCP binary order gateway`
@@ -336,7 +336,7 @@ Sequential, dependency-ordered. **Build them in order.** Each milestone is one f
---
-## M10 — Network market data publisher
+## M10. Network market data publisher
- **Branch:** `feat/m10-network-market-data`
- **PR title:** `feat: network market data publisher`
@@ -364,7 +364,7 @@ Sequential, dependency-ordered. **Build them in order.** Each milestone is one f
---
-## M11 — Benchmarks and performance reporting
+## M11. Benchmarks and performance reporting
- **Branch:** `feat/m11-benchmarks`
- **PR title:** `perf: benchmark matching, protocol, gateway, and replay`
@@ -396,7 +396,7 @@ Sequential, dependency-ordered. **Build them in order.** Each milestone is one f
---
-## M12 — Hardening with sanitizers and invariant tests
+## M12. Hardening with sanitizers and invariant tests
- **Branch:** `feat/m12-hardening`
- **PR title:** `test: harden engine with sanitizers and invariant tests`
@@ -426,7 +426,7 @@ Sequential, dependency-ordered. **Build them in order.** Each milestone is one f
---
-## M13 — Final architecture, demo, and recruiting polish
+## M13. Final architecture, demo, and recruiting polish
- **Branch:** `feat/m13-docs-polish`
- **PR title:** `docs: final architecture, demo, and recruiting polish`
@@ -458,10 +458,10 @@ Sequential, dependency-ordered. **Build them in order.** Each milestone is one f
---
-## Backlog — optional after M13 only
+## Backlog, optional after M13 only
-> **Execution status (historical).** This backlog was tracked as GitHub issues #24–#51. Items
-> **#34–#51 were completed and merged before v0.1.0** (differential follow-ups, shrinker
+> **Execution status (historical).** This backlog was tracked as GitHub issues #24, #51. Items
+> **#34, #51 were completed and merged before v0.1.0** (differential follow-ups, shrinker
> improvements, oracle hardening, larger property corpus, regression archive, and
> differential-harness benchmarks). Several remaining issues are **promoted into the Phase III/IV
> roadmap** (issue → milestone): **#24 → M24** (ring buffer), **#26 → M26** (threaded pipeline),
@@ -472,7 +472,7 @@ Sequential, dependency-ordered. **Build them in order.** Each milestone is one f
> accept) and **#28** (realistic synthetic order-flow model). The human later reprioritized two
> backlog items, now **done**: **#32** (perf/flamegraph) and **#29** (FIX-like text protocol
> adapter). The genuinely **deferred** product/API items remain **#30** (web dashboard), **#31**
-> (Docker packaging), and **#33** (Pages site) — do not start them before the Phase III/IV systems
+> (Docker packaging), and **#33** (Pages site), do not start them before the Phase III/IV systems
> roadmap unless the human explicitly reprioritizes.
Do not pull backlog items into earlier PRs.
@@ -482,21 +482,21 @@ Do not pull backlog items into earlier PRs.
- Multithreaded gateway and market data pipeline, plus portable threaded TCP serving follow-up. (#26)
- ThreadSanitizer coverage. (#27)
- More realistic synthetic order-flow model. (#28)
-- FIX-like text protocol adapter. (#29) — **done**: `tag=value` SOH-framed adapter
+- FIX-like text protocol adapter. (#29), **done**: `tag=value` SOH-framed adapter
(`include/qsl/protocol/fix.hpp`, `src/protocol/fix.cpp`) over the same internal structs as the
binary codec, with genuine FIX BeginString/BodyLength/CheckSum framing for NewOrderSingle (35=D)
and OrderCancelRequest (35=F). Cross-codec equivalence + malformed-input rejection tested in
`tests/unit/test_fix_protocol.cpp`; docs in `docs/fix_protocol.md`.
- Web dashboard for visualization. (#30)
- Docker packaging. (#31)
-- Perf/flamegraph docs. (#32) — **done**: `make flamegraph` renders a perf call-graph flamegraph
+- Perf/flamegraph docs. (#32), **done**: `make flamegraph` renders a perf call-graph flamegraph
via the dependency-free `scripts/flamegraph.py` (`results/flamegraph.svg` + `.txt`), unit-tested in
`tests/shell/test_flamegraph.sh`. Full hardware cache-PMU evidence stays in #90.
- GitHub Pages documentation site. (#33)
### Differential-testing follow-ups (prioritized)
-**Definitely track — differential oracle self-test.** Deliberately inject a known C++≠OCaml
+**Definitely track, differential oracle self-test.** Deliberately inject a known C++≠OCaml
mismatch and assert end-to-end: (1) the differential test fails, (2) the failure is detected
correctly, (3) the shrinker reduces the failing stream, (4) the resulting minimal fixture
reproduces the mismatch. This tests the fire alarm, not just the building. (#34)
@@ -504,9 +504,9 @@ reproduces the mismatch. This tests the fire alarm, not just the building. (#34)
High:
- CI seed sweep: generate seeds 1..N dynamically in CI instead of relying on only the 8
- committed property seeds — stronger differential coverage. (#35)
+ committed property seeds, stronger differential coverage. (#35)
- Dedicated negative fixtures for `best_bid`, `best_ask`, `trades` (trade_count), and bid-side
- `level` lines (today only ask-level qty, `last_seq`, and `order_count` are covered) — cheap,
+ `level` lines (today only ask-level qty, `last_seq`, and `order_count` are covered), cheap,
improves oracle robustness. (#36)
- Synthetic divergence demonstration: show the shrinker finding a real C++≠OCaml failure rather
than only the artificial "produces a trade" predicate. (#37)
@@ -526,7 +526,7 @@ Medium:
the Linux golden check against macOS-committed fixtures). (#45)
- **Differential failure artifact bundle (highest-value addition):** on a CI divergence, save and
upload as artifacts the original stream, the shrunk stream, the C++ output, the OCaml output,
- and the unified diff — mirrors mature fuzzing/differential systems and makes debugging easy. (#40)
+ and the unified diff, mirrors mature fuzzing/differential systems and makes debugging easy. (#40)
- Shrinker effectiveness metrics: report original/final command counts, reduction %, and shrink
iterations during CI. (#46)
- Seed reproducibility manifest: record generator version, seed, and fixture hash so future
@@ -535,11 +535,11 @@ Medium:
Medium-Low:
- Mutation testing for the oracle: intentionally corrupt snapshots, trade counts, best bid/ask,
- and sequence numbers, and verify the differential layer detects each — validates the checker. (#48)
+ and sequence numbers, and verify the differential layer detects each, validates the checker. (#48)
Low:
-- Larger committed corpus (e.g. prop_seed1..50) — more confidence, lower signal per maintenance. (#49)
+- Larger committed corpus (e.g. prop_seed1..50), more confidence, lower signal per maintenance. (#49)
- Historical regression archive: keep a folder of important shrunk failures discovered over time
(useful once real bugs are ever found). (#50)
- Performance benchmarks for the differential harness. (#51)
@@ -573,9 +573,9 @@ docs/linux_performance.md
docs/socket_gateway.md
```
-These docs should evolve gradually. They may start as placeholders in M0, but should become substantive by M9–M13.
+These docs should evolve gradually. They may start as placeholders in M0, but should become substantive by M9-M13.
-## M14 — OCaml replay verifier
+## M14. OCaml replay verifier
- **Branch:** `feat/m14-ocaml-replay-verifier`
- **PR title:** `feat: OCaml replay verifier for exchange event logs`
@@ -633,7 +633,7 @@ ocaml/bin/verify_replay.ml
- [ ] Root README references the OCaml verifier only after it exists.
- [ ] `PROGRESS.md` updated.
-## M15 — Export normalized command streams + final snapshots
+## M15. Export normalized command streams + final snapshots
- **Branch:** `feat/m15-export-command-streams-and-snapshots`
- **PR title:** `feat: export normalized command streams and final snapshots`
@@ -678,7 +678,7 @@ Add or extend tooling so the C++ side can export deterministic fixtures containi
- [ ] `dune runtest --root ocaml` passes.
- [ ] `PROGRESS.md` updated.
-## M16 — Independent OCaml replay engine
+## M16. Independent OCaml replay engine
- **Branch:** `feat/m16-independent-ocaml-replay-engine`
- **PR title:** `feat: independently replay command streams in OCaml`
@@ -729,7 +729,7 @@ Implement OCaml modules for:
- [ ] Docs state exact limitations.
- [ ] `PROGRESS.md` updated.
-## M17 — Differential replay tests: C++ vs OCaml snapshot equality
+## M17. Differential replay tests: C++ vs OCaml snapshot equality
- **Branch:** `feat/m17-differential-replay-tests`
- **PR title:** `test: compare C++ and OCaml replay snapshots`
@@ -760,7 +760,7 @@ Add test harnesses that:
- [ ] `dune runtest --root ocaml` passes.
- [ ] `PROGRESS.md` updated.
-## M18 — Property-based command generator
+## M18. Property-based command generator
- **Branch:** `feat/m18-property-command-generator`
- **PR title:** `test: generate property-based market command streams`
@@ -803,7 +803,7 @@ Generated streams must be replayable by both C++ and OCaml. Seeds must be printe
- [ ] `dune runtest --root ocaml` passes.
- [ ] `PROGRESS.md` updated.
-## M19 — Shrinker + minimal failing fixture exporter
+## M19. Shrinker + minimal failing fixture exporter
- **Branch:** `feat/m19-shrinker-minimal-failing-fixtures`
- **PR title:** `test: shrink failing command streams to minimal fixtures`
@@ -839,7 +839,7 @@ Implement shrink strategy for failing command streams:
- [ ] `dune runtest --root ocaml` passes.
- [ ] `PROGRESS.md` updated.
-## M20 — Final docs: differential testing architecture
+## M20. Final docs: differential testing architecture
- **Branch:** `feat/m20-differential-testing-docs`
- **PR title:** `docs: document differential replay and property testing architecture`
@@ -885,7 +885,7 @@ Document:
- [ ] `dune runtest --root ocaml` passes.
- [ ] `PROGRESS.md` updated.
-## M21 — Repository license and maintainer docs
+## M21. Repository license and maintainer docs
- **Branch:** `feat/m21-repo-license-maintainer-docs`
- **PR title:** `chore: add repository license and maintainer docs`
@@ -918,11 +918,11 @@ corporate open-source governance theater.
benchmark numbers must be reproducible from committed scripts.
- `SECURITY.md` documents: no bug bounty; local/demo TCP/UDP services are unauthenticated and
loopback-focused; do not expose `qsl-gateway` or `qsl-mdfeed` to untrusted networks; report
- issues through GitHub issues or maintainer contact; honest language — this is a systems lab,
+ issues through GitHub issues or maintainer contact; honest language, this is a systems lab,
not a hardened production service.
-- `CHANGELOG.md` starts with: Unreleased; M15–M20 Phase II differential-testing roadmap; M14
+- `CHANGELOG.md` starts with: Unreleased; M15-M20 Phase II differential-testing roadmap; M14
OCaml replay verifier; M12 sanitizer/fuzz/invariant hardening; M11 measured benchmarks;
- M7/M8 replay log and recovery; M3–M6 matching/risk/market-data core.
+ M7/M8 replay log and recovery; M3-M6 matching/risk/market-data core.
### Definition of Done
@@ -935,7 +935,7 @@ corporate open-source governance theater.
- [ ] `dune runtest --root ocaml` passes.
- [ ] PR opened and not merged.
-## M22 — Release readiness audit
+## M22. Release readiness audit
- **Branch:** `feat/m22-release-readiness-audit`
- **PR title:** `docs: audit release readiness`
@@ -976,11 +976,11 @@ substance, not marketing.
- [ ] No fake production claims.
- [ ] PR opened and not merged.
-## M23 — Optional v0.1.0 release
+## M23. Optional v0.1.0 release
- **Branch:** `feat/m23-v0-1-0-release`
- **PR title:** `chore: prepare v0.1.0 release notes`
-- **Suggested release title:** `v0.1.0 — deterministic exchange systems lab`
+- **Suggested release title:** `v0.1.0, deterministic exchange systems lab`
### Goal
@@ -989,7 +989,7 @@ audit are complete. This is optional and requires explicit human approval.
### Scope
-- Only start if M20–M22 are merged and the human explicitly approves a release.
+- Only start if M20-M22 are merged and the human explicitly approves a release.
- Create release notes for `v0.1.0`.
- Do not package the project.
- Do not publish to Homebrew, vcpkg, Conan, opam, pip, npm, or Docker.
@@ -1029,11 +1029,11 @@ The project is strong enough when a reviewer can quickly infer:
v0.1.0 proved a correctness-first deterministic exchange-systems lab. Phase III/IV is the next
deliberate credibility arc: real concurrency, memory discipline, Linux profiling, socket
-hardening, and external review signal — not more product surface area.
+hardening, and external review signal, not more product surface area.
## Phase III / IV execution rule
-Do M24–M49 **in order**, except that issue #90 can be worked as an evidence follow-up as soon as a
+Do M24-M49 **in order**, except that issue #90 can be worked as an evidence follow-up as soon as a
PMU-capable Linux host is available. Do not skip to Linux perf/socket work before the concurrency
primitive and threaded pipeline exist. Do not start external review before there is enough evidence
to review.
@@ -1069,11 +1069,11 @@ to review.
After PR #89, prioritize:
-1. Issue #90 — generate full Linux hardware PMU perf artifacts on a PMU-capable Linux target.
-2. M30 — socket/kernel profiling.
-3. M31 — external review signal.
-4. M32 — pool-backed order-book integration.
-5. M33 — advanced concurrency validation.
+1. Issue #90, generate full Linux hardware PMU perf artifacts on a PMU-capable Linux target.
+2. M30, socket/kernel profiling.
+3. M31, external review signal.
+4. M32, pool-backed order-book integration.
+5. M33, advanced concurrency validation.
These items produce more systems-engineering signal than more isolated microbenchmarks.
Future CPU locality, false-sharing, contiguous-storage, and external-review evidence should remain
@@ -1081,7 +1081,7 @@ ahead of speculative DPDK/NIC exploration because they exercise the current code
Forbidden throughout: production-grade/HFT/real-exchange/formally-verified/profitable/guaranteed-
low-latency/production-networking claims; and dashboards, trading strategies, market-data APIs,
-FIX adapters, Docker packaging, or Pages sites (the deferred #29–#31 and #33) before this arc completes.
+FIX adapters, Docker packaging, or Pages sites (the deferred #29, #31 and #33) before this arc completes.
## Roadmap audit decisions after PR #112
@@ -1113,7 +1113,7 @@ Rejected additions:
---
-## M24 — Bounded SPSC ring buffer
+## M24. Bounded SPSC ring buffer
- **Branch:** `feat/m24-spsc-ring-buffer`
- **PR title:** `feat: add bounded SPSC ring buffer`
@@ -1143,7 +1143,7 @@ marketing language unless the implementation actually qualifies, no benchmark cl
---
-## M25 — Memory-ordering and concurrency evidence package
+## M25. Memory-ordering and concurrency evidence package
- **Branch:** `feat/m25-memory-ordering-evidence`
- **PR title:** `docs: document concurrency ownership and memory ordering`
@@ -1167,7 +1167,7 @@ marketing language unless the implementation actually qualifies, no benchmark cl
---
-## M26 — Multithreaded gateway-engine-feed pipeline prototype
+## M26. Multithreaded gateway-engine-feed pipeline prototype
- **Branch:** `feat/m26-threaded-pipeline`
- **PR title:** `feat: add threaded gateway-engine-feed pipeline prototype`
@@ -1191,7 +1191,7 @@ No production matching-latency attempt, no MPMC queue, no kernel bypass, no real
---
-## M27 — ThreadSanitizer coverage
+## M27. ThreadSanitizer coverage
- **Branch:** `feat/m27-thread-sanitizer`
- **PR title:** `test: add ThreadSanitizer coverage for concurrent pipeline`
@@ -1214,7 +1214,7 @@ No production matching-latency attempt, no MPMC queue, no kernel bypass, no real
---
-## M28 — Memory pool allocator experiment
+## M28. Memory pool allocator experiment
- **Branch:** `feat/m28-memory-pool-allocator`
- **PR title:** `perf: add memory pool allocator experiment`
@@ -1237,7 +1237,7 @@ No production matching-latency attempt, no MPMC queue, no kernel bypass, no real
---
-## M29 — Linux perf profiling workflow and artifacts
+## M29. Linux perf profiling workflow and artifacts
- **Branch:** `feat/m29-linux-perf-profiling`
- **PR title:** `perf: add Linux perf profiling artifacts`
@@ -1269,7 +1269,7 @@ No production matching-latency attempt, no MPMC queue, no kernel bypass, no real
---
-## M30 — Kernel/socket path profiling and Linux socket hardening
+## M30. Kernel/socket path profiling and Linux socket hardening
- **Branch:** `feat/m30-socket-profiling-hardening`
- **PR title:** `perf: profile and harden Linux socket path`
@@ -1293,7 +1293,7 @@ No production matching-latency attempt, no MPMC queue, no kernel bypass, no real
---
-## M31 — External review / maintainer signal
+## M31. External review / maintainer signal
- **Branch:** `docs/m31-external-review`
- **PR title:** `docs: prepare external review package`
@@ -1369,7 +1369,7 @@ work without pretending the M28 allocator microbenchmark already changed the eng
---
-## M32 — Pool-backed order-book storage experiment
+## M32. Pool-backed order-book storage experiment
- **Branch:** `feat/m32-pool-backed-order-book-storage`
- **PR title:** `perf: evaluate pool-backed order-book storage`
@@ -1400,7 +1400,7 @@ work without pretending the M28 allocator microbenchmark already changed the eng
---
-## M33 — Advanced concurrency validation
+## M33. Advanced concurrency validation
- **Branch:** `feat/m33-advanced-concurrency-validation`
- **PR title:** `test: expand concurrency validation methodology`
@@ -1423,7 +1423,7 @@ work without pretending the M28 allocator microbenchmark already changed the eng
---
-## M34 — epoll gateway architecture
+## M34, epoll gateway architecture
- **Branch:** `feat/m34-epoll-gateway-architecture`
- **PR title:** `feat: add epoll gateway architecture prototype`
@@ -1445,7 +1445,7 @@ work without pretending the M28 allocator microbenchmark already changed the eng
---
-## M35 — Multi-client load and socket-pressure testing
+## M35. Multi-client load and socket-pressure testing
- **Branch:** `feat/m35-multi-client-socket-pressure`
- **PR title:** `test: add multi-client socket pressure coverage`
@@ -1471,15 +1471,15 @@ work without pretending the M28 allocator microbenchmark already changed the eng
After M35, a CodeScene Code Health analysis (project 80913) of all production and test files
identified eleven files below 9.0. They are addressed by the seven behavior-preserving refactor
-milestones **M36–M42** below, inserted before the original networking/persistence roadmap (which
-shifts to **M43–M49**; NUMA is now **M43**). M36–M39 and M41 come from production findings; M40
+milestones **M36-M42** below, inserted before the original networking/persistence roadmap (which
+shifts to **M43-M49**; NUMA is now **M43**). M36-M39 and M41 come from production findings; M40
consolidates the engine test suites (a CodeScene test finding, kept as its own milestone); M42
(shell-script helpers) was identified manually because CodeScene cannot score shell. Determinism,
replay, the differential suite, and integer-tick pricing remain invariants, not refactor targets.
---
-## M36 — Decompose the epoll event loop and connection lifecycle
+## M36. Decompose the epoll event loop and connection lifecycle
- **Branch:** `refactor/m36-epoll-event-loop-decomposition`
- **PR title:** `refactor: decompose epoll event-loop and connection lifecycle`
@@ -1503,7 +1503,7 @@ replay, the differential suite, and integer-tick pricing remain invariants, not
---
-## M37 — Extract threaded-pipeline stage helpers
+## M37. Extract threaded-pipeline stage helpers
- **Branch:** `refactor/m37-threaded-pipeline-stage-helpers`
- **PR title:** `refactor: extract threaded pipeline stage helpers`
@@ -1527,7 +1527,7 @@ replay, the differential suite, and integer-tick pricing remain invariants, not
---
-## M38 — Split the command-stream shrinker into named passes
+## M38. Split the command-stream shrinker into named passes
- **Branch:** `refactor/m38-shrinker-reduction-passes`
- **PR title:** `refactor: split shrinker into named reduction passes`
@@ -1549,7 +1549,7 @@ replay, the differential suite, and integer-tick pricing remain invariants, not
---
-## M39 — Encapsulate order-book matching parameters
+## M39. Encapsulate order-book matching parameters
- **Branch:** `refactor/m39-order-book-matching-parameters`
- **PR title:** `refactor: encapsulate order-book matching parameters`
@@ -1572,12 +1572,12 @@ replay, the differential suite, and integer-tick pricing remain invariants, not
---
-## M40 — Consolidate engine correctness test suites
+## M40. Consolidate engine correctness test suites
- **Branch:** `refactor/m40-engine-test-consolidation`
- **PR title:** `test: consolidate engine correctness test suites`
- **Goal:** Remove duplication and oversized assertion blocks from the engine/risk test suites
- without weakening coverage. Test-only milestone — no production change.
+ without weakening coverage. Test-only milestone, no production change.
### Scope
@@ -1590,12 +1590,12 @@ replay, the differential suite, and integer-tick pricing remain invariants, not
- [ ] Code Health ≥ 9.0 for all four files.
- [ ] No production code changed; the same scenarios and invariants are asserted (coverage and
- non-vacuity preserved — trades/rejects/cancels/modifies still exercised).
+ non-vacuity preserved, trades/rejects/cancels/modifies still exercised).
- [ ] `make check` + `make asan` pass; `PROGRESS.md` updated.
---
-## M41 — Simplify gateway session frame dispatch
+## M41. Simplify gateway session frame dispatch
- **Branch:** `refactor/m41-session-frame-dispatch`
- **PR title:** `refactor: simplify gateway session frame dispatch`
@@ -1615,12 +1615,12 @@ replay, the differential suite, and integer-tick pricing remain invariants, not
---
-## M42 — Extract shared shell-script helpers
+## M42. Extract shared shell-script helpers
- **Branch:** `refactor/m42-shared-shell-script-helpers`
- **PR title:** `refactor: extract shared shell-script helpers`
- **Goal:** Remove duplicated boilerplate across the socket/perf scripts (the M35 deferred follow-up).
- Manually identified — CodeScene does not score shell, so this milestone has no Code Health metric.
+ Manually identified. CodeScene does not score shell, so this milestone has no Code Health metric.
### Scope
@@ -1637,7 +1637,7 @@ replay, the differential suite, and integer-tick pricing remain invariants, not
---
-## M43 — NUMA awareness study
+## M43. NUMA awareness study
- **Branch:** `feat/m43-numa-awareness-study`
- **PR title:** `docs: study NUMA and CPU affinity effects`
@@ -1680,7 +1680,7 @@ replay, the differential suite, and integer-tick pricing remain invariants, not
---
-## M44 — Ingress queue memory-ordering and false-sharing study
+## M44. Ingress queue memory-ordering and false-sharing study
- **Branch:** `feat/m44-ingress-memory-ordering-false-sharing`
- **PR title:** `perf: study ingress memory ordering and false sharing`
@@ -1724,7 +1724,7 @@ replay, the differential suite, and integer-tick pricing remain invariants, not
---
-## M45 — Exchange-grade persistence prototype
+## M45. Exchange-grade persistence prototype
- **Branch:** `feat/m45-persistence-prototype`
- **PR title:** `feat: prototype stronger persistence strategy`
@@ -1752,13 +1752,13 @@ replay, the differential suite, and integer-tick pricing remain invariants, not
---
-## M46 — Recovery benchmarking
+## M46. Recovery benchmarking
- **Branch:** `feat/m46-recovery-benchmarking`
- **PR title:** `perf: benchmark recovery paths`
- **Goal:** Measure recovery objectives from replay and snapshot restoration.
- **Status:** ☑ merged via PR #118 (`aeba72c`).
-- **Dependencies:** M8 replay/recovery, M15–M20 differential fixtures, M45 persistence prototype if
+- **Dependencies:** M8 replay/recovery, M15-M20 differential fixtures, M45 persistence prototype if
it lands first, and the benchmark metadata policy from M11/M29.
- **Signal gained:** Recovery objective framing, replay-cost visibility, and benchmark
interpretation.
@@ -1780,7 +1780,7 @@ replay, the differential suite, and integer-tick pricing remain invariants, not
---
-## M47 — Contiguous order-book storage and cache-locality study
+## M47. Contiguous order-book storage and cache-locality study
- **Branch:** `feat/m47-contiguous-order-book-storage`
- **PR title:** `perf: study contiguous order-book storage`
@@ -1814,7 +1814,7 @@ replay, the differential suite, and integer-tick pricing remain invariants, not
---
-## M48 — DPDK research and prototype
+## M48. DPDK research and prototype
- **Branch:** `feat/m48-dpdk-research-prototype`
- **PR title:** `docs: research DPDK packet-path tradeoffs`
@@ -1849,7 +1849,7 @@ replay, the differential suite, and integer-tick pricing remain invariants, not
---
-## M49 — NIC offload and low-latency networking study
+## M49. NIC offload and low-latency networking study
- **Branch:** `feat/m49-nic-offload-study`
- **PR title:** `docs: study NIC offload and low-latency networking`
diff --git a/PERFORMANCE.md b/PERFORMANCE.md
index 3e93355..08c578a 100644
--- a/PERFORMANCE.md
+++ b/PERFORMANCE.md
@@ -1,162 +1,177 @@
-# Performance Evidence — matching-engine hot path
-
-This is the performance-evidence report for the v0.2.2 order-book optimizations. It profiles the
-matching-engine hot path with Linux `perf` and flamegraphs on **ARM64 (Apple M2, Fedora Asahi)**,
-identifies **order-book insertion and matching as the dominant cost**, and documents the
-**before → after** change in latency, throughput, and CPU counters. Every number comes from the
-committed `qsl-perfeval` harness and `perf`; nothing is estimated.
-
-> Scope and honesty. This is a single-machine, single-process, synthetic micro-evidence report —
-> not a production-latency or HFT-readiness claim. Absolute numbers are hardware/compiler/build/
-> thermal dependent; the **before/after delta** measured back-to-back on the same host is the load-
-> bearing result. Two metrics are reported honestly as **unavailable** rather than estimated:
-> cache-miss counters (the Apple Silicon PMU does not expose them — [issue #90]) and any
-> sub-`steady_clock`-resolution timing.
-
-## Optimizations under test
-
-| # | Change | Where |
-|---|---|---|
-| #138 | `std::map::emplace` → `try_emplace` for baseline price levels | `OrderBook::level_for` |
-| #145 | order-index `unordered_map` `max_load_factor` 1.0 → **0.25** | `OrderBook` constructor |
-
-Both preserve determinism (the differential fixtures are byte-identical across g++/clang++ and vs
-the committed copies; the OCaml differential passes). The index is never iterated for output, so
-changing its bucket count cannot change emitted events or snapshots.
-
-## Headline before/after
-
-Workload: `qsl-perfeval 60000000` — a steady-state deep book (~512 resting orders, baseline storage).
-Each **order** is one `new_limit` (it may match resting liquidity and rest its remainder); the book
-is held ~512 deep by cancelling the oldest order each cycle, so the per-order throughput cost
-includes that maintenance cancel.
-
-| Metric | Before | After | Δ |
-|---|---|---|---|
-| **Throughput** (orders/sec) | 8.89 M | **11.13 M** | **+25.2 %** |
-| Median (p50) latency¹ | 83 ns | 83 ns | ~0 |
-| **p99 latency**¹ | 250 ns | **208 ns** | **−16.8 %** |
-| Mean latency¹ | 92 ns | 75 ns | −18.5 % |
-| **Cycles / order** | 348.2 | **288.4** | **−17.2 %** |
-| Instructions / order | 1239 | 1143 | −7.8 % |
-| IPC | 3.56 | 3.96 | +11.4 % |
-| Branches / order | 244 | 229 | −6.1 % |
-| **Branch-miss rate** | 2.02 % | **1.81 %** | −0.21 pp |
-| Cache-miss rate | _unavailable_ | _unavailable_ | — ([#90]) |
-| **Allocations / order** | 1.106 | 1.106 | **0 (unchanged)** |
-
-¹ Latency is per `new_limit` only (not the maintenance cancel) and includes ~12 ns of `steady_clock`
-read overhead per measured op (two VDSO clock reads); the before/after delta cancels it. Throughput
-is per full cycle (`new_limit` + cancel), which is why p50 (~83 ns) is below the per-cycle wall time
-(1 / 8.89 M ≈ 112 ns before; ≈ 90 ns after).
+# Performance Evidence: matching-engine hot path
+
+This report profiles the matching-engine hot path with Linux `perf` and flamegraphs on
+**ARM64 (Apple M2, Fedora Asahi)**, identifies **order-book insertion and matching as the dominant
+cost**, and documents the change from the **v0.1.0 baseline (first release) to v0.2.2** in
+allocations, latency, throughput, and CPU counters. Every number comes from the committed
+`qsl-perfeval` harness and `perf`; nothing is estimated.
+
+> Scope and honesty. This is a single-machine, single-process, synthetic micro-evidence report,
+> not a production-latency or HFT-readiness claim. Absolute numbers are hardware, compiler, build,
+> and thermal dependent; the **v0.1.0 to v0.2.2 delta** measured back-to-back on the same host with
+> the same harness and preset is the load-bearing result. One metric is reported as **unavailable**
+> rather than estimated: cache-miss counters (the Apple Silicon PMU does not expose them,
+> [issue #90]).
+
+## Headline: v0.1.0 to v0.2.2
+
+Workload: `qsl-perfeval 60000000`, a steady-state deep book (~512 resting orders, **baseline
+storage** on both versions). Each **order** is one `new_limit` (it may match resting liquidity and
+rest its remainder, or fully fill). The book is held ~512 deep by cancelling the oldest **resting**
+order each cycle (only orders that actually rested are tracked, so depth does not drift with the
+match rate). The same harness and the same `release` build preset were used for both versions; the
+v0.1.0 figure comes from the same harness ported into a `git worktree` at the `v0.1.0` tag.
+
+| Metric | v0.1.0 | v0.2.2 | Delta |
+|---|--:|--:|--:|
+| **Allocations / order** | 4.094 | **2.670** | **-34.8 %** |
+| **Cycles / order** | 310.7 | **289.5** | **-6.8 %** |
+| Instructions / order | 1215 | 1157 | -4.7 % |
+| IPC | 3.91 | 4.00 | +2.3 % |
+| Branch-miss rate | 2.01 % | 1.68 % | -0.33 pp |
+| p50 / p99 latency, new_limit | 83 / 209 ns | 83 / 209 ns | ~0 |
+| Cache-miss rate | _unavailable_ | _unavailable_ | ([#90]) |
+
+Cycles/order, instructions/order, and allocations/order are **frequency-independent counts**, so they
+are the reliable comparison metrics; raw wall-clock throughput (~10.5 to 11 M orders/sec) is governed
+by CPU frequency scaling under `schedutil` and is too thermally noisy to quote a precise delta. At a
+fixed clock the -6.8 % cycles/order corresponds to roughly +7 % throughput. Latency is per `new_limit`
+only and includes ~12 ns of `steady_clock` read overhead per op; the delta cancels it.
### The honest mechanism
-The win is **fewer cycles and instructions per order**, not fewer allocations:
-
-- **Allocations are unchanged** (1.106 → 1.106). The original `#138` rationale ("`emplace` allocates
- then frees a throwaway map node") turned out to be **wrong for libstdc++** — `std::map::emplace`
- checks the key *before* allocating a node, so it does not churn the heap. The `try_emplace` win is
- avoiding the construction/destruction of a throwaway empty `std::pmr::list` (and the heavier
- `emplace` code path) on every insert when the level already exists — pure instruction savings,
- which the counters confirm. This correction is the whole point of measuring with hardware counters
- instead of guessing.
-- **Shorter index probe chains.** Capping the order-index load factor at 0.25 keeps the
- `OrderId → Locator` hash table sparse, so each of the 1–4 lookups per order (`contains`, `cancel`
- find/erase, `rest` insert) probes fewer buckets. That shows up as lower instructions/order **and**
- higher IPC (+11.4 %) — shorter chains stall the pipeline/memory system less — and a lower
- branch-miss rate (fewer mispredicted bucket-traversal loop branches). The cache-locality component
- is plausible but **not directly measurable here** (no cache counters; [#90]).
+Measuring with hardware counters keeps the claims grounded, and corrected two earlier mistakes:
+
+- **The dominant cumulative win is allocations: 4.094 to 2.670 per order, a 35 % cut**, the payoff of
+ the storage and PMR work across the v0.1.x and v0.2.x arc (pooled/monotonic resources for the
+ order-book nodes), measured even on the **default baseline storage path**. (An earlier draft claimed
+ 73 %; that was a measurement bug. The allocation counter only overrode `operator new(size_t)`, so it
+ missed the ~1.56 **over-aligned** allocations per order that v0.2.2's storage makes and v0.1.0 does
+ not. Counting every `operator new` variant gives the true 2.670, and the harness now counts all of
+ them in its `qsl-perfeval-allocs` build.)
+- **Cycles/order fell 6.8 % and instructions/order 4.7 %.** Fewer, and cheaper, allocations cut memory
+ traffic and branch mispredictions (-16 % relative); the baseline-storage hot path is otherwise
+ bounded by the ordered-map and intrusive-list operations. The two most recent micro-optimizations
+ (see below) are part of this.
+- **The latency distribution is unchanged** (p50 83 ns, p99 209 ns on both). The median order already
+ hits an existing level with a short probe; the gains are in aggregate allocation traffic and
+ cycles/order, not the per-op tail.
## Profiling: where the time goes
`perf record --call-graph fp` on `qsl-perfeval`, rendered with the dependency-free
-`scripts/flamegraph.py` (no external FlameGraph toolkit). Frame width ∝ on-CPU samples.
+`scripts/flamegraph.py` (no external FlameGraph toolkit). Frame width is proportional to on-CPU
+samples. **Every frame is a resolved symbol; there are zero `[unknown]` frames** (see the section
+after next for how the unresolvable boundary frames were identified and handled).
-| Before | After |
+| v0.1.0 baseline | v0.2.2 |
|---|---|
-| [](docs/performance/before.svg) | [](docs/performance/after.svg) |
+| [](docs/performance/before.svg) | [](docs/performance/after.svg) |
+
+Both flamegraphs show **order-book insertion and matching dominating** (`new_limit` to `add_limit` to
+`rest`/match, ~77 % to ~83 % of samples). The libc malloc internals (`operator new`, `_mid_memalign`,
+`_int_malloc`, `_int_free`, `cfree`) are visible and named on both; their share shrinks in v0.2.2,
+consistent with the 35 % allocation cut.
+
+## The two most recent optimizations (within v0.2.2)
+
+Two micro-optimizations landed late in the arc. Measured as a focused A/B (the same v0.2.2 source
+with just these two reverted vs applied, `flamegraph` preset):
-`perf report` (children %, hot path) confirms **order-book insertion + matching dominate**, and pins
-the two optimizations' effect:
+| # | Change | Where |
+|---|---|---|
+| #138 | `std::map::emplace` to `try_emplace` for baseline price levels | `OrderBook::level_for` |
+| #145 | order-index `unordered_map` `max_load_factor` 1.0 to **0.25** | `OrderBook` constructor |
+
+`perf report` (children %, hot path) pins their effect:
```
- BEFORE AFTER
-MatchingEngine::new_limit 80.1 % 83.2 %
- OrderBook::add_limit 69.5 % 74.7 %
- OrderBook::match_baseline 25.7 % 32.0 % <- matching
- OrderBook::rest 33.3 % 31.8 % <- insertion
- OrderBook::level_for 21.3 % -> 17.5 % <- #138 try_emplace
- OrderBook::contains 3.6 % -> 1.3 % <- #145 load-factor (dup-id lookup)
-MatchingEngine::cancel 18.2 % 15.8 %
- OrderBook::cancel 16.0 % -> 13.2 % <- #145 load-factor (find + erase)
+ reverted applied
+OrderBook::level_for 21 % -> 17 % #138 try_emplace
+OrderBook::contains 3.6 % -> 1.3 % #145 load-factor (dup-id lookup)
+OrderBook::cancel 16 % -> 13 % #145 load-factor (find + erase)
```
-(Percentages are of total samples, so as the optimized functions shrink the survivors grow
-proportionally — e.g. `new_limit` rises 80→83 % only because the total dropped. The *absolute* wins
-are `level_for`, `contains`, and `cancel` all falling, exactly the two changes' targets.)
-
-`perf annotate` attributes the remaining cost of `level_for` to the `std::_Rb_tree` lookup/insert
-loads (`ldr`/`ldp` over the red-black-tree nodes) — the inherent cost of an ordered price-level map,
-not avoidable allocation.
+The `try_emplace` win is **not** fewer allocations (libstdc++ `std::map::emplace` checks the key
+before allocating; allocs/order is unchanged by these two changes): it avoids constructing and
+destroying a throwaway empty `std::pmr::list` on every insert when the level already exists. The
+load-factor cap keeps the order-index hash table sparse, shortening every probe. Both preserve
+determinism: the differential fixtures stay byte-identical across g++/clang++ and the OCaml
+differential passes (the index is never iterated for output, so its bucket count cannot change
+emitted events or snapshots).
+
+## What the `[unknown]` frames were, and why there are none
+
+The flamegraphs render with **zero `[unknown]` frames**, and that is real resolution, not hiding.
+Every unresolvable frame was identified:
+
+- **fp allocator-boundary artifact.** glibc 2.43's malloc fast paths (`_mid_memalign`, `_int_malloc`,
+ `tcache_get`, `cfree`) do not preserve the frame-pointer register (x29). When fp unwinding walks
+ out of them it reads a data register as a return address, inserting **one spurious frame with a
+ corrupted address** (for example `0x62ffff027c1a63`) between two already-resolved frames, e.g.
+ `operator new(...)` ; `[unknown]` ; `_mid_memalign`. The real allocator frames are present on both
+ sides; removing the spurious one **reveals the true `operator new` to `_mid_memalign` to
+ `_int_malloc` chain**.
+- **vDSO leaf.** A sample taken inside `[vdso]` `clock_gettime` (from `steady_clock::now()`) that perf
+ cannot symbolize; it is attributed to its resolved `clock_gettime` caller.
+
+DWARF unwinding was tested and is **worse** here: it resolves the malloc internals but mangles the
+`_start` assembly entry (no CFI) into roughly three unknown frames per stack (4477 vs fp's 37 on the
+same workload). So fp unwinding plus folding each identified artifact into its resolved caller is the
+cleanest fully-symbolized result on this aarch64 host. `scripts/flamegraph.py --keep-unknown`
+disables the fold if you want to see the raw artifacts.
## Hardware counters
-Full raw `perf stat` for both builds, with derivations and the counter-availability caveat, is in
+Full raw `perf stat` for both versions, with derivations and the counter-availability caveat, is in
**[`docs/performance/perf-stat.txt`](docs/performance/perf-stat.txt)**. Cycles, instructions,
-branches, and branch-misses are **real Apple Avalanche P-core PMU counts**; cache-references /
+branches, and branch-misses are **real Apple Avalanche P-core PMU counts**; cache-references and
cache-misses are **not implemented** by this PMU ([#90]), so cache-miss rate is reported as
unavailable, never estimated.
-## Methodology / reproduction
+## Methodology and reproduction
```
Hardware Apple M2 (aarch64), Avalanche performance cores (MIDR CPU part 0x032), bare metal
Kernel Linux 6.19.14-400.asahi.fc44.aarch64+16k (Fedora Asahi Remix)
Governor schedutil
Compiler GCC (c++) 16.1.1
-Flags Release (-O3 -DNDEBUG) + -fno-omit-frame-pointer -g (CMake "flamegraph" preset)
+Flags Release (-O3 -DNDEBUG); flamegraphs add -fno-omit-frame-pointer -g
perf 6.19.14, kernel.perf_event_paranoid = 2
```
-Reproduce (the `qsl-perfeval` harness is a dedicated binary — it overrides global `operator new` to
-count allocations, kept out of `qsl-bench` so it cannot perturb `results/latest.txt`):
+There are **two build targets**, on purpose: performance is measured with `qsl-perfeval` (the system
+allocator untouched), and allocations/order with `qsl-perfeval-allocs` (it overrides every global
+`operator new` variant to count, which adds a little work per aligned allocation and would perturb
+the cycle numbers). Both are kept out of `qsl-bench` so neither can perturb `results/latest.txt`.
```bash
-cmake --preset flamegraph
-cmake --build --preset flamegraph --target qsl-perfeval
-BIN=build/flamegraph/qsl-perfeval
-
-# throughput + allocations/order (clean: no per-op timer in the cycle count)
-"$BIN" 60000000
+cmake --preset release
+cmake --build --preset release --target qsl-perfeval qsl-perfeval-allocs
-# latency distribution (mean / p50 / p99; includes timer overhead, reported)
-"$BIN" 5000000 --latency
+# performance (no allocation-counting instrumentation):
+build/release/qsl-perfeval 60000000 # throughput
+build/release/qsl-perfeval 5000000 --latency # latency (mean / p50 / p99)
+perf stat -e cycles,instructions,branches,branch-misses -- build/release/qsl-perfeval 60000000
-# hardware counters
-perf stat -e cycles,instructions,branches,branch-misses -- "$BIN" 60000000
-
-# flamegraph
-perf record --call-graph fp -F 4000 -g -e cpu-clock -o perf.data -- "$BIN" 60000000
-perf script -i perf.data | python3 scripts/flamegraph.py --collapse-only \
- | python3 scripts/flamegraph.py --from-collapsed > flame.svg
-
-# hot path / annotation
-perf report -i perf.data --stdio
-perf annotate -i perf.data --stdio
+# allocations/order (counting build, reports "n/a" in the plain build):
+build/release/qsl-perfeval-allocs 60000000
```
-The **before** build is the same source with the two changes reverted (`emplace` in `level_for`,
-no `max_load_factor` call). Before and after were measured back-to-back on the same host.
-
-## Tuning balance (why 0.25, not lower)
-
-The index load factor was swept on the profile workload: 0.5 → ~+10 %, 0.25 → ~+18 %, 0.125 → ~+20 %.
-The curve flattens below ~0.25, so **0.25** captures essentially all of the throughput win as a clean
-load-factor *policy* (memory scales with book size) rather than over-tuning a fixed bucket count or
-paying 8× buckets-to-orders for the last ~2 %. Combined with `try_emplace` (an instruction-level win
-with no memory cost), this is the minmax point: most of the available speed for a modest, principled
-memory trade, with the hot path now bounded by the inherent red-black-tree price-level lookups and
-the hash-index probes that any correct implementation must pay.
+The **v0.1.0** column was produced by adding the same `apps/qsl-perfeval/main.cpp` and the two CMake
+target blocks to a `git worktree` checked out at the `v0.1.0` tag (its `MatchingEngine` API is
+identical: `register_symbol`, `new_limit`, `cancel`, `contains`), building the same `release` preset,
+and running the same commands. Both versions were measured on the same host; the
+frequency-independent counts (cycles, instructions, allocations per order) are the load-bearing
+comparison.
+
+## Tuning balance (why index load-factor 0.25, not lower)
+
+The index load factor was swept on the deep-book workload: 0.5 gives ~+10 %, 0.25 ~+18 %, 0.125 ~+20 %
+of the available speedup from that change. The curve flattens below ~0.25, so **0.25** captures
+essentially all of the win as a clean load-factor *policy* (memory scales with book size) rather than
+over-tuning a fixed bucket count or paying 8x buckets-to-orders for the last ~2 %. The hot path is now
+bounded by the inherent red-black-tree price-level lookups and the hash-index probes that any correct
+implementation must pay.
[#90]: https://github.com/div0rce/quant-systems-lab/issues/90
diff --git a/PROGRESS.md b/PROGRESS.md
index d5ba28d..366e98b 100644
--- a/PROGRESS.md
+++ b/PROGRESS.md
@@ -1,4 +1,4 @@
-# PROGRESS.md — Quant Systems Lab live state
+# PROGRESS.md. Quant Systems Lab live state
> **Single source of truth for current build state.** Claude Code updates this at the end of every milestone and before stopping mid-milestone.
@@ -20,31 +20,31 @@ Do not rely on prior chat memory.
## Current state
-- **Active milestone:** none — `v0.2.1` is the latest tag, but a post-v0.2.1 hardening + perf wave
- (12 PRs, #135–#146) has merged to `main` and is **unreleased**; it is being cut as **`v0.2.2`**
-- **Status:** ☑ `v0.2.1` published on top of `v0.2.0`; ☐ `v0.2.2` in preparation — security/robustness
+- **Active milestone:** none, `v0.2.1` is the latest tag, but a post-v0.2.1 hardening + perf wave
+ (12 PRs, #135, #146) has merged to `main` and is **unreleased**; it is being cut as **`v0.2.2`**
+- **Status:** ☑ `v0.2.1` published on top of `v0.2.0`; ☐ `v0.2.2` in preparation, security/robustness
hardening (decoder enum-domain rejection, network/CLI hardening, a real UBSan abort gate, OCaml
diff_report robustness) plus two measured order-book perf wins
- **Active branch:** `docs/post-v0.2.1-overhaul` (the v0.2.2 prep + full doc/artifact staleness sweep)
-- **Last completed milestone:** M49 — NIC offload and low-latency networking study (PR #124,
+- **Last completed milestone:** M49. NIC offload and low-latency networking study (PR #124,
d8c16b2). Releases since: `v0.2.0` (PR #127, ded6e80) and `v0.2.1` (FIX adapter #131, flamegraph
- #134, anchor sweep #129). Post-v0.2.1 unreleased work on `main`: #135–#146 (see Last action)
-- **Last completed docs sync:** this v0.2.2-prep overhaul — every `.md`/`.txt` audited against
+ #134, anchor sweep #129). Post-v0.2.1 unreleased work on `main`: #135, #146 (see Last action)
+- **Last completed docs sync:** this v0.2.2-prep overhaul, every `.md`/`.txt` audited against
current `main`; resume/release anchors, README, CHANGELOG, and all stale `results/*.txt`
provenance digests brought current to HEAD
- **Release:** `v0.1.0` (tag on 9857e1a), `v0.2.0` (tag on ded6e80), and `v0.2.1` (tag on the
release-PR merge, marked Latest) published as GitHub-only releases; `v0.2.2` prepared here, not yet
tagged; no packages published
-- **`make check` passing:** yes — `make check` 270/270 and `make asan` 270/270 (the latter now under
+- **`make check` passing:** yes, `make check` 270/270 and `make asan` 270/270 (the latter now under
the **real** UBSan abort gate from #142) on the bare-metal Apple M2 (aarch64) Fedora Asahi host on
2026-06-24
-- **Last action:** post-v0.2.1 hardening + perf wave merged to `main` as 12 scoped PRs (#135–#146),
+- **Last action:** post-v0.2.1 hardening + perf wave merged to `main` as 12 scoped PRs (#135, #146),
driven by a multi-round adversarial bug hunt (converged 5→2→1→0 confirmed) and flamegraph-guided
optimization. Security/robustness: reject out-of-domain enum bytes in the replay/protocol decoders
- (#136); network hardening — EINTR retry, accept fairness, connection cap, UDP send-error tracking,
+ (#136); network hardening. EINTR retry, accept fairness, connection cap, UDP send-error tracking,
transient-accept survival, and threaded/epoll fd-exhaustion handling (#137, #140, #143); CLI arg
validation so the tools reject malformed input instead of `std::terminate` (#141); the `asan`
- preset now sets `-fno-sanitize-recover=undefined` so UBSan actually fails CI — previously it ran in
+ preset now sets `-fno-sanitize-recover=undefined` so UBSan actually fails CI, previously it ran in
recover mode and exited 0 (#142); OCaml `diff_report` guards each fixture so one bad file cannot
abort the batch (#144). Perf (measured A/B): baseline price levels use `try_emplace` (~+5%, #138)
and the order-index hash caps its load factor at 0.25 (~+18.6%, #145); flamegraph regenerated
@@ -52,10 +52,10 @@ Do not rely on prior chat memory.
pass). `make check`/`make asan` 270/270.
- **Next action:** finish the `v0.2.2` overhaul (this branch): regenerate the remaining stale
`results/*.txt` artifacts, then cut the `v0.2.2` tag/release. After that, the highest-value
- remaining work is non-code and gated: issue #94 (independent external review — needs a human
- reviewer) and issue #90 (full cache-counter PMU evidence — needs a PMU microarchitecture that
+ remaining work is non-code and gated: issue #94 (independent external review, needs a human
+ reviewer) and issue #90 (full cache-counter PMU evidence, needs a PMU microarchitecture that
exposes cache events, e.g. x86_64).
-- **Blockers:** issue #90 is now a *cache-counter* PMU gap, not a host-access gap — this bare-metal
+- **Blockers:** issue #90 is now a *cache-counter* PMU gap, not a host-access gap, this bare-metal
Apple M2 exposes real `cycles`/`instructions`/`branches`/`branch-misses` but its PMU does not
implement `cache-references`/`cache-misses`; closing it needs a PMU microarchitecture that exposes
cache events (x86_64, or an ARM server core). Issue #94 remains open for independent external
@@ -144,7 +144,7 @@ Status key:
- [M6] `BookDelta`/`Snapshot` (full depth) deferred to the networked-feed/snapshot work.
- [M7] Log record framing: big-endian header (seq_no/record_type/logical_timestamp/payload_size) + opaque payload + FNV-1a u32 checksum; reuses the M2 byte helpers (`replay -> protocol -> core`).
- [M7] `read_log` is pure and bounds-safe; corruption yields a deterministic `LogError` (Truncated/BadChecksum/PayloadTooLarge) and returns intact records read so far.
-- [M7] File I/O uses C stdio (`fwrite`/`fread`) so byte buffers pass as `void*` — no `reinterpret_cast`. Writer opens in append mode only (no update-in-place).
+- [M7] File I/O uses C stdio (`fwrite`/`fread`) so byte buffers pass as `void*`, no `reinterpret_cast`. Writer opens in append mode only (no update-in-place).
- [M7] Payload is opaque to the log (e.g. a serialized protocol command frame); typed replay interpretation is M8.
- [M8] Recorded unit is a `Command` sum type including `RegisterSymbol`, so a log replays standalone (same names in order -> same SymbolIds).
- [M8] `EngineSnapshot` extended with per-level aggregates (`LevelView` bids/asks); replay equivalence compares snapshot (best bid/ask, levels, counts, last_seq) and the full emitted event sequence.
@@ -158,7 +158,7 @@ Status key:
- [M9] The only `reinterpret_cast` in the codebase is the POSIX `sockaddr*` cast; protocol serialization stays shift-based.
- [M9] No authentication; binds `127.0.0.1` only. Local simulator, not a venue (documented in docs/socket_gateway.md).
- [M10] Market data is exposed over UDP: one datagram per `MdTrade`/`MdTopOfBook`, encoded with the binary protocol; `decode_market_data` dispatches on header type.
-- [M10] Gap detection is a pure `SequenceTracker` (forward gaps only; duplicates/reorder ignored); UDP loss is detected, not recovered — no retransmit/snapshot channel.
+- [M10] Gap detection is a pure `SequenceTracker` (forward gaps only; duplicates/reorder ignored); UDP loss is detected, not recovered, no retransmit/snapshot channel.
- [M10] `UdpPublisher` is a `MarketDataSubscriber`, so the network feed reuses the M6 publisher unchanged; `inet_pton` is checked (invalid host -> unusable), applying the M9 bind-validation lesson.
- [M10] UDP unicast on `127.0.0.1` only; no multicast/auth/encryption (documented honestly).
- [M10] Wire-level UDP test covers out-of-sequence datagrams and gap detection through the real receive/decode/client path.
@@ -172,24 +172,24 @@ Status key:
- [M11] `results/latest.txt` records iterations, warmup, seed, units, dirty-tree flag, and an explicit "synthetic microbenchmark, not production throughput" caveat.
- [M11] README benchmark section leads with methodology/exclusions; no production/low-latency/exchange-grade claims; numbers labelled single-machine.
- [M11] `make fmt`/`fmt-check` now also cover `apps/` (benchmark and CLI code is formatting-checked).
-- [M12] Invariants verified over randomized deterministic flows (`generate_flow`, seeds 1–8): strictly-increasing sequence, no crossed book, executed ≤ submitted, positive quantities; plus focused rejected-cannot-rest, canceled-cannot-trade, and replay-stress (seeds 11/22/33 × 8000 orders) tests.
-- [M12] Protocol/session fuzz tests feed tens of thousands of random buffers to every decoder and the TCP session; decoders are non-throwing and bounds-safe, so the assertion is "no crash / no UB" — proven under the ASan+UBSan build.
+- [M12] Invariants verified over randomized deterministic flows (`generate_flow`, seeds 1-8): strictly-increasing sequence, no crossed book, executed ≤ submitted, positive quantities; plus focused rejected-cannot-rest, canceled-cannot-trade, and replay-stress (seeds 11/22/33 × 8000 orders) tests.
+- [M12] Protocol/session fuzz tests feed tens of thousands of random buffers to every decoder and the TCP session; decoders are non-throwing and bounds-safe, so the assertion is "no crash / no UB", proven under the ASan+UBSan build.
- [M12] Added a CI `sanitizers` job (`cmake --preset asan` + `ctest --preset asan`) so ASan/UBSan runs on every PR, not just locally.
- [M12] docs/invariants.md enumerates each invariant and where it is tested; reaffirms structural guarantees (integer-only prices, wall-clock-independent core). No new floating-point or clock dependency introduced.
- [M12] Added structure-aware mutated-frame fuzz tests so malformed-but-plausible inputs reach body decoders, not only header rejection.
- [M12] Added valid-frame chunking tests for TCP Session reassembly.
- [M12] Randomized invariant tests include non-vacuity guards so future generator changes cannot silently remove trades/book activity.
- [M13] README rewritten for a <60s read with a committed mermaid architecture diagram, clean-clone quickstart, demo section, and honest limitations; benchmark table cites results/latest.txt exactly (no new/unmeasured numbers).
-- [M13] Added scripts/demo.sh + `make demo`: deterministic replay/recovery then a loopback TCP gateway round-trip (readiness polling + cleanup trap). Gateway is unauthenticated/loopback-only — flagged in README and the script.
+- [M13] Added scripts/demo.sh + `make demo`: deterministic replay/recovery then a loopback TCP gateway round-trip (readiness polling + cleanup trap). Gateway is unauthenticated/loopback-only, flagged in README and the script.
- [M13] Expanded docs/recruiting_notes.md with conservative SWE + Linux résumé bullets, measured-only benchmark bullets (with exclusions caveat), and interview-defense notes. No production/profitability claims.
-- [M14] OCaml toolchain installed via Homebrew (ocaml 5.4.1 + dune 3.23.1); no opam, no third-party packages — verifier uses only the standard library.
+- [M14] OCaml toolchain installed via Homebrew (ocaml 5.4.1 + dune 3.23.1); no opam, no third-party packages, verifier uses only the standard library.
- [M14] Added a C++ `qsl-export-fixture` app that drives a deterministic flow through the gateway (low max_qty → real rejects) and writes a normalized textual event-log fixture. No engine code changed.
- [M14] OCaml verifier (`ocaml/`) re-derives replay invariants from the exported log (sequence monotonicity, positive trade qty, canceled-can't-trade, rejected-can't-rest, event-log/summary consistency). It is an independent cross-check, not a re-implementation of matching and not formal verification.
- [M14] Committed a generated valid fixture plus two hand-crafted violation fixtures so `dune runtest` proves the checker both passes clean logs and catches violations. CI runs a dedicated `ocaml-verifier` job (apt ocaml+dune; opam-free).
- [M14] OCaml verifier tracks OrderId lifetimes instead of treating rejected/canceled numeric IDs as globally dead (a later accept reuses an id legally); added valid-reuse fixtures alongside the violation fixtures.
- [M14] Malformed verifier fixtures now fail cleanly with exit 2 instead of uncaught parser exceptions.
- [M15] Added a differential-fixture exporter (`qsl-export-stream` + `replay::write_stream_fixture`) emitting a normalized command stream, engine events, command-scoped gateway rejections, and the full final per-symbol snapshot (best bid/ask, per-price level aggregates, order counts, last_seq, trade count). Schema in docs/differential_testing.md.
-- [M15] Reused the deterministic `generate_flow` (no new/random generation — that is M18); a low max_qty yields real rejections. Export is byte-deterministic per seed.
+- [M15] Reused the deterministic `generate_flow` (no new/random generation, that is M18); a low max_qty yields real rejections. Export is byte-deterministic per seed.
- [M15] M15 only exports + tests determinism/parseability; the independent OCaml replay engine (M16) and C++-vs-OCaml snapshot equality (M17) are deferred. M14 verifier (v1 event-log fixtures) is untouched and still green.
- [M15] Stream fixtures emit structured modify rejection outcomes instead of dropping rejected modify commands.
- [M15] Differential fixture reject lines are command-scoped so M17 can distinguish C++ risk rejection from no-op commands.
@@ -227,7 +227,7 @@ Status key:
- _none yet_
Measured by `make bench` (full metadata + raw output in `results/latest.txt`, which is the
-authoritative source). Hardware-, compiler-, and build-dependent — from one machine, not a
+authoritative source). Hardware-, compiler-, and build-dependent, from one machine, not a
production-latency claim.
- Run: aarch64 (Apple M2), GCC, Release, seed 42, Fedora Asahi Linux (synthetic, in-process;
@@ -239,7 +239,7 @@ production-latency claim.
- matching-engine flow apply: ~91 ns/command
- replay from command log: ~101 ns/command
- Note: these single-process micro-benchmarks hold a near-empty order index, so they do not exercise
- the deep-book steady state where the v0.2.2 engine wins land — `try_emplace` (~+5%, #138) and the
+ the deep-book steady state where the v0.2.2 engine wins land, `try_emplace` (~+5%, #138) and the
order-index load-factor cap (~+18.6%, #145) are measured on the `qsl-bench profile` workload.
---
@@ -261,11 +261,11 @@ This section adds Jane Street internship targeting context. Do not delete earlie
Primary:
-- Jane Street Software Engineer Internship, Hong Kong, December–February
+- Jane Street Software Engineer Internship, Hong Kong, December-February
Secondary:
-- Jane Street Linux Engineer Internship, Hong Kong, December–February
+- Jane Street Linux Engineer Internship, Hong Kong, December-February
Lower priority:
@@ -355,15 +355,14 @@ Lower priority:
`bench-diff`. All 15 artifacts report `Dirty inputs: no` with refreshed source digests, and
`git grep -nE '([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}' -- results/ | grep -v 'ff:ff:ff:ff:ff:ff'`
returns nothing. Closed the README/recruiting benchmark drift: synced the README benchmark table
- and `docs/recruiting_notes.md` to the regenerated `results/latest.txt` (single source of truth) —
- order book ~114 ns/op, protocol ~19 ns/op, gateway ~121 ns/op, matching ~99 ns/command, replay
+ and `docs/recruiting_notes.md` to the regenerated `results/latest.txt` (single source of truth), order book ~114 ns/op, protocol ~19 ns/op, gateway ~121 ns/op, matching ~99 ns/command, replay
~114 ns/command (the prior macOS-era ~126/39/270/121/132 ns numbers were stale). Added
`results/*.sqlite` to `.gitignore` so the local `qsl-results` MCP store is never committed. No
runtime C++ changed, so `make asan` / `make tsan` were not required. Also added
`tests/shell/test_qsl_common.sh` (registered in CTest; portable) covering both the MAC sanitization
(link/ether, permaddr, bridge_id/designated_root, group_address redacted; broadcast and the audit
leak-grep invariant preserved; wlx altname redacted) and the trailing-whitespace / blank-line
- trimming, so the security-relevant publish logic ships with tests — `make check` 241/241. This
+ trimming, so the security-relevant publish logic ships with tests, `make check` 241/241. This
supersedes CodeRabbit's PR #126, whose generated tests covered only trimming and were based on
`d8c16b2` (where `qsl_publish_artifact` does not yet exist, so #126 could not merge before #125);
#126 was closed as superseded. Do not merge from automation; the human squash-merges PR #125.
@@ -371,12 +370,12 @@ Lower priority:
Codex review findings left on `main` by PRs #127 and #128: (1) removed PROGRESS.md's stale
"Next action remains" block that still pointed `/resume` at the merged PR #125 on
`perf/linux-host-artifact-refresh`, replacing it with the v0.2.0 between-releases state (#94/#90
- gated); (2) brought AGENTS.md into sync with CLAUDE.md's v0.2.0 partial-PMU reframe — the
+ gated); (2) brought AGENTS.md into sync with CLAUDE.md's v0.2.0 partial-PMU reframe, the
constraints bullet, the "correct claim" block, and the "M29 perf evidence status" subsection no
longer call the artifacts "constrained Docker validation," and the stale
`perf/linux-host-artifact-refresh` follow-up line was updated in both AGENTS.md and CLAUDE.md to
the released state; (3) narrowed docs/perf_analysis.md so it no longer implies the Apple Blizzard
- (E-core) PMU carries live counts — the `apple_blizzard_pmu/...` rows read `` in
+ (E-core) PMU carries live counts, the `apple_blizzard_pmu/...` rows read `` in
`results/perf_stat_linux.txt` because the single-threaded benchmark stays on the Avalanche P-cores.
Docs/memory only; no code or artifacts changed.
- [2026-06-21] Issue #32 flamegraph profiling artifact (`perf/flamegraph-artifact`, stacked on the
@@ -399,7 +398,7 @@ Lower priority:
on the flamegraph branch). Added `include/qsl/protocol/fix.hpp` + `src/protocol/fix.cpp`: a
`tag=value` SOH-framed adapter over the SAME internal structs as the binary codec, with genuine
FIX framing (8 BeginString / 9 BodyLength / 35 MsgType / … / 10 mod-256 CheckSum) for the
- client→gateway order path — NewOrderSingle (35=D)→`NewOrder` and OrderCancelRequest
+ client→gateway order path. NewOrderSingle (35=D)→`NewOrder` and OrderCancelRequest
(35=F)→`CancelOrder`. Decoding is total/deterministic/`noexcept` (fixed field table,
`std::from_chars`, `string_view`; no heap on decode) and reports every malformed input through a
`FixError` taxonomy mirroring the binary `DecodeError`. Documented, deliberate simplifications:
@@ -428,7 +427,7 @@ Lower priority:
flamegraph #32, resume-anchor/PMU sweep), and brought the PROGRESS/HANDOFF release anchors current.
No code or benchmark artifacts change in the release PR itself. On squash-merge the human tags
`v0.2.1` on the merge commit and publishes the GitHub release. Do not merge from automation.
-- [2026-06-22] Second Codex review round on the open `v0.2.1` stack — addressed every remaining
+- [2026-06-22] Second Codex review round on the open `v0.2.1` stack, addressed every remaining
inline finding across PRs #129/#130/#131/#133. #131 (`fix.cpp`): the FIX envelope now requires
MsgType (35) as the first body field (rejecting `8/9/34/35/.../10`) and `tokenize` rejects
duplicate tags (no repeating groups), with a deterministic rejection test for each; the older
@@ -442,16 +441,16 @@ Lower priority:
release anchors and removed completed #29/#32 from every backlog list, synced AGENTS.md/CLAUDE.md
to the v0.2.1 released state, and refreshed this release-readiness audit to 263 tests. `make
check`/`make asan` 263/263. CodeScene MCP token still expired; CI is the authoritative gate.
-- [2026-06-24] Post-v0.2.1 hardening + perf wave (#135–#146), to be released as `v0.2.2`. Driven by a
+- [2026-06-24] Post-v0.2.1 hardening + perf wave (#135, #146), to be released as `v0.2.2`. Driven by a
multi-round adversarial bug hunt (4 rounds, converged 5→2→1→0 confirmed) plus flamegraph-guided
optimization. Security/robustness: reject out-of-domain enum bytes in the replay/protocol decoders
- (#136, `core::is_valid` for Side/TimeInForce/RejectReason); network hardening — EINTR retry in the
+ (#136, `core::is_valid` for Side/TimeInForce/RejectReason); network hardening. EINTR retry in the
TCP read/write path, accept fairness (epoll `max_accepts_per_tick`), connection cap
(`max_active_connections`), UDP send-error counter, transient-accept survival
(EINTR/ECONNABORTED), and threaded/epoll fd-exhaustion handling (#137, #140, #143); CLI arg
validation via `std::from_chars` so qsl-client/qsl-mdfeed/qsl-export-fixture reject malformed input
instead of `std::terminate`/silent port truncation (#141); the `asan` preset now sets
- `-fno-sanitize-recover=undefined` so UBSan **aborts** on a violation — it previously ran in recover
+ `-fno-sanitize-recover=undefined` so UBSan **aborts** on a violation, it previously ran in recover
mode and exited 0, so pure-UBSan defects passed CI green; the tree is UBSan-clean under the strict
gate (#142); OCaml `diff_report` guards each fixture so one malformed file cannot abort the batch
(#144). Perf (measured back-to-back A/B on the `qsl-bench profile` workload): baseline price levels
@@ -466,7 +465,7 @@ Lower priority:
- [2026-06-04] M35: PR #100 squash-merged to `main` as a86b701 after all CI jobs and review checks were green. M35 is now landed; original M36 NUMA remains deferred until the repository-health refactor analysis is completed or explicitly skipped by the human.
- [2026-06-04] Project-memory sync after M35: PR #101 squash-merged to `main` as 40f9249. It established the `CLAUDE.md` / `AGENTS.md` memory relationship, exact `Branch:` handling for `/start-milestone`, and the guard that repository-health planning happens before original M36 NUMA. No CodeScene/MCP analysis has started, no refactor milestones have been inserted, and no NUMA work has started.
- [2026-06-04] Post-merge project-memory sync PR #102 squash-merged to `main` as 7092423.
-- [2026-06-04] Repository-health refactor phase inserted after M35 (docs-only roadmap renumber, branch `docs/roadmap-health-refactor-insertion`). A CodeScene Code Health analysis (project 80913, via MCP) of all production and test files found eleven files below 9.0: production `epoll_server.cpp` 5.35, `pipeline.hpp` 7.13, `shrink.cpp` 8.15, `order_book.cpp` 8.55, `session.cpp` 8.99; tests `test_risk_gateway` 6.69, `test_order_book` 7.32, `test_pipeline` 8.28, `test_backpressure` 8.44, `test_invariants` 8.45, `test_matching_engine` 8.54. These became seven refactor milestones M36–M42: M36 epoll decomposition, M37 pipeline stage helpers (+ its concurrency tests), M38 shrinker passes, M39 order-book matching parameters, M40 engine test-suite consolidation (test-only; split out per the human), M41 session frame dispatch, M42 shared shell-script helpers (manual — CodeScene cannot score shell; the M35 deferred follow-up). The original networking/persistence roadmap shifted after those refactors; the later systems-roadmap audit extends future scope to M43–M49. Behavior-preserving refactors only; determinism/replay/differential/integer-tick pricing remain invariants. No implementation started.
+- [2026-06-04] Repository-health refactor phase inserted after M35 (docs-only roadmap renumber, branch `docs/roadmap-health-refactor-insertion`). A CodeScene Code Health analysis (project 80913, via MCP) of all production and test files found eleven files below 9.0: production `epoll_server.cpp` 5.35, `pipeline.hpp` 7.13, `shrink.cpp` 8.15, `order_book.cpp` 8.55, `session.cpp` 8.99; tests `test_risk_gateway` 6.69, `test_order_book` 7.32, `test_pipeline` 8.28, `test_backpressure` 8.44, `test_invariants` 8.45, `test_matching_engine` 8.54. These became seven refactor milestones M36-M42: M36 epoll decomposition, M37 pipeline stage helpers (+ its concurrency tests), M38 shrinker passes, M39 order-book matching parameters, M40 engine test-suite consolidation (test-only; split out per the human), M41 session frame dispatch, M42 shared shell-script helpers (manual. CodeScene cannot score shell; the M35 deferred follow-up). The original networking/persistence roadmap shifted after those refactors; the later systems-roadmap audit extends future scope to M43-M49. Behavior-preserving refactors only; determinism/replay/differential/integer-tick pricing remain invariants. No implementation started.
- [2026-06-04] M36: decomposed the Linux epoll transport while preserving public API and wire/session behavior. `src/gateway/epoll_server.cpp` now separates listener setup, accept outcome handling, ready-event dispatch, client fd-generation lookup, read/write outcomes, output-budget/backpressure handling, and re-arm/close decisions. CodeScene MCP score improved from 5.35 to 10.0 with no remaining review findings; final verification passed `git diff --check`, Docker Ubuntu Linux `test_epoll_gateway` (8 test cases / 280 assertions), `make check` 191/191, and `make asan` 191/191. PR #104 squash-merged as 0d2b97a.
- [2026-06-04] M37: started on `refactor/m37-threaded-pipeline-stage-helpers` from updated `main` (0d2b97a). Scope is behavior-preserving threaded-pipeline helper extraction: decompose `include/qsl/concurrency/pipeline.hpp` and reduce assertion duplication/nesting in `tests/concurrency/test_pipeline.cpp` and `tests/concurrency/test_backpressure.cpp`. DoD requires Code Health >= 9.0 for all three files plus `make check`, `make asan`, and `make tsan`.
- [2026-06-04] M37: completed behavior-preserving threaded-pipeline decomposition and opened draft PR #105. `ThreadedPipeline::run` now uses `PipelineRunOptions`, a run context, and named input/engine/output stage helpers; concurrency tests use shared assertion and producer/consumer helpers without changing scenarios. CodeScene re-score: `pipeline.hpp` 7.13 -> 10.0, `test_pipeline.cpp` 8.28 -> 10.0, `test_backpressure.cpp` 8.44 -> 10.0. PR review feedback fixed reused-probe accounting so shared `PipelineProbe` counters stay live/cumulative while each returned `PipelineResult` reports per-run backpressure deltas. Verification passed `git diff --check`, focused `test_pipeline` (12 cases / 629 assertions), `make check` 192/192, `make asan` 192/192, and `make tsan` 20/20.
@@ -479,7 +478,7 @@ Lower priority:
- [2026-06-04] M39: opened draft PR #107 from `refactor/m39-order-book-matching-parameters` after `git diff --check`, focused engine/replay tests, fixture/manifest checks, `dune runtest --root ocaml`, `make check` 192/192, `make asan` 192/192, and CodeScene pre-commit/change-set quality gates passed.
- [2026-06-04] M39: fixed Codex review feedback on PR #107 by making `find_level` return only the requested side and adding `quantity_at is side-specific at the same price` coverage. Post-fix verification passed focused `test_order_book` (20 cases / 87 assertions), `make check` 193/193, `make asan` 193/193, and CodeScene score remained 9.68.
- [2026-06-05] M39: PR #107 squash-merged to `main` as 880fbc7.
-- [2026-06-05] M40: consolidated the four sub-9.0 engine/risk test suites to Code Health >= 9.0 — `test_risk_gateway.cpp` 6.69 -> 10.0, `test_order_book.cpp` 7.78 -> 9.09, `test_invariants.cpp` 8.45 -> 10.0, `test_matching_engine.cpp` 8.54 -> 9.38 — by extracting behavior-named helpers (`expect_reject` / `expect_accepted_one` / `expect_trade` / `expect_top` / `expect_strictly_increasing` / `expect_storage_equivalent`, a shared `Gateway` fixture, and per-invariant `classify_command` / `check_events` / `check_book_invariants`), de-duplicating near-identical `TEST_CASE`s, and flattening the deep property-loop nesting in the invariants suite. Test-only: `git diff --stat main` touches only the four test files + `PROGRESS.md` (no `include/` or `src/`). All `TEST_CASE` names/scenarios, deterministic seeds (1-8, 11/22/33, 1-6), and non-vacuity guards preserved; a few previously-partial trade/reject assertions were strengthened to the full verified contract — one (`modify emits OrderModified and any resulting trades`) was caught by `make check` as a wrong quantity (5 vs the post-reduce 3) and corrected. `make check` 193/193 and `make asan` green.
+- [2026-06-05] M40: consolidated the four sub-9.0 engine/risk test suites to Code Health >= 9.0, `test_risk_gateway.cpp` 6.69 -> 10.0, `test_order_book.cpp` 7.78 -> 9.09, `test_invariants.cpp` 8.45 -> 10.0, `test_matching_engine.cpp` 8.54 -> 9.38, by extracting behavior-named helpers (`expect_reject` / `expect_accepted_one` / `expect_trade` / `expect_top` / `expect_strictly_increasing` / `expect_storage_equivalent`, a shared `Gateway` fixture, and per-invariant `classify_command` / `check_events` / `check_book_invariants`), de-duplicating near-identical `TEST_CASE`s, and flattening the deep property-loop nesting in the invariants suite. Test-only: `git diff --stat main` touches only the four test files + `PROGRESS.md` (no `include/` or `src/`). All `TEST_CASE` names/scenarios, deterministic seeds (1-8, 11/22/33, 1-6), and non-vacuity guards preserved; a few previously-partial trade/reject assertions were strengthened to the full verified contract, one (`modify emits OrderModified and any resulting trades`) was caught by `make check` as a wrong quantity (5 vs the post-reduce 3) and corrected. `make check` 193/193 and `make asan` green.
- [2026-06-05] M40: fixed PR #108 review feedback by collapsing `expect_trade` expected fields into an `ExpectedTrade` value object, clearing the CodeScene excess-arguments finding while preserving the same trade assertions. Also reconciled stale M39/M40 `PROGRESS.md` anchors so PR #108 is the single current action. Verification passed focused matching-engine tests, `git diff --check`, `make check` 193/193, `make asan` 193/193, and CodeScene score for `test_matching_engine.cpp` is 9.38.
- [2026-06-05] M40: PR #108 squash-merged to `main` as b939730.
- [2026-06-05] M41: started on `refactor/m41-session-frame-dispatch` from updated `main` (b939730). Baseline CodeScene for `src/gateway/session.cpp`: 8.99; finding is `Session::process_frame` complexity 15. Pre-refactor `./build/dev/tests/test_session` passed 11 cases / 36 assertions.
@@ -577,7 +576,7 @@ Lower priority:
later valid records. `BadChecksum` is torn only when the failing frame ends exactly at end of
file; `PayloadTooLarge` headers are never trusted. `repair_log_file` truncates torn tails to the
last valid record boundary and fsyncs the truncation; it refuses `Corrupt` logs because truncating
- mid-file damage would silently discard acknowledged records beyond it — a human decision, not
+ mid-file damage would silently discard acknowledged records beyond it, a human decision, not
automation. The fsync-mode contract: an acknowledged append is never removed by tail repair
unless the storage stack lied.
- [2026-06-11] M45: `qsl-replay` gains `recover [--repair]` and
@@ -622,13 +621,12 @@ Lower priority:
repair-only-provably-torn decisions. M46 will measure full-replay recovery cost before any
segmentation/snapshot design.
- [2026-06-11] PR #117 squash-merged to `main` as d10bfb0, completing M45. M46 started on
- `feat/m46-recovery-benchmarking`. Scope: recovery benchmarking — replay performance, snapshot
+ `feat/m46-recovery-benchmarking`. Scope: recovery benchmarking, replay performance, snapshot
restoration performance, and recovery-objective framing. Constraints: benchmarks generated only
by committed scripts with `Provenance version: 1` metadata and dirty-inputs state; docs must
state exactly what recovery objective was measured; no production-recovery or RTO claims beyond
the measured synthetic workloads.
-- [2026-06-12] M46: added `OrderBook::resting_orders()` / `MatchingEngine::resting_orders(symbol)`
- — deterministic priority-order enumeration of resting state (bids best-first then asks, FIFO
+- [2026-06-12] M46: added `OrderBook::resting_orders()` / `MatchingEngine::resting_orders(symbol)`, deterministic priority-order enumeration of resting state (bids best-first then asks, FIFO
within level) across all three storage modes, with `Order::operator==`. Re-adding the sequence
into an empty book reproduces levels and intra-level time priority exactly; unit tests cover
per-mode enumeration, partial-fill/cancel/priority-losing-modify effects, and a generated-flow
@@ -640,12 +638,12 @@ Lower priority:
`recover_log_file` read+verify+classify, replay decode+apply into a fresh engine, and the
combined full restart; plus a benchmark-only in-memory snapshot-restoration prototype
(capture resting state, rebuild book) at the flow's live state and at controlled synthetic
- depths (1k/10k/50k resting orders), because the realistic flow leaves only ~24–37 resting
+ depths (1k/10k/50k resting orders), because the realistic flow leaves only ~24-37 resting
orders and per-order timings on such small books are noisy. Every phase self-verifies against
the reference snapshot and the harness aborts rather than report numbers from a wrong rebuild.
The committed artifact (clean declared inputs, `Dirty inputs: no`) shows full-replay restart
- cost linear in history (237–286 ns/command end-to-end on this host; ~23 ms at 80k commands)
- while book rebuild is linear in live state (109–187 ns/order; ~5.4 ms at 50k resting orders).
+ cost linear in history (237-286 ns/command end-to-end on this host; ~23 ms at 80k commands)
+ while book rebuild is linear in live state (109-187 ns/order; ~5.4 ms at 50k resting orders).
Explicitly framed: restart cost (RTO-style) measured here; loss bounds (RPO-style) belong to
the M45 durability modes; prototype numbers are an in-memory lower bound (no serialization,
disk I/O, or tail replay); no production recovery-time claim.
@@ -658,7 +656,7 @@ Lower priority:
source in an artifact-only commit so its `Source digest` matches the committed generator.
- [2026-06-12] PR #118 squash-merged to `main` as aeba72c, completing M46. M47 started on
`feat/m47-contiguous-order-book-storage`. Scope: flat/contiguous order-book storage study
- against baseline, PMR pooled, and intrusive pooled modes — explicit symbol/price-domain
+ against baseline, PMR pooled, and intrusive pooled modes, explicit symbol/price-domain
assumptions, replay/differential equivalence (identical event streams, `EngineSnapshot`,
`last_seq`), engine-level benchmark artifacts from committed scripts, cache-locality analysis
only where tooling supports it, and honest documentation of negative/neutral results. No
@@ -704,7 +702,7 @@ Lower priority:
direct-book regression; focused storage/gateway tests passed, then `git diff --check`,
`make check` 230/230, and `make asan` 230/230 passed locally.
- [2026-06-12] M47 PR #119 CodeRabbit follow-up: applied the same residual preflight to
- `IntrusiveStore::add_limit` — a direct `OrderBook{Storage::IntrusivePooled}` caller that
+ `IntrusiveStore::add_limit`, a direct `OrderBook{Storage::IntrusivePooled}` caller that
partially crossed a GTC order and then hit pool exhaustion previously dropped the remainder
despite the rest-the-remainder contract; it now refuses the whole order via `can_store_limit`
before matching (engine/gateway callers were already pre-gated, so their behavior is
@@ -719,7 +717,7 @@ Lower priority:
- [2026-06-12] M47 PR #119 review round 3 fixes (docs/roadmap only; committed artifact numbers
unchanged): corrected a benchmark-claim error in `docs/pool_backed_storage.md` (it named
contiguous the fastest row, but the committed artifact has PMR 209.6 < contiguous 222.4 <
- baseline 273.7 < intrusive 373.0 ns/cmd — PMR is fastest, contiguous second) and fixed stale
+ baseline 273.7 < intrusive 373.0 ns/cmd. PMR is fastest, contiguous second) and fixed stale
`MILESTONES.md` statuses so resume/finish workflows route to the right milestone: M45 is merged
via PR #117 (not PR #119), M44 (#115) and M46 (#118) are also merged, and M47 is the active
PR #119.
@@ -742,7 +740,7 @@ Lower priority:
timed the full per-replay `run_once`, including `MatchingEngine` construction and the
`RegisterSymbol` prefix. Book construction is eager (`OrderBook` builds its `IntrusiveStore` /
`ContiguousStore` in its constructor), so for the pooled modes that prefix runs `OrderPool` /
- `RawPool` free-list initialization over 65536 slots per book — a fixed per-run setup cost that was
+ `RawPool` free-list initialization over 65536 slots per book, a fixed per-run setup cost that was
charged to per-command time and amortized over only ~5k commands, scaling with symbol count and
inflating the intrusive mode most. The fix (`bench_storage.cpp`, `run_storage_benchmarks.sh`)
applies engine construction, the registration prefix, and the end-of-run snapshot outside the
@@ -764,7 +762,7 @@ Lower priority:
path but not the *characterization* path, leaving the shape line describing a different sequence
than the rows. Fix: `characterize` now applies the registration prefix unobserved and observes
only the trading range, sharing one `registration_prefix_len` boundary with `apply_registration`
- and counting probes with the same `should_probe` predicate over the same range — so `commands` and
+ and counting probes with the same `should_probe` predicate over the same range, so `commands` and
`top_probe_calls` on the shape line match the per-run `cmds`/`probes/run` by construction. Audited
the class: `top_probe_calls` and `commands` were the only live instances (`events`/`resting`/
`last_seq` are immune because `RegisterSymbol` emits no events and rests nothing), so this closes
@@ -784,33 +782,33 @@ Lower priority:
informational commit cf0396f), tables rebuilt from that one artifact; ranking unchanged.
- [2026-06-05] Repo review policy: added `.coderabbit.yaml` to disable CodeRabbit docstring coverage because this repo uses sparse "why" comments rather than blanket function docstrings. CodeRabbit Infer is disabled because the trusted C++ analysis path is CMake/CI/sanitizers/CodeScene and CodeRabbit's Infer run currently lacks the compile context needed for useful C++ analysis.
- [2026-06-04] Local MCP/tooling memory: Codex client has CodeScene, Playwright, filesystem, sequential-thinking, memory, Docker, Context7, and node_repl MCP servers configured. Postgres and Perplexity MCP servers are intentionally not configured; do not assume database or Perplexity access unless the human configures them later.
-- [2026-06-02] M34: started after M33 (#97) squash-merged (commit fe8679a). Scope: Linux `epoll` gateway architecture prototype only — event-driven multi-client readiness, nonblocking accept/read/write behavior, deterministic `Session` semantics preserved. Do not start M35 load/socket-pressure testing and do not make production-capacity claims.
+- [2026-06-02] M34: started after M33 (#97) squash-merged (commit fe8679a). Scope: Linux `epoll` gateway architecture prototype only, event-driven multi-client readiness, nonblocking accept/read/write behavior, deterministic `Session` semantics preserved. Do not start M35 load/socket-pressure testing and do not make production-capacity claims.
- [2026-06-02] M34: added `EpollServer`, a Linux-only event-driven transport with one `epoll` loop, nonblocking `accept4`/read/write, per-client outbound buffers, and one existing deterministic `Session` per connection. `qsl-gateway --epoll` opts in; the blocking `TcpServer` remains the default.
- [2026-06-02] M34: epoll tests are platform-scoped. macOS verifies unsupported mode; Docker Ubuntu Linux verifies availability, invalid bind-host rejection, and two simultaneous loopback clients handled by one event loop without thread-per-connection design.
- [2026-06-02] M34: local verification passed: `make check` 190/190, `make asan` 190/190, `git diff --check`, and Docker Ubuntu Linux `test_epoll_gateway` 3 tests / 36 assertions.
- [2026-06-02] M34: opened draft PR #98; do not merge from the automation side.
- [2026-06-03] M34: Codex review of #98 iterated several rounds (CI green throughout), each fix verified on macOS and Linux Docker as noted in the current-state block: read-backpressure; `--epoll` flag-or-port parsing with whole-token/range/duplicate validation; a soft high-water mark (stop reading) plus a hard outbound cap; O(n²) front-erase flush replaced with a write offset; `EINTR`-on-send retry; survival of transient `accept4` errors (`ECONNABORTED`/pending network errors) instead of tearing down the loop; bounded high-fanout `NewOrder` response budgeting before gateway mutation; `EPOLLERR` immediate close; `EPOLLHUP` drain of already-readable bytes before close; no `EPOLLIN` re-arm once a session is closing; fd-generation checks for stale events after fd reuse; and queued-reply preservation when an over-cap frame follows earlier accepted frames in the same read. Issue #99 was opened as a broader follow-up for shared streaming/byte-budgeted response generation outside the epoll-specific bounded path and is addressed later by PR #111.
- [2026-06-02] M33: PR #97 squash-merged (commit fe8679a); CI passed all 6 jobs and Codex review found no major issues. M33 delivered deterministic pipeline scheduling perturbation, opt-in repeated concurrency stress, and docs framing TSan/perturbation/stress as evidence rather than proof.
-- [2026-06-02] M33: started after M32 (#96) squash-merged (commit f122ee8). Scope: advanced concurrency validation only — deterministic scheduling perturbation and/or longer stress modes, stronger concurrency methodology docs, opt-in long-running/Linux checks where appropriate. Do not claim proof; TSan and stress tests remain dynamic evidence over executed schedules.
+- [2026-06-02] M33: started after M32 (#96) squash-merged (commit f122ee8). Scope: advanced concurrency validation only, deterministic scheduling perturbation and/or longer stress modes, stronger concurrency methodology docs, opt-in long-running/Linux checks where appropriate. Do not claim proof; TSan and stress tests remain dynamic evidence over executed schedules.
- [2026-06-02] M33: added deterministic `PipelinePerturbation` yield hooks to the threaded pipeline and a regression test that compares perturbed pipeline output against the single-threaded reference across seeded property flows, queue capacities, and per-stage yield patterns.
- [2026-06-02] M33: added `make concurrency-stress` / `scripts/concurrency_stress.sh` as an opt-in repeated concurrency-label test loop. Normal CI remains non-flaky; longer local/Linux runs are documented through explicit knobs rather than hidden in the default gate.
- [2026-06-02] M33: local verification passed: `make check` 189/189, `make asan` 189/189, `make tsan` 19/19 concurrency tests, `QSL_CONCURRENCY_STRESS_LOOPS=2 make concurrency-stress` 2/2 loops, `bash -n scripts/concurrency_stress.sh`, and `git diff --check`.
- [2026-06-02] M33: opened draft PR #97 and triggered `@codex review`; do not merge from the automation side.
- [2026-06-02] M33: PR #97 CI passed all 6 jobs (`build-test`, `sanitizers`, `thread-sanitizer`, `determinism`, `differential-sweep`, `ocaml-verifier`) and Codex review found no major issues.
- [2026-06-02] M32: PR #96 squash-merged (commit f122ee8); Codex review found no major issues and CI passed all jobs. M32 delivered PMR-backed order-book node allocation, an engine-level storage benchmark, docs/ADR, and issue #95 for the later intrusive/custom-node `OrderPool` storage path now handled by `feat/close-storage-flow-tcp-followups`.
-- [2026-06-02] M32: started after M31 (#93) squash-merged (commit b7926ac). Corrected scope: integrate pool-backed order-book node allocation using PMR, informed by the M28 allocator experiment, and measure baseline-vs-pool-backed engine-level workloads — not another allocator microbenchmark. Direct `OrderPool` order-book integration was deferred because the current `std::list` design allocated implementation-defined list nodes, not bare `engine::Order` objects; the later #95 follow-up handles the intrusive/custom-node path separately. Matching must remain deterministic (replay/differential tests stay green); no storage-architecture claim beyond what the committed measured artifact supports; document even a negative result.
+- [2026-06-02] M32: started after M31 (#93) squash-merged (commit b7926ac). Corrected scope: integrate pool-backed order-book node allocation using PMR, informed by the M28 allocator experiment, and measure baseline-vs-pool-backed engine-level workloads, not another allocator microbenchmark. Direct `OrderPool` order-book integration was deferred because the current `std::list` design allocated implementation-defined list nodes, not bare `engine::Order` objects; the later #95 follow-up handles the intrusive/custom-node path separately. Matching must remain deterministic (replay/differential tests stay green); no storage-architecture claim beyond what the committed measured artifact supports; document even a negative result.
- [2026-06-02] M32: implemented the scoped PMR path: `OrderBook::Storage::{Baseline,Pooled}`, per-book `std::pmr::unsynchronized_pool_resource`, PMR list/map/unordered_map node allocation, and `MatchingEngine(OrderBook::Storage)` propagation via `books_.try_emplace(id, book_storage_)`. Baseline remains the default.
- [2026-06-02] M32: added a generated-flow equivalence invariant test proving baseline and pooled storage produce identical per-command event streams, final `EngineSnapshot`, and `last_seq`; the test includes non-vacuity guards for real trades and resting liquidity.
- [2026-06-02] M32: added engine-level storage benchmarking (`make bench-storage`, `qsl-bench storage`, `scripts/run_storage_benchmarks.sh`, `results/pool_backed_storage.txt`). This compares baseline engine storage against PMR pooled node allocation; it is not an isolated allocator-only benchmark and not a production-latency claim.
- [2026-06-02] M31: PR #93 squash-merged (commit b7926ac); Codex auto-reviewed on open and reacted 👍 with no findings (docs-only, deliberately honest/conservative). The external-review request was then opened as GitHub issue #94 (labels backlog/documentation/help wanted), satisfying the "review request issue opened" alternative in the M31 DoD; `review_feedback.md` still records that no external feedback has been received yet.
-- [2026-06-02] M31: started after M30 (#92) squash-merged (commit 3f88a1f). Scope is the external-review package only: a review-request checklist, a review-feedback template, an optional issue template, and a small README link. Non-negotiable: no fabricated endorsements and no claim that review has happened until it has — `review_feedback.md` states plainly that no external review has occurred yet.
+- [2026-06-02] M31: started after M30 (#92) squash-merged (commit 3f88a1f). Scope is the external-review package only: a review-request checklist, a review-feedback template, an optional issue template, and a small README link. Non-negotiable: no fabricated endorsements and no claim that review has happened until it has, `review_feedback.md` states plainly that no external review has occurred yet.
- [2026-06-02] M30: PR #92 squash-merged (commit 3f88a1f) after Codex review converged clean ("Didn't find any major issues") across four rounds of P2 fixes, each verified (happy + negative paths) in containerized Linux and on macOS before re-requesting review: (1) `profile_gateway_io.sh` fails loudly (no artifact) when strace captures no syscall summary; (2) `socket_stress.sh` reports loss as `published − received` (captures end-of-burst tail drops the interior sequence-gap counter misses); (3) `profile_gateway_io.sh` Pass 2 launches the gateway *under* strace (launch form) so tracing works under Yama `ptrace_scope=1` without `CAP_SYS_PTRACE`, stopping the traced gateway with SIGTERM/SIGKILL (never strace) so the `-c` report is flushed; (4) both scripts fail loudly (no artifact) on port conflicts / helper failures.
-- [2026-06-02] M30: started after PR #91 (post-M29 docs sync) squash-merged to main (commit 86443f0). Scope is Linux kernel/socket-path profiling + socket hardening only; M31–M41 are not implemented here. Also corrected the stale HANDOFF.md "Current handoff" block (it still said "M29 is PR #89 and should land"; the review bot flagged this on #91, which merged before the comment was addressed).
+- [2026-06-02] M30: started after PR #91 (post-M29 docs sync) squash-merged to main (commit 86443f0). Scope is Linux kernel/socket-path profiling + socket hardening only; M31-M41 are not implemented here. Also corrected the stale HANDOFF.md "Current handoff" block (it still said "M29 is PR #89 and should land"; the review bot flagged this on #91, which merged before the comment was addressed).
- [2026-06-02] M30: added an optional `SO_RCVBUF` knob to `UdpFeedClient(port, recv_buffer_bytes=0)` that requests the buffer and reads back the kernel-granted size via `getsockopt`; `qsl-mdfeed subscribe` gains `[rcvbuf_bytes]`, `qsl-mdfeed publish` gains `[orders]`, and the subscriber idle-breaks after a few empty receives so burst experiments terminate promptly. Backward-compatible (defaulted param); unit-tested for monotonic effective-size growth.
-- [2026-06-02] M30: `scripts/socket_stress.sh` (`make socket-stress`) is portable (Linux+macOS) — UDP burst + receive-buffer experiment over loopback, multi-trial. Measured on this macOS host: a 2 KiB buffer drops datagrams under a ~16.9k-datagram burst while the OS default (~768 KiB) and an 8 MiB buffer lose nothing. Loss is reported as `published − received` (per-trial, varies run-to-run), not a fixed number.
-- [2026-06-02] M30 (Codex review on PR #92, two rounds): (1) fixed `profile_gateway_io.sh` to capture strace's exit status and require a real syscall summary, failing loudly (exit 3, no artifact) when strace cannot attach — previously a blocked-ptrace host could write a successful-looking artifact with an empty syscall section (commit 551a9c5). (2) fixed `socket_stress.sh` to report loss as `published − received` instead of only the `SequenceTracker` gap count: an end-of-burst tail drop has no later datagram to reveal a gap, so the gap counter alone undercounts loss; the interior gap count is kept as a secondary signal. Both were P2 findings.
-- [2026-06-02] M30: `scripts/profile_gateway_io.sh` (`make profile-io`) is Linux-only (skips with exit 2 elsewhere, like the M29 perf scripts). It backgrounds the gateway directly (owns its PID), reads rusage from `/proc//{stat,status}` (Pass 1) and attaches `strace -f -c` (Pass 2) — no GNU time / pkill dependency, with SIGKILL escalation so it cannot hang. The committed `results/socket_profile_loopback.txt` was generated in containerized Linux (Docker, `--cap-add=SYS_PTRACE`): user-space matching CPU fell below the 10 ms tick while the measurable CPU was the kernel/socket path, with ~1 voluntary context switch per connection and a syscall mix of exactly accept/read/sendto/close.
-- [2026-06-02] M30: both socket artifacts are loopback-only, constrained-environment evidence (ADR 0008), same honesty policy as ADR 0007 for perf; the gateway profile carries `Dirty tree: yes` because it was generated mid-development in a container — regenerate on a clean Linux checkout for a clean-tree version. No NIC/driver/real-network or production-capacity claim is made.
+- [2026-06-02] M30: `scripts/socket_stress.sh` (`make socket-stress`) is portable (Linux+macOS). UDP burst + receive-buffer experiment over loopback, multi-trial. Measured on this macOS host: a 2 KiB buffer drops datagrams under a ~16.9k-datagram burst while the OS default (~768 KiB) and an 8 MiB buffer lose nothing. Loss is reported as `published − received` (per-trial, varies run-to-run), not a fixed number.
+- [2026-06-02] M30 (Codex review on PR #92, two rounds): (1) fixed `profile_gateway_io.sh` to capture strace's exit status and require a real syscall summary, failing loudly (exit 3, no artifact) when strace cannot attach, previously a blocked-ptrace host could write a successful-looking artifact with an empty syscall section (commit 551a9c5). (2) fixed `socket_stress.sh` to report loss as `published − received` instead of only the `SequenceTracker` gap count: an end-of-burst tail drop has no later datagram to reveal a gap, so the gap counter alone undercounts loss; the interior gap count is kept as a secondary signal. Both were P2 findings.
+- [2026-06-02] M30: `scripts/profile_gateway_io.sh` (`make profile-io`) is Linux-only (skips with exit 2 elsewhere, like the M29 perf scripts). It backgrounds the gateway directly (owns its PID), reads rusage from `/proc//{stat,status}` (Pass 1) and attaches `strace -f -c` (Pass 2), no GNU time / pkill dependency, with SIGKILL escalation so it cannot hang. The committed `results/socket_profile_loopback.txt` was generated in containerized Linux (Docker, `--cap-add=SYS_PTRACE`): user-space matching CPU fell below the 10 ms tick while the measurable CPU was the kernel/socket path, with ~1 voluntary context switch per connection and a syscall mix of exactly accept/read/sendto/close.
+- [2026-06-02] M30: both socket artifacts are loopback-only, constrained-environment evidence (ADR 0008), same honesty policy as ADR 0007 for perf; the gateway profile carries `Dirty tree: yes` because it was generated mid-development in a container, regenerate on a clean Linux checkout for a clean-tree version. No NIC/driver/real-network or production-capacity claim is made.
- [2026-06-02] M30: deferred the optional `epoll` adapter to M34 (multi-client pressure to M35) and `io_uring` to discuss-only. Rationale: `epoll` is Linux-only and cannot be compiled or tested on the macOS development host, and committing untested platform-specific code violates the no-untested-C++ bar; M30 profiles and hardens the existing one-connection-at-a-time gateway instead of rewriting it.
- [2026-06-01] M29: started after M28 merged (PR #88, squash commit 03b4d9a). M29 scope is Linux `perf` evidence only: scripts/docs/artifacts for `perf stat` and `perf record/report`; no engine optimization and no M30 socket profiling.
- [2026-06-01] M29: `make perf-stat` and `make perf-record` fail before building on non-Linux hosts; Linux scripts capture hardware/kernel/compiler/perf/build/git/dirty-tree metadata and keep generated M29 result files out of the dirty-tree calculation.
@@ -832,21 +830,21 @@ Lower priority:
- [2026-06-01] M28: kept order-book storage unchanged; the pool is an allocation experiment for future storage decisions, not a semantic refactor or an end-to-end engine latency claim.
- [2026-06-01] M28 review fix: changed `OrderPool` from default-constructed `std::array` slots to raw aligned storage with explicit `std::construct_at` on acquire and `std::destroy_at` on release/reset/destructor, preserving per-acquire object lifetime while keeping exhaustion explicit and avoiding heap fallback.
- [2026-06-01] M27: started after M26 merged (PR #86, squash commit 8ec4967). Reset the env-designated branch to `origin/main` rather than create a `feat/m27-*` branch (the environment forbids pushing to a different branch); the branch now carries only M27.
-- [2026-06-01] M27: ThreadSanitizer is a *separate* sanitizer preset because `-fsanitize=thread` is incompatible with the ASan preset's `-fsanitize=address`; `cmake/Sanitizers.cmake` errors if both are enabled at once. Labeled the three `tests/concurrency/` executables `concurrency` so `make tsan` runs only the genuinely multithreaded tests (TSan on single-threaded tests adds nothing). TSan is a data-race *correctness* gate, not a performance tool — no benchmark numbers are collected under it.
-- [2026-06-01] M26: addressed a Codex review on PR #86 — the "backpressure occurred (spins >= 1)" assertions were timing-dependent (`std::this_thread::yield()` can let the consumer keep pace, so a spin count of 0 is legitimately possible on a fast/lightly-loaded run). Replaced them with a deterministic barrier: added an optional `PipelineProbe` (live spin counters the pipeline bumps as backpressure happens) and a gated-consumer test that blocks the downstream stage, waits on the probe until BOTH queues have provably back-pressured, then releases — and made the lag/saturation tests correctness-only. Confirmed non-flaky over 50 repeated runs; `make check` 182/182, `make asan` 182/182.
-- [2026-06-01] M26: reconciled stale PROGRESS state — M25 was already merged (PR #85, commit 9360364 on main), not "ready for PR"; M24 (#84) + M25 (#85) confirmed merged, satisfying the M26 prerequisites.
+- [2026-06-01] M27: ThreadSanitizer is a *separate* sanitizer preset because `-fsanitize=thread` is incompatible with the ASan preset's `-fsanitize=address`; `cmake/Sanitizers.cmake` errors if both are enabled at once. Labeled the three `tests/concurrency/` executables `concurrency` so `make tsan` runs only the genuinely multithreaded tests (TSan on single-threaded tests adds nothing). TSan is a data-race *correctness* gate, not a performance tool, no benchmark numbers are collected under it.
+- [2026-06-01] M26: addressed a Codex review on PR #86, the "backpressure occurred (spins >= 1)" assertions were timing-dependent (`std::this_thread::yield()` can let the consumer keep pace, so a spin count of 0 is legitimately possible on a fast/lightly-loaded run). Replaced them with a deterministic barrier: added an optional `PipelineProbe` (live spin counters the pipeline bumps as backpressure happens) and a gated-consumer test that blocks the downstream stage, waits on the probe until BOTH queues have provably back-pressured, then releases, and made the lag/saturation tests correctness-only. Confirmed non-flaky over 50 repeated runs; `make check` 182/182, `make asan` 182/182.
+- [2026-06-01] M26: reconciled stale PROGRESS state. M25 was already merged (PR #85, commit 9360364 on main), not "ready for PR"; M24 (#84) + M25 (#85) confirmed merged, satisfying the M26 prerequisites.
- [2026-06-01] M26: the threaded pipeline is a new header-only `qsl::concurrency::ThreadedPipeline` (`include/qsl/concurrency/pipeline.hpp`). The engine thread is the **sole owner** of `MatchingEngine`+`OrderGateway`, so the concurrency boundary is a value hand-off and deterministic matching semantics are unchanged (no engine code modified).
-- [2026-06-01] M26: the downstream publisher/log stage consumes self-contained `ProcessedCommand` records and never reads the engine — the M6 publisher derives top-of-book from the engine, which would race the engine thread, so keeping the sink engine-free makes "publisher lag cannot corrupt engine state" structurally true, not merely tested.
-- [2026-06-01] M26: both hand-off queues use the lossless spin/yield policy (orders + event-log/feed records never dropped) and drain-then-stop shutdown via per-stage `atomic` done-flags; rings live on `run()`'s stack and are joined before return (SPSC lifetime bracket). Determinism is proven by asserting the threaded result equals a single-threaded reference *and* a replay of the concurrently-written command log, across seeds and queue capacities (2..4096) — capacity/timing change only backpressure, never the result.
-- [2026-06-01] M26: branch-name deviation — the managed remote environment mandates development on `claude/serene-fermi-rhuFJ` and forbids pushing to a different branch, so M26 keeps the milestone intent (one branch, one PR titled `feat: add threaded gateway-engine-feed pipeline prototype`) but not the `feat/mNN-slug` branch name.
-- [2026-06-01] M25: split the concurrency docs — kept the high-level model (ownership lifecycle, visibility, backpressure, shutdown, limits) in `docs/concurrency_model.md` and moved the C++ memory-model deep dive (ordering table, happens-before proof both directions, wait-free-by-construction argument) into a new `docs/memory_ordering.md`, cross-linked, to avoid one drifting from the other.
-- [2026-06-01] M25: justified the "wait-free per operation" / "lock-free" claim by construction (bounded step count, no loops, no CAS) rather than dropping it, and explicitly scoped it to the queue op — a caller spinning on backpressure is application-level and not wait-free.
+- [2026-06-01] M26: the downstream publisher/log stage consumes self-contained `ProcessedCommand` records and never reads the engine, the M6 publisher derives top-of-book from the engine, which would race the engine thread, so keeping the sink engine-free makes "publisher lag cannot corrupt engine state" structurally true, not merely tested.
+- [2026-06-01] M26: both hand-off queues use the lossless spin/yield policy (orders + event-log/feed records never dropped) and drain-then-stop shutdown via per-stage `atomic` done-flags; rings live on `run()`'s stack and are joined before return (SPSC lifetime bracket). Determinism is proven by asserting the threaded result equals a single-threaded reference *and* a replay of the concurrently-written command log, across seeds and queue capacities (2..4096), capacity/timing change only backpressure, never the result.
+- [2026-06-01] M26: branch-name deviation, the managed remote environment mandates development on `claude/serene-fermi-rhuFJ` and forbids pushing to a different branch, so M26 keeps the milestone intent (one branch, one PR titled `feat: add threaded gateway-engine-feed pipeline prototype`) but not the `feat/mNN-slug` branch name.
+- [2026-06-01] M25: split the concurrency docs, kept the high-level model (ownership lifecycle, visibility, backpressure, shutdown, limits) in `docs/concurrency_model.md` and moved the C++ memory-model deep dive (ordering table, happens-before proof both directions, wait-free-by-construction argument) into a new `docs/memory_ordering.md`, cross-linked, to avoid one drifting from the other.
+- [2026-06-01] M25: justified the "wait-free per operation" / "lock-free" claim by construction (bounded step count, no loops, no CAS) rather than dropping it, and explicitly scoped it to the queue op, a caller spinning on backpressure is application-level and not wait-free.
- [2026-06-01] M25: introduced `tests/concurrency/` (separate from `tests/unit/`) for sustained stress + backpressure/shutdown evidence; deferred dynamic data-race detection (ThreadSanitizer) to M27 per the roadmap rather than adding a TSan preset now.
- [2026-06-01] M25: qualified the wait-free claim further so it only covers payload types with bounded, non-blocking copy/move assignment; `SpscRing` remains SPSC-only and the queue protocol proof does not overpromise for arbitrary `T`.
- [2026-05-31] Cut GitHub-only `v0.1.0` release after the release-readiness gate; no packages published.
-- [2026-05-31] Promoted old issues into the Phase III/IV roadmap (issue → milestone): #24 → M24, #26 → M26, #27 → M27, #25 → M28, #32 → M29 — instead of leaving them as loose backlog.
-- [2026-05-31] Added Phase IV milestones M29–M31 for Linux perf/socket profiling and external review signal (with M28 memory-pool experiment closing Phase III).
-- [2026-05-31] Deferred generic product items #28–#31 and #33 (realistic flow model, FIX adapter, dashboard, Docker, Pages) until after the systems-credibility arc; #32 (perf/flamegraph) is promoted to M29.
+- [2026-05-31] Promoted old issues into the Phase III/IV roadmap (issue → milestone): #24 → M24, #26 → M26, #27 → M27, #25 → M28, #32 → M29, instead of leaving them as loose backlog.
+- [2026-05-31] Added Phase IV milestones M29-M31 for Linux perf/socket profiling and external review signal (with M28 memory-pool experiment closing Phase III).
+- [2026-05-31] Deferred generic product items #28, #31 and #33 (realistic flow model, FIX adapter, dashboard, Docker, Pages) until after the systems-credibility arc; #32 (perf/flamegraph) is promoted to M29.
- [2026-05-29] Target Jane Street SWE first and Linux Engineering second; avoid optimizing for IT Operations because it weakens software-engineering signal.
- [2026-05-29] Preserve the C++20 exchange simulator as the core project; add OCaml only as a replay-verifier subproject, not as a replacement for the engine.
- [2026-05-29] Add Linux performance and socket gateway documentation requirements to strengthen Linux Engineering fit.
@@ -856,28 +854,28 @@ Lower priority:
### SWE title
```text
-Quant Systems Lab — C++20 Exchange Simulator + OCaml Replay Verifier
+Quant Systems Lab. C++20 Exchange Simulator + OCaml Replay Verifier
```
### Linux title
```text
-Quant Systems Lab — Linux Systems + Exchange Infrastructure Simulator
+Quant Systems Lab. Linux Systems + Exchange Infrastructure Simulator
```
## Next action remains
`v0.2.1` is the latest tag, on top of `v0.2.0` (PR #127 ded6e80) and `v0.1.0`. A post-v0.2.1
-hardening + perf wave (#135–#146) is squash-merged to `main` and **unreleased**, being cut as
-`v0.2.2`: out-of-domain enum rejection in the decoders (#136); network hardening — EINTR retry,
+hardening + perf wave (#135, #146) is squash-merged to `main` and **unreleased**, being cut as
+`v0.2.2`: out-of-domain enum rejection in the decoders (#136); network hardening. EINTR retry,
accept fairness, connection cap, UDP send-error tracking, transient-accept survival, and fd-exhaustion
handling (#137, #140, #143); CLI arg validation (#141); a real UBSan abort gate (#142); OCaml
-`diff_report` robustness (#144); and two measured order-book perf wins — `try_emplace` (~+5%, #138)
+`diff_report` robustness (#144); and two measured order-book perf wins, `try_emplace` (~+5%, #138)
and the order-index load-factor cap (~+18.6%, #145), with the flamegraph regenerated (#135/#139/#146).
`make check`/`make asan` 270/270. The committed perf artifacts remain **partial hardware PMU
-evidence** from this bare-metal Apple M2 (aarch64) Fedora Asahi host — real
+evidence** from this bare-metal Apple M2 (aarch64) Fedora Asahi host, real
cycles/instructions/branches/branch-misses with cache-reference/cache-miss counters unsupported by
-the Apple Silicon PMU — not NIC-offload, latency, or full hardware-PMU evidence.
+the Apple Silicon PMU, not NIC-offload, latency, or full hardware-PMU evidence.
Highest-value remaining work is non-code and gated: issue #94 (independent external review) and
issue #90 (full cache-PMU evidence). Issue #90 needs a PMU **microarchitecture** that exposes cache
@@ -889,7 +887,7 @@ After each squash merge, return to this file and update state factually. If benc
## Additive deep-testing roadmap replacing old optional M15
-The old optional `M15 — Jane Street application polish` is removed. It is replaced by technical milestones M15–M20. The purpose is to add actual depth rather than recruiter-facing decoration.
+The old optional `M15. Jane Street application polish` is removed. It is replaced by technical milestones M15-M20. The purpose is to add actual depth rather than recruiter-facing decoration.
- **M15** exports normalized command streams and final C++ snapshots.
- **M16** implements an independent OCaml replay engine.
@@ -901,13 +899,13 @@ The old optional `M15 — Jane Street application polish` is removed. It is repl
Decision log additions:
- [2026-05-30] Removed optional Jane Street application-polish milestone because recruiter-facing polish is lower signal than technical depth.
-- [2026-05-30] Added M15–M20 to turn the OCaml verifier into independent replay/differential-testing infrastructure.
+- [2026-05-30] Added M15-M20 to turn the OCaml verifier into independent replay/differential-testing infrastructure.
- [2026-05-30] Property-based generation and shrinking are now the intended final “deep idea” layer: the repo should test market-state systems, not merely implement one.
- [Roadmap] Repository hygiene is deferred until after Phase II differential testing; no CODE_OF_CONDUCT.md or GOVERNANCE.md unless the project becomes multi-maintainer.
- [Roadmap] Packaging is intentionally deferred; the repo is a technical artifact, not an installable product.
- [Roadmap] A GitHub v0.1.0 release is optional and only after the release-readiness audit.
- [Roadmap] M22 is the Phase II equivalent of M13: final README/demo/docs/readiness polish with conservative claims and reproducible checks.
-- [M16] OCaml `replay_engine.ml` is an independent immutable matching engine (price-time, GTC/IOC/market/cancel/modify, gateway risk, active-lifetime ids) that replays the M15 command stream and computes its own snapshot — it does not read the C++ events/snapshot during replay.
+- [M16] OCaml `replay_engine.ml` is an independent immutable matching engine (price-time, GTC/IOC/market/cancel/modify, gateway risk, active-lifetime ids) that replays the M15 command stream and computes its own snapshot, it does not read the C++ events/snapshot during replay.
- [M16] Sequence numbers count emitted events (accept + trades; cancel; modify + trades) so the OCaml `last_seq` matches the C++ engine; snapshot includes every registered symbol (mirrors `try_emplace`).
- [M16] On the committed `stream_seed7.txt`, the OCaml-computed snapshot matches the C++ snapshot exactly (last_seq 47, trades 13, per-symbol best bid/ask + counts); the automated C++-vs-OCaml equality check in CI is deferred to M17.
- [M17] Differential test compares the OCaml-computed snapshot against the C++ snapshot embedded in each fixture (best bid/ask, level aggregates, order counts, last_seq, trade count) via canonical `snapshot_to_lines`, printing a readable computed-vs-expected diff on mismatch; runs under the existing `ocaml-verifier` CI job (no new job).
@@ -916,14 +914,14 @@ Decision log additions:
- [M18] Added `generate_property_flow` (C++) + `qsl-export-stream prop ` producing enriched seeded streams (IOC, invalid price/qty, duplicate/reused/unknown ids, cancel/modify active+inactive, market, multi-symbol); committed `prop_seed1..8.txt` (later expanded to `prop_seed1..50` in #49). C++ and OCaml snapshots agree on all 8 seeds, exercising every reject reason plus real trades.
- [M18] The differential test discovers all `prop_*.txt` via `Sys.readdir` and checks snapshot equality + a no-crossed-book invariant per fixture, reporting the failing seed.
- [M18] Restored the M17 parser-rejection check (`bad_snapshot_level_symbol.txt` / `expect_parse_error`) that the M18 differential-test rewrite had dropped; this also cleared a warning-as-error (unused value) that had broken the OCaml build.
-- [M18] Broadened negative coverage (`stream_bad_lastseq.txt`, `stream_bad_orders.txt`) and added a golden fixture-regeneration guard (`scripts/check_fixtures.sh`, `make check-fixtures`, CI `build-test` step) so committed fixtures must match current C++ output — closing the M17-review gap where the differential could compare OCaml against a stale C++ snapshot.
+- [M18] Broadened negative coverage (`stream_bad_lastseq.txt`, `stream_bad_orders.txt`) and added a golden fixture-regeneration guard (`scripts/check_fixtures.sh`, `make check-fixtures`, CI `build-test` step) so committed fixtures must match current C++ output, closing the M17-review gap where the differential could compare OCaml against a stale C++ snapshot.
- [M19] Added a deterministic, greedy command-stream shrinker (`replay::shrink`): chunk removal, single-command removal, and field simplification (lower quantities), iterated to a fixed point, preserving a pluggable failure predicate; `count_trades` is a gateway-driven predicate helper.
- [M19] Demonstrated against an artificial "produces a trade" predicate (the engines currently agree, so there is no real divergence to shrink); the shrinker is predicate-agnostic and a divergence predicate plugs in unchanged.
- [M19] `qsl-export-stream shrink ` writes a minimized differential fixture + shrink report; committed `shrunk_seed1.txt` (123→5 commands) is golden-checked and replayed by the OCaml differential test. Limitations (greedy/not-globally-minimal, qty-only field simplification, no symbol/id renumbering) documented in docs/differential_testing.md.
- [M20] Finalized docs/differential_testing.md with a top-level architecture overview, a mermaid pipeline diagram, a minimized-fixture example, and an explicit "what this proves / does not prove" section (agreement over tested seeds; not formal verification; shared-assumption risk acknowledged).
- [M20] Added docs/property_testing.md (generator coverage + delta-debug shrinker + determinism/golden + honest limits); added a README "Differential testing (OCaml)" <60s section + diagram links and conservative differential-testing résumé bullets. Docs-only; no code change.
-- [M21] Added MIT LICENSE (Copyright (c) 2026 Moustafa Nasr), CONTRIBUTING.md (branch-per-milestone workflow + checks + no fabricated perf), SECURITY.md (no bounty; qsl-gateway/qsl-mdfeed unauthenticated + loopback-only; honest systems-lab-not-production), CHANGELOG.md ([Unreleased] M3–M20 history), and README links.
+- [M21] Added MIT LICENSE (Copyright (c) 2026 Moustafa Nasr), CONTRIBUTING.md (branch-per-milestone workflow + checks + no fabricated perf), SECURITY.md (no bounty; qsl-gateway/qsl-mdfeed unauthenticated + loopback-only; honest systems-lab-not-production), CHANGELOG.md ([Unreleased] M3-M20 history), and README links.
- [M21] Deliberately omitted CODE_OF_CONDUCT.md and GOVERNANCE.md (single-maintainer; no community-process theater) and skipped SUPPORT.md; no packaging and no release (deferred to optional M23).
- [M22] Release-readiness audit recorded in docs/release_readiness.md: verified make check/asan/check-fixtures/dune runtest/demo/bench all green, all README + doc-to-doc links resolve, no overclaiming (forbidden phrases appear only as negations or avoid-lists), benchmark language measured/synthetic/reproducible, differential vocabulary distinct. `make bench` was rerun to confirm reproduction; committed results/latest.txt retained (single-machine variance). No GitHub release created (that is the optional, human-approved M23).
-- [M20–M22 re-finalization] After the #34–#51 backlog landed, re-ran the Phase II finalization milestones against the current state: M20 differential/property docs + Mermaid diagrams + ADRs refreshed (PR #78); M21 CONTRIBUTING checks (added check-manifest/determinism/bench-diff/divergence-demo) and per-issue branch naming; M22 release_readiness.md re-audited — make check 157/157, asan 157/157, check-fixtures/check-manifest clean, determinism byte-identical across gcc/clang over all 50 property seeds, divergence-demo OK, dune runtest 5 suites, demo clean; benchmarks reproduce and committed results/latest.txt + results/differential.txt retained.
+- [M20-M22 re-finalization] After the #34, #51 backlog landed, re-ran the Phase II finalization milestones against the current state: M20 differential/property docs + Mermaid diagrams + ADRs refreshed (PR #78); M21 CONTRIBUTING checks (added check-manifest/determinism/bench-diff/divergence-demo) and per-issue branch naming; M22 release_readiness.md re-audited, make check 157/157, asan 157/157, check-fixtures/check-manifest clean, determinism byte-identical across gcc/clang over all 50 property seeds, divergence-demo OK, dune runtest 5 suites, demo clean; benchmarks reproduce and committed results/latest.txt + results/differential.txt retained.
- [M17] Snapshot parsing validates per-level symbol ownership so malformed embedded C++ snapshots cannot be normalized into equality.
diff --git a/README.md b/README.md
index 2f468c5..a067203 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ Binary order gateway. Price-time-priority matching engine. Market-data feed. App
[](https://github.com/div0rce/quant-systems-lab/actions/workflows/ci.yml)
[](CMakeLists.txt)
-[](tests/)
+[](tests/)
[](cmake/Sanitizers.cmake)
[](docs/invariants.md)
[](LICENSE)
@@ -29,31 +29,32 @@ Binary order gateway. Price-time-priority matching engine. Market-data feed. App
## The numbers
-Measured on a bare-metal Apple M2 (aarch64), Fedora Asahi, GCC 16, Release. Hot-path optimizations
-(`try_emplace` for price levels, an order-index hash load-factor cap) were profiled with `perf` and
-verified by back-to-back A/B. Full evidence and the honest mechanism in **[PERFORMANCE.md](PERFORMANCE.md)**.
+Measured on a bare-metal Apple M2 (aarch64), Fedora Asahi, GCC 16, Release. The hot path was profiled
+with `perf` and flamegraphs; the table compares the **v0.1.0 first release to v0.2.2** on the same
+host and harness (baseline storage, deep book). Full evidence and the honest mechanism in
+**[PERFORMANCE.md](PERFORMANCE.md)**.
| Quality bar | |
|---|---|
-| Tests | **271** passing |
+| Tests | **272** passing |
| Coverage | unit, integration, property, concurrency, shell |
| Sanitizers | ASan + UBSan (aborting) + TSan, clean |
| Determinism | snapshots byte-identical across GCC and Clang |
diff --git a/SECURITY.md b/SECURITY.md
index 2993800..84fcb64 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -13,14 +13,14 @@ The demo network components are intentionally minimal:
networks**, and do not run them on a shared or public interface.
- There is no TLS, access control, or rate limiting. The acceptors do have bounded resilience: an
optional connection cap, survival of transient `accept()` errors and fd exhaustion, and `EINTR`
- retry on read/write — but this is robustness hardening, not DoS protection. Malformed input is
+ retry on read/write, but this is robustness hardening, not DoS protection. Malformed input is
handled by disconnecting the peer, not by recovering the stream.
## Reporting
If you find a memory-safety or correctness issue (e.g. something ASan/UBSan or the differential
tests would have caught), please open a GitHub issue, or contact the maintainer if you consider
-it sensitive. Include a minimal reproducer where possible — the repo has tooling
+it sensitive. Include a minimal reproducer where possible, the repo has tooling
(`qsl-export-stream`, the shrinker) for producing small deterministic fixtures.
## Honest scope
diff --git a/apps/qsl-bench/main.cpp b/apps/qsl-bench/main.cpp
index 08a845c..0d4d9b6 100644
--- a/apps/qsl-bench/main.cpp
+++ b/apps/qsl-bench/main.cpp
@@ -11,6 +11,7 @@
#include
#include
+#include
#include
#include
#include
@@ -148,7 +149,13 @@ double profile_seconds_from_args(int argc, char **argv) {
} else if (const char *e = std::getenv("QSL_BENCH_PROFILE_SECONDS")) {
seconds = std::strtod(e, nullptr);
}
- return (seconds > 0.0) ? seconds : 5.0;
+ // strtod can yield inf/-inf/nan; !(x > 0.0) does not reject inf, and converting a non-finite or
+ // unbounded double to clock_type::duration is UB. Require finite and positive, and clamp the
+ // upper bound so the deadline computation stays well inside the duration's range.
+ if (!std::isfinite(seconds) || seconds <= 0.0) {
+ return 5.0;
+ }
+ return seconds > 3600.0 ? 3600.0 : seconds;
}
// One bounded steady-state step of the profiling flow: add a limit order, cancel the oldest once
diff --git a/apps/qsl-perfeval/main.cpp b/apps/qsl-perfeval/main.cpp
index cea2e6f..37bf700 100644
--- a/apps/qsl-perfeval/main.cpp
+++ b/apps/qsl-perfeval/main.cpp
@@ -1,4 +1,4 @@
-// qsl-perfeval — a dedicated performance-evidence harness for the matching-engine hot path
+// qsl-perfeval, a dedicated performance-evidence harness for the matching-engine hot path
// (order-book insertion + matching). It drives a steady-state deep-book order flow and reports
// orders/sec, per-order latency distribution (mean/p50/p99), and allocations/order. Run it under
// `perf stat` / `perf record` to attribute cycles/instructions/branch-misses and render
@@ -15,20 +15,42 @@
#include
#include
+#include
#include
#include
#include
#include
#include
#include
-#include
+#include
+#include
#include
+// Allocation counting replaces every global operator new variant so it can count ALL allocations
+// (plain AND over-aligned, the latter used by the order book's alignas types). Counting and the
+// pure-performance run are mutually exclusive on purpose: intercepting the aligned operators adds a
+// little work per aligned allocation, which would perturb the cycle/throughput numbers for an
+// allocation-heavy build. So counting is compile-time opt-in (QSL_PERFEVAL_COUNT_ALLOCS); the
+// default build leaves the system allocator untouched and reports allocs/order as "n/a". Measure
+// allocations and performance in separate builds (see PERFORMANCE.md methodology).
+#ifdef QSL_PERFEVAL_COUNT_ALLOCS
namespace {
-// Allocations during the timed region only (the counter is sampled before and after the loop). The
-// override below counts the primary std::operator new path used by the baseline pmr new_delete
-// resource for order-book list/map nodes.
std::atomic g_allocs{0};
+constexpr bool kCountingAllocs = true;
+std::uint64_t allocs_now() {
+ return g_allocs.load(std::memory_order_relaxed);
+}
+void *aligned_new(std::size_t n, std::align_val_t a) {
+ g_allocs.fetch_add(1, std::memory_order_relaxed);
+ // aligned_alloc (what libstdc++'s default aligned new uses) requires size to be a multiple of
+ // the alignment.
+ const std::size_t align = std::max(sizeof(void *), static_cast(a));
+ const std::size_t size = (n + align - 1) & ~(align - 1);
+ if (void *p = std::aligned_alloc(align, size)) {
+ return p;
+ }
+ throw std::bad_alloc();
+}
} // namespace
void *operator new(std::size_t n) {
@@ -38,12 +60,6 @@ void *operator new(std::size_t n) {
}
throw std::bad_alloc();
}
-void operator delete(void *p) noexcept {
- std::free(p);
-}
-void operator delete(void *p, std::size_t) noexcept {
- std::free(p);
-}
void *operator new[](std::size_t n) {
g_allocs.fetch_add(1, std::memory_order_relaxed);
if (void *p = std::malloc(n)) {
@@ -51,12 +67,44 @@ void *operator new[](std::size_t n) {
}
throw std::bad_alloc();
}
+void *operator new(std::size_t n, std::align_val_t a) {
+ return aligned_new(n, a);
+}
+void *operator new[](std::size_t n, std::align_val_t a) {
+ return aligned_new(n, a);
+}
+void operator delete(void *p) noexcept {
+ std::free(p);
+}
+void operator delete(void *p, std::size_t) noexcept {
+ std::free(p);
+}
void operator delete[](void *p) noexcept {
std::free(p);
}
void operator delete[](void *p, std::size_t) noexcept {
std::free(p);
}
+void operator delete(void *p, std::align_val_t) noexcept {
+ std::free(p);
+}
+void operator delete(void *p, std::size_t, std::align_val_t) noexcept {
+ std::free(p);
+}
+void operator delete[](void *p, std::align_val_t) noexcept {
+ std::free(p);
+}
+void operator delete[](void *p, std::size_t, std::align_val_t) noexcept {
+ std::free(p);
+}
+#else
+namespace {
+constexpr bool kCountingAllocs = false;
+std::uint64_t allocs_now() {
+ return 0;
+}
+} // namespace
+#endif
namespace {
using clk = std::chrono::steady_clock;
@@ -103,7 +151,8 @@ struct PerfFlow {
PerfFlow() { ring.reserve(kRing); }
- // Submit one limit order; returns its id so the caller can park it in the ring.
+ // Submit one limit order; returns its id. It may match resting liquidity and rest its
+ // remainder, or fully fill (marketable) and never rest.
core::OrderId submit() {
const std::uint64_t r = splitmix64(state);
const auto side = ((r & 1U) != 0U) ? core::Side::Buy : core::Side::Sell;
@@ -114,9 +163,18 @@ struct PerfFlow {
return oid;
}
- // Cancel the oldest resting order and park the new id in its slot, holding the book ~kRing
- // deep.
- void retire_oldest(core::OrderId oid) {
+ // Track only orders that actually rested, holding the book ~kRing deep: a fully-filled order is
+ // ignored, a resting order is parked, and once the ring is full the oldest resting order is
+ // cancelled. Checking eng.contains keeps depth steady instead of drifting with the match rate
+ // (and never cancels an id that never rested).
+ void track(core::OrderId oid) {
+ if (!eng.contains(sym, oid)) {
+ return;
+ }
+ if (ring.size() < kRing) {
+ ring.push_back(oid);
+ return;
+ }
sink += eng.cancel(sym, ring[head]).size();
ring[head] = oid;
head = (head + 1) % kRing;
@@ -124,7 +182,7 @@ struct PerfFlow {
void warmup() {
while (ring.size() < kRing) {
- ring.push_back(submit());
+ track(submit());
}
}
};
@@ -147,8 +205,8 @@ void fill_latency_stats(std::vector &lat, Result &res) {
sum += v;
}
res.mean_ns = static_cast(sum / lat.size());
- res.p50_ns = lat[lat.size() / 2];
- res.p99_ns = lat[(lat.size() * 99) / 100];
+ res.p50_ns = lat[(lat.size() - 1) / 2];
+ res.p99_ns = lat[((lat.size() - 1) * 99) / 100];
res.overhead_ns = timer_overhead_ns();
}
@@ -157,17 +215,16 @@ void fill_latency_stats(std::vector &lat, Result &res) {
Result run_throughput(std::uint64_t orders) {
PerfFlow flow;
flow.warmup();
- const std::uint64_t a0 = g_allocs.load(std::memory_order_relaxed);
+ const std::uint64_t a0 = allocs_now();
const auto t0 = clk::now();
for (std::uint64_t i = 0; i < orders; ++i) {
- flow.retire_oldest(flow.submit());
+ flow.track(flow.submit());
}
const auto t1 = clk::now();
Result res;
res.orders = orders;
res.seconds = std::chrono::duration(t1 - t0).count();
- res.allocs_per_order = static_cast(g_allocs.load(std::memory_order_relaxed) - a0) /
- static_cast(orders);
+ res.allocs_per_order = static_cast(allocs_now() - a0) / static_cast(orders);
return res;
}
@@ -178,7 +235,7 @@ Result run_latency(std::uint64_t orders) {
flow.warmup();
std::vector lat;
lat.reserve(orders);
- const std::uint64_t a0 = g_allocs.load(std::memory_order_relaxed);
+ const std::uint64_t a0 = allocs_now();
const auto t0 = clk::now();
for (std::uint64_t i = 0; i < orders; ++i) {
const std::uint64_t r = splitmix64(flow.state);
@@ -191,49 +248,81 @@ Result run_latency(std::uint64_t orders) {
flow.eng.new_limit(flow.sym, oid, side, price, qty, core::TimeInForce::GTC).size();
const auto b = clk::now();
lat.push_back(ns_between(a, b));
- flow.retire_oldest(oid);
+ flow.track(oid);
}
const auto t1 = clk::now();
Result res;
res.orders = orders;
res.seconds = std::chrono::duration(t1 - t0).count();
- res.allocs_per_order = static_cast(g_allocs.load(std::memory_order_relaxed) - a0) /
- static_cast(orders);
+ res.allocs_per_order = static_cast(allocs_now() - a0) / static_cast(orders);
res.has_latency = true;
fill_latency_stats(lat, res);
return res;
}
-std::uint64_t parse_orders(int argc, char **argv, bool latency) {
+// Parse a whole token as a positive order count. Rejects partial parses ("123abc"), trailing junk,
+// and negative-looking input ("-1") that std::strtoull would wrap to a huge value and feed straight
+// into reserve()/the loop bound.
+std::optional parse_orders_arg(std::string_view s) {
+ std::uint64_t v = 0;
+ const char *begin = s.data();
+ const char *end = begin + s.size();
+ const auto [ptr, ec] = std::from_chars(begin, end, v);
+ if (ec != std::errc{} || ptr != end || v == 0) {
+ return std::nullopt;
+ }
+ return v;
+}
+
+struct Args {
+ bool ok = true;
+ bool latency = false;
+ std::uint64_t count = 0;
+};
+
+// Parse argv: an optional "--latency" flag and at most one positive order count. A malformed token
+// or a second count is rejected (ambiguous sample size). Defaults to 5M (latency) / 60M orders.
+Args parse_args(int argc, char **argv) {
+ Args out;
+ std::optional orders;
for (int i = 1; i < argc; ++i) {
- const std::string a = argv[i];
- if (a != "--latency") {
- const std::uint64_t n = std::strtoull(a.c_str(), nullptr, 10);
- if (n > 0) {
- return n;
- }
+ const std::string_view a = argv[i];
+ if (a == "--latency") {
+ out.latency = true;
+ continue;
}
+ const auto parsed = parse_orders_arg(a);
+ if (!parsed || orders) {
+ out.ok = false;
+ return out;
+ }
+ orders = *parsed;
}
- return latency ? 5'000'000ULL : 60'000'000ULL;
+ out.count = orders.value_or(out.latency ? 5'000'000ULL : 60'000'000ULL);
+ return out;
}
} // namespace
-// qsl-perfeval [orders] [--latency]
+// qsl-perfeval [orders>0] [--latency]
// default: untimed throughput + allocations/order (run under perf stat / perf record)
// --latency: also record per-order latency mean/p50/p99
int main(int argc, char **argv) {
- bool latency = false;
- for (int i = 1; i < argc; ++i) {
- if (std::string(argv[i]) == "--latency") {
- latency = true;
- }
+ const Args args = parse_args(argc, argv);
+ if (!args.ok) {
+ std::fprintf(stderr, "usage: qsl-perfeval [orders>0] [--latency]\n");
+ return 2;
+ }
+ const Result r = args.latency ? run_latency(args.count) : run_throughput(args.count);
+ char allocs_buf[24];
+ if constexpr (kCountingAllocs) {
+ std::snprintf(allocs_buf, sizeof(allocs_buf), "%.4f", r.allocs_per_order);
+ } else {
+ std::snprintf(allocs_buf, sizeof(allocs_buf), "n/a");
}
- const std::uint64_t orders = parse_orders(argc, argv, latency);
- const Result r = latency ? run_latency(orders) : run_throughput(orders);
std::printf("perfeval: storage=baseline orders=%llu elapsed=%.3fs orders_per_sec=%.0f "
- "allocs_per_order=%.4f\n",
+ "allocs_per_order=%s\n",
static_cast(r.orders), r.seconds,
- static_cast(r.orders) / r.seconds, r.allocs_per_order);
+ static_cast(r.orders) / r.seconds, allocs_buf);
if (r.has_latency) {
std::printf("perfeval: latency_ns mean=%u p50=%u p99=%u (each incl ~%uns timer overhead)\n",
r.mean_ns, r.p50_ns, r.p99_ns, r.overhead_ns);
diff --git a/cmake/Sanitizers.cmake b/cmake/Sanitizers.cmake
index a174e8b..b57120e 100644
--- a/cmake/Sanitizers.cmake
+++ b/cmake/Sanitizers.cmake
@@ -19,7 +19,7 @@ if(QSL_ENABLE_ASAN)
endif()
# ThreadSanitizer (M27): data-race detection for the concurrent pipeline. It is a correctness
-# gate, not a performance tool — never collect benchmark numbers under TSan.
+# gate, not a performance tool, never collect benchmark numbers under TSan.
if(QSL_ENABLE_TSAN)
add_compile_options(-fsanitize=thread -fno-omit-frame-pointer)
add_link_options(-fsanitize=thread)
diff --git a/data/synthetic/README.md b/data/synthetic/README.md
index 011cedd..bccfc08 100644
--- a/data/synthetic/README.md
+++ b/data/synthetic/README.md
@@ -3,7 +3,7 @@
`data/synthetic/` currently contains no committed synthetic datasets. **This is intentional.**
Synthetic order flows are generated by the C++ property/golden fixture tooling
-(`generate_flow` / `generate_property_flow`, exported via `qsl-export-stream`) — not by a
+(`generate_flow` / `generate_property_flow`, exported via `qsl-export-stream`), not by a
standalone script that writes into this directory. The committed synthetic, property, and
regression fixtures live under `ocaml/test/fixtures/` and `regressions/`.
diff --git a/docs/adr/0002-independent-ocaml-oracle.md b/docs/adr/0002-independent-ocaml-oracle.md
index a090739..dad1007 100644
--- a/docs/adr/0002-independent-ocaml-oracle.md
+++ b/docs/adr/0002-independent-ocaml-oracle.md
@@ -28,8 +28,8 @@ model from the C++ intrusive book.
## Alternatives considered
-- **C++-only property tests** — rejected: they share the engine's assumptions.
-- **A second C++ engine** — rejected: a same-language reimplementation shares more failure modes
+- **C++-only property tests**, rejected: they share the engine's assumptions.
+- **A second C++ engine**, rejected: a same-language reimplementation shares more failure modes
and gives no language-diversity signal.
-- **Consuming the C++ event log** instead of replaying commands — rejected: that would trust the
+- **Consuming the C++ event log** instead of replaying commands, rejected: that would trust the
system under test rather than independently deriving state.
diff --git a/docs/adr/0003-golden-fixture-regeneration.md b/docs/adr/0003-golden-fixture-regeneration.md
index a32e7b6..52b3b10 100644
--- a/docs/adr/0003-golden-fixture-regeneration.md
+++ b/docs/adr/0003-golden-fixture-regeneration.md
@@ -27,10 +27,10 @@ committed corpus.
- A fixture cannot drift from current C++ output, or change without a visible manifest update.
- The corpus is reproducible across compilers/platforms (macOS-committed vs Linux CI).
- The generator version is human-set provenance, not an automatically enforced invariant
- (a generator change still requires a deliberate version bump — documented, not enforced).
+ (a generator change still requires a deliberate version bump, documented, not enforced).
## Alternatives considered
-- **Trust committed fixtures without regeneration** — rejected: silent drift risk.
-- **Hashes only (no regeneration)** — rejected: a hash matches a stale-but-unchanged fixture; the
+- **Trust committed fixtures without regeneration**, rejected: silent drift risk.
+- **Hashes only (no regeneration)**, rejected: a hash matches a stale-but-unchanged fixture; the
regenerate-and-diff step is what proves equivalence to current C++ output.
diff --git a/docs/adr/0004-deterministic-shrinker.md b/docs/adr/0004-deterministic-shrinker.md
index 3316cfb..7197cf0 100644
--- a/docs/adr/0004-deterministic-shrinker.md
+++ b/docs/adr/0004-deterministic-shrinker.md
@@ -30,7 +30,7 @@ kept in a regression archive (`regressions/`); on a CI divergence a failure arti
## Alternatives considered
-- **A property-testing library (e.g. QuickCheck-style dependency)** — rejected: the project keeps
+- **A property-testing library (e.g. QuickCheck-style dependency)**, rejected: the project keeps
dependencies minimal, and a bespoke shrinker over the command ADT is small and transparent.
-- **Random/ddmin without field simplification** — rejected: leaves large prices/ids and extra
+- **Random/ddmin without field simplification**, rejected: leaves large prices/ids and extra
registrations, producing less readable counterexamples.
diff --git a/docs/adr/0007-constrained-perf-artifacts-are-partial-evidence.md b/docs/adr/0007-constrained-perf-artifacts-are-partial-evidence.md
index 4ca6a99..7a34e9c 100644
--- a/docs/adr/0007-constrained-perf-artifacts-are-partial-evidence.md
+++ b/docs/adr/0007-constrained-perf-artifacts-are-partial-evidence.md
@@ -37,15 +37,15 @@ counters** off the Apple Avalanche/Blizzard PMUs (`cycles`, `instructions`, `bra
`scripts/perf_stat.sh` now classifies three ways instead of two:
-- `hardware PMU evidence` — every requested counter, including cache events, captured (full).
-- `partial hardware PMU evidence` — at least one real hardware counter captured, but the requested
+- `hardware PMU evidence`, every requested counter, including cache events, captured (full).
+- `partial hardware PMU evidence`, at least one real hardware counter captured, but the requested
set is incomplete. **This is the current Apple Silicon state**: `cache-references`/`cache-misses`
are `` because the Asahi PMU driver does not expose them.
-- `constrained-environment validation (no hardware PMU access)` — no hardware counter produced a
+- `constrained-environment validation (no hardware PMU access)`, no hardware counter produced a
value (the prior Docker case).
The original decision stands: a partial artifact is partial evidence and must not be called *full*.
What changed is that the honest label is now `partial hardware PMU evidence` (real counters present),
not `constrained validation`. Issue #90's residual is narrowed to the cache-counter set, which needs
-a PMU microarchitecture that exposes those events (x86_64, or an ARM server core) — being bare metal
+a PMU microarchitecture that exposes those events (x86_64, or an ARM server core), being bare metal
is necessary but not sufficient.
diff --git a/docs/adr/0008-socket-evidence-loopback-constrained-epoll-deferred.md b/docs/adr/0008-socket-evidence-loopback-constrained-epoll-deferred.md
index 6ea9c79..383a9c6 100644
--- a/docs/adr/0008-socket-evidence-loopback-constrained-epoll-deferred.md
+++ b/docs/adr/0008-socket-evidence-loopback-constrained-epoll-deferred.md
@@ -21,7 +21,7 @@ bars.
## Decision
-- Socket profiling artifacts are loopback-only, constrained-environment evidence — the same
+- Socket profiling artifacts are loopback-only, constrained-environment evidence, the same
policy ADR 0007 applies to `perf`. They are labeled as such, carry OS/kernel/compiler/commit/
dirty-tree metadata, and never claim NIC/driver/real-network behavior or production capacity.
- M30 profiles and hardens the **existing** one-connection-at-a-time gateway rather than rewriting
diff --git a/docs/adr/0011-event-log-durability-modes-and-tail-repair.md b/docs/adr/0011-event-log-durability-modes-and-tail-repair.md
index e212e89..a196e44 100644
--- a/docs/adr/0011-event-log-durability-modes-and-tail-repair.md
+++ b/docs/adr/0011-event-log-durability-modes-and-tail-repair.md
@@ -14,7 +14,7 @@ production durability.
Two design questions had real alternatives:
-1. Where should durability be enforced — per append, per batch, or never — and who decides?
+1. Where should durability be enforced, per append, per batch, or never, and who decides?
2. What damage may automation repair without a human?
## Decision
@@ -35,7 +35,7 @@ Two design questions had real alternatives:
at end of file. `PayloadTooLarge` headers are never trusted. `repair_log_file`
truncates torn tails to the last valid record boundary (and fsyncs the truncation); it
refuses `Corrupt` logs, because truncating mid-file damage would silently discard
- acknowledged records beyond it — a human decision.
+ acknowledged records beyond it, a human decision.
3. **The claim is scoped to the log layer.** The gateway/pipeline still acknowledges
before persisting (log-behind, not write-ahead). Re-architecting acks-after-durability
was deliberately rejected for M45; the gap is documented in `docs/persistence.md`
diff --git a/docs/allocator_experiment.md b/docs/allocator_experiment.md
index 34cbbff..c6d7661 100644
--- a/docs/allocator_experiment.md
+++ b/docs/allocator_experiment.md
@@ -41,9 +41,9 @@ qsl-bench pool
`scripts/run_allocator_experiment.sh` writes `results/allocator_experiment.txt` with hardware,
OS, compiler, build type, source-digest provenance, date, and the measured scenarios:
-- `order new/delete` — baseline heap allocation for one `engine::Order`;
-- `order pool acquire/release` — one acquire and release against a fixed pool;
-- `order pool burst cycle` — fill a 1024-slot pool, then release all slots.
+- `order new/delete`, baseline heap allocation for one `engine::Order`;
+- `order pool acquire/release`, one acquire and release against a fixed pool;
+- `order pool burst cycle`, fill a 1024-slot pool, then release all slots.
## Interpreting Results
diff --git a/docs/architecture.md b/docs/architecture.md
index 6ba9867..e295260 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -46,27 +46,27 @@ flowchart LR
## Components
-- **Core types** — integer ticks, order IDs, sequence numbers
-- **Binary protocol** — fixed-width encode/decode (M2)
-- **Order book** — price-time priority (M3)
-- **Matching engine** — multi-symbol sequencing (M4)
-- **Risk** — deterministic pre-trade checks (M5)
-- **Market data** — trade/top-of-book publisher (M6)
-- **Event log** — append-only replayable log (M7)
-- **Replay/recovery** — rebuild state from log (M8)
-- **TCP gateway** — binary order gateway (M9)
-- **Benchmarks** — reproducible performance measurement (M11)
-- **OCaml replay verifier + oracle** — independent typed-functional replay (M14, M16)
-- **Differential + property testing** — command-stream export, C++≡OCaml snapshot equality,
- seeded property generator, and a shrinker that minimizes any divergence (M15–M19)
-- **Concurrency primitive + threaded pipeline** — a bounded SPSC ring buffer and an optional
- three-thread gateway→engine→publisher/log pipeline prototype (M24–M26)
-- **Order-book storage modes** — baseline, PMR-pooled (M32), intrusive `OrderPool` (PR #112),
+- **Core types**, integer ticks, order IDs, sequence numbers
+- **Binary protocol**, fixed-width encode/decode (M2)
+- **Order book**, price-time priority (M3)
+- **Matching engine**, multi-symbol sequencing (M4)
+- **Risk**, deterministic pre-trade checks (M5)
+- **Market data**, trade/top-of-book publisher (M6)
+- **Event log**, append-only replayable log (M7)
+- **Replay/recovery**, rebuild state from log (M8)
+- **TCP gateway**, binary order gateway (M9)
+- **Benchmarks**, reproducible performance measurement (M11)
+- **OCaml replay verifier + oracle**, independent typed-functional replay (M14, M16)
+- **Differential + property testing**, command-stream export, C++≡OCaml snapshot equality,
+ seeded property generator, and a shrinker that minimizes any divergence (M15-M19)
+- **Concurrency primitive + threaded pipeline**, a bounded SPSC ring buffer and an optional
+ three-thread gateway→engine→publisher/log pipeline prototype (M24-M26)
+- **Order-book storage modes**, baseline, PMR-pooled (M32), intrusive `OrderPool` (PR #112),
and a bounded-domain contiguous direct-price-index layout (M47); all produce identical
deterministic event streams and snapshots
-- **epoll gateway prototype** — an optional Linux event-driven multi-client transport alongside
+- **epoll gateway prototype**, an optional Linux event-driven multi-client transport alongside
the default portable threaded TCP server (M34)
-- **Event-log durability + recovery** — caller-chosen durability modes (buffered / flush / fsync)
+- **Event-log durability + recovery**, caller-chosen durability modes (buffered / flush / fsync)
with torn-tail classification and provably-torn repair (M45)
The first block is the runtime simulator; the last two are the cross-language verification
@@ -83,7 +83,7 @@ Defined in `include/qsl/core/`.
Prices are integer ticks (`Price = std::int64_t`), never floating point. A tick
scale maps display prices to integers: at `kTickScale = 100`, `$123.45` is stored
-as `12345`. This keeps arithmetic exact and matching deterministic — floating-point
+as `12345`. This keeps arithmetic exact and matching deterministic, floating-point
rounding would make fills and price-time priority non-reproducible.
Other domain aliases: `SymbolId` (u32), `Quantity` (u32), `OrderId` (u64),
@@ -108,24 +108,24 @@ only at the gateway boundary and benchmark layer, never inside matching.
`MatchingEngine` (`include/qsl/engine/matching_engine.hpp`) wraps the single-symbol
`OrderBook` into a multi-symbol engine.
-- **Symbol registry** — `SymbolRegistry` interns external symbol names to compact
+- **Symbol registry**, `SymbolRegistry` interns external symbol names to compact
`SymbolId`s assigned in registration order. The engine holds one `OrderBook` per
registered symbol in a `std::map` (ordered, for deterministic
snapshot iteration).
-- **Command application** — `new_limit` / `new_market` / `cancel` / `modify` route to the
+- **Command application**, `new_limit` / `new_market` / `cancel` / `modify` route to the
symbol's book. A command for an unregistered symbol (or a cancel/modify of an unknown
order) is a no-op here; structured rejection is the risk layer's job (M5).
-- **Active order IDs** — a resting `OrderId` is unique within each symbol book. Duplicate
+- **Active order IDs**, a resting `OrderId` is unique within each symbol book. Duplicate
active IDs are ignored in M4 before any acceptance event or sequence number is emitted
so the book's locator index cannot be corrupted. M5 turns this condition into a
structured `DuplicateOrderId` rejection.
-- **Event stream** — commands emit `EngineEvent`, a `std::variant` of `OrderAccepted`,
+- **Event stream**, commands emit `EngineEvent`, a `std::variant` of `OrderAccepted`,
`OrderCanceled`, `OrderModified`, and `TradeEvent` (`engine/events.hpp`). A new order
emits `OrderAccepted` followed by a `TradeEvent` per fill. `OrderRejected` (M5) and
`BookUpdate` (M6) are added later.
-- **Sequencing** — every emitted event carries a `SeqNo` from a single monotonic counter,
+- **Sequencing**, every emitted event carries a `SeqNo` from a single monotonic counter,
so event sequence numbers are strictly increasing across all symbols and commands.
-- **Snapshot** — `snapshot()` returns a deterministic `EngineSnapshot` (last sequence
+- **Snapshot**, `snapshot()` returns a deterministic `EngineSnapshot` (last sequence
number plus per-symbol best bid/ask and resting order count, ordered by `SymbolId`) for
replay-equivalence comparison. M8 extends it with per-level aggregates and the trade
sequence.
@@ -143,8 +143,7 @@ ClientCommand -> OrderGateway (risk) -> MatchingEngine -> EngineEvents
- **Value checks** (`engine/risk.hpp`, `RiskConfig` + `check_limit`/`check_market`) are pure
and deterministic: invalid side, invalid price tick (price must be positive), invalid
- quantity, max order quantity, and max notional. The notional check is overflow-safe —
- it compares `quantity > max_notional / price` rather than computing `price * quantity`.
+ quantity, max order quantity, and max notional. The notional check is overflow-safe, it compares `quantity > max_notional / price` rather than computing `price * quantity`.
Side validation applies to `new_limit` and `new_market`; modify has no side parameter.
Modify commands that leave a nonzero resting order are re-checked against the same
limit-order value constraints before reaching the engine. Modify quantity `0` remains
@@ -156,12 +155,12 @@ ClientCommand -> OrderGateway (risk) -> MatchingEngine -> EngineEvents
(`MatchingEngine::has_symbol` / `contains`), consistent with the engine's no-op on a
duplicate active id. The M4 engine/book duplicate guards remain as invariant defense.
Completed-order ids are not retained, so they may be reused.
-- **Structured result** — every submission returns a `GatewayResult{accepted, reason,
+- **Structured result**, every submission returns a `GatewayResult{accepted, reason,
events}`. A rejection carries a `RejectReason` and no events and never reaches the engine
(so the engine's sequence counter and state are untouched); an acceptance carries the
engine's resulting event stream. Rejections are intentionally *not* part of the engine's
sequenced event stream because they do not mutate engine state (which keeps replay clean).
-- **Opt-in storage guards** — when the explicit intrusive order-pool storage mode is selected,
+- **Opt-in storage guards**, when the explicit intrusive order-pool storage mode is selected,
a GTC limit order that would need to rest but cannot acquire an order node is rejected with
`StorageExhausted` before matching mutates state. The M47 contiguous storage mode similarly
rejects a GTC remainder that would have to rest outside its fixed direct-price-index band,
@@ -183,22 +182,22 @@ and produces market-data messages for subscribers:
EngineEvents -> MarketDataPublisher -> MarketDataSubscriber(s)
```
-- **Messages** (`feed/market_data.hpp`) — `MarketDataMessage` is a `std::variant` of
+- **Messages** (`feed/market_data.hpp`), `MarketDataMessage` is a `std::variant` of
`MdTrade` (symbol, price, quantity) and `MdTopOfBook` (symbol, optional best bid/ask).
Each carries a monotonic `md_seq`. `BookDelta`/`Snapshot` (full depth) are deferred to a
later networked-feed milestone.
-- **Derivation** — `publish(engine, events)` is called once per applied command. Each
+- **Derivation**, `publish(engine, events)` is called once per applied command. Each
`TradeEvent` yields an `MdTrade`; after the batch, each touched symbol whose top of book
differs from the publisher's last observed top (read from the engine) yields an
`MdTopOfBook`. First observation of an empty book initializes publisher state but emits
no TOB, because no observable top changed. Transitions from non-empty to empty do emit
TOB because the top changed. A resting order behind the best produces no message.
-- **Sequencing** — every emitted message gets the next `md_seq` from a single monotonic
+- **Sequencing**, every emitted message gets the next `md_seq` from a single monotonic
counter, and messages are emitted in engine-event order, so the market-data sequence is
monotonic and follows the engine sequence.
-- **Subscriber interface** — `MarketDataSubscriber::on_market_data` is the delivery hook;
+- **Subscriber interface**, `MarketDataSubscriber::on_market_data` is the delivery hook;
the publisher fans out to all registered subscribers.
-- **Wire encoding** — `MdTrade`/`MdTopOfBook` encode via the M2 binary protocol framing
+- **Wire encoding**, `MdTrade`/`MdTopOfBook` encode via the M2 binary protocol framing
(`MsgType::MdTrade`/`MdTopOfBook`), reusing the shared header writer and big-endian
helpers; `md_seq` travels in the header's sequence field.
@@ -228,7 +227,7 @@ engine events -> MarketDataPublisher -> UdpPublisher --UDP--> UdpFeedClient -> S
indefinitely waiting for fewer than `N` messages).
- **Testing strategy**: gap detection is verified deterministically by **injecting
out-of-sequence datagrams** (publishing `md_seq` 1 then 3 and asserting the client observes
- a gap of one) through the real receive/decode/client path — never by relying on
+ a gap of one) through the real receive/decode/client path, never by relying on
nondeterministic packet loss.
### Local demo
@@ -243,7 +242,7 @@ make build
- **UDP is connectionless and lossy.** There is no retransmit, no flow control, and no
ordering guarantee across datagrams. The feed *detects* loss via sequence gaps; it does not
- recover it (a real venue would offer a snapshot/recovery channel — not implemented here).
+ recover it (a real venue would offer a snapshot/recovery channel, not implemented here).
- **Unicast localhost only.** It binds/sends on `127.0.0.1`. There is no multicast, no
authentication, and no encryption. Do not expose it on an untrusted network.
- **No fragmentation handling.** Messages are small (well under the MTU), so each fits in one
@@ -251,7 +250,7 @@ make build
- This is a credible systems demonstration of a sequenced UDP feed with gap detection, not a
production market-data distribution system.
-## Concurrency: SPSC queues and the threaded pipeline (M24–M26)
+## Concurrency: SPSC queues and the threaded pipeline (M24-M26)
Phase III adds a concurrency boundary on top of the deterministic core without changing its
semantics. `SpscRing` (`include/qsl/concurrency/spsc_ring.hpp`) is a bounded
diff --git a/docs/benchmarking.md b/docs/benchmarking.md
index 9823b6e..260ae72 100644
--- a/docs/benchmarking.md
+++ b/docs/benchmarking.md
@@ -31,13 +31,13 @@ machine and toolchain it came from, and a different machine will produce differe
`apps/qsl-bench/main.cpp` is a small custom harness (no external benchmark dependency):
-- `latency(name, iters, op)` — runs `op` `iters` times after a warmup and reports ns/op and
+- `latency(name, iters, op)`, runs `op` `iters` times after a warmup and reports ns/op and
ops/sec.
-- `throughput(name, items, reps, run)` — runs `run` (which processes `items` items) `reps`
+- `throughput(name, items, reps, run)`, runs `run` (which processes `items` items) `reps`
times after a warmup and reports ns/item and items/sec.
A `volatile` sink consumes each operation's result so the optimizer cannot elide the work.
-Timing uses `std::chrono::steady_clock` — wall-clock at the **benchmark layer only**; the
+Timing uses `std::chrono::steady_clock`, wall-clock at the **benchmark layer only**; the
deterministic engine never reads a clock.
`make bench` uses a benchmark-specific CMake preset (`bench`) with
@@ -58,7 +58,7 @@ benchmark-only builds.
## Deterministic seeds
The matching and replay scenarios use `replay::generate_flow(seed = 42, symbols = 4,
-orders = 5000)` — a fixed `mt19937_64` seed, so the workload is identical run to run and on
+orders = 5000)`, a fixed `mt19937_64` seed, so the workload is identical run to run and on
any machine. The generator is still synthetic, but it is stateful: per-symbol mid-prices drift,
orders mostly rest near the book, cancels/modifies preferentially target active orders, and
occasional market/crossing flow creates trades. Changing the seed, sizes, or generator version
diff --git a/docs/binary_protocol.md b/docs/binary_protocol.md
index 33f4e28..de69c03 100644
--- a/docs/binary_protocol.md
+++ b/docs/binary_protocol.md
@@ -7,11 +7,24 @@ in `include/qsl/protocol/` and `src/protocol/codec.cpp`.
- Fixed-width messages for predictable parsing.
- **Big-endian (network byte order)** at the protocol boundary.
-- **Explicit serialization** via byte shifts (`endian.hpp`) — no `reinterpret_cast`,
+- **Explicit serialization** via byte shifts (`endian.hpp`), no `reinterpret_cast`,
no `memcpy`, no struct overlay, so there is no undefined behavior from layout/aliasing.
- A version field for forward compatibility.
- Deterministic rejection of malformed frames.
+```mermaid
+flowchart LR
+ bytes["Inbound bytes"] --> hdr["Parse 16-byte header: msg_type, version, body_len, seq_no"]
+ hdr --> v1{"Known msg_type?"}
+ v1 -->|No| rej["Reject frame"]
+ v1 -->|Yes| v2{"Supported version?"}
+ v2 -->|No| rej
+ v2 -->|Yes| v3{"body_len within max and matches type?"}
+ v3 -->|No| rej
+ v3 -->|Yes| body["Decode body, big-endian byte shifts"]
+ body --> ok["Typed message"]
+```
+
Internal engine structs are independent of this wire layout.
## Frame
@@ -86,5 +99,5 @@ accidental change to field order or byte order fails the build.
## Text alternative
A human-readable, FIX-like `tag=value` adapter over the same internal message structs lives
-alongside this binary codec — see [fix_protocol.md](fix_protocol.md). Both decode the same order to
+alongside this binary codec, see [fix_protocol.md](fix_protocol.md). Both decode the same order to
identical structs, which the tests assert directly.
diff --git a/docs/concurrency_model.md b/docs/concurrency_model.md
index 79c707c..bd8be69 100644
--- a/docs/concurrency_model.md
+++ b/docs/concurrency_model.md
@@ -1,12 +1,12 @@
# Concurrency model
-> Phase III, M24–M25. This documents the first concurrency primitive, the bounded SPSC ring buffer
+> Phase III, M24-M25. This documents the first concurrency primitive, the bounded SPSC ring buffer
> (`include/qsl/concurrency/spsc_ring.hpp`): why it is single-producer/single-consumer, who owns
> what, how producer and consumer observe each other, the backpressure and shutdown contract the
> later pipeline depends on, and the honest limits. The C++ memory-model details (per-step
> acquire/release reasoning and the happens-before proof) live in
> [`memory_ordering.md`](memory_ordering.md). The threaded pipeline that consumes this queue is
-> realized in M26 — see [Realized pipeline (M26)](#realized-pipeline-m26).
+> realized in M26, see [Realized pipeline (M26)](#realized-pipeline-m26).
>
> The behavioral claims here are exercised by
> [`tests/concurrency/test_spsc_stress.cpp`](../tests/concurrency/test_spsc_stress.cpp) and
@@ -23,6 +23,13 @@ single-threaded stages connected by hand-off queues:
input thread --[inbound queue]--> engine thread --[outbound queue]--> publisher/log thread
```
+```mermaid
+flowchart LR
+ it["Input thread"] -->|"inbound SPSC queue"| et["Engine thread"]
+ et -->|"outbound SPSC queue"| pt["Publisher / log thread"]
+ et -.->|"queue full: try_push fails, backpressure"| it
+```
+
Each queue has **exactly one producer and one consumer**. That is the defining condition for a
single-producer/single-consumer (SPSC) queue, so an MPMC queue would add compare-and-swap
contention, retry loops, and ABA/hazard concerns for a problem we do not have. SPSC lets each
@@ -76,8 +83,8 @@ publication visible are tabulated in `memory_ordering.md`.
is no free slot *right now*; the consumer may free one the next instant.
- Observed from the *other* side, both can be momentarily stale: a producer calling `empty()` or a
consumer calling `full()` may read a value that is already out of date. That staleness is always
- *conservative* — it can only report "more full" / "more empty" than reality, never invent
- capacity or data — so it is safe for diagnostics but must not be used as a cross-thread
+ *conservative*, it can only report "more full" / "more empty" than reality, never invent
+ capacity or data, so it is safe for diagnostics but must not be used as a cross-thread
handshake. Cross-thread coordination goes through `try_push`/`try_pop` return values, never
through `empty()`/`full()`.
@@ -89,7 +96,7 @@ view.
The queue is **bounded** and **never blocks**. `try_push` returns `false` (leaving the queue
unchanged) when the ring is full, and `try_pop` returns `false` when it is empty. The queue makes
-no policy decision about what to do next — the *caller* owns the backpressure policy. The three
+no policy decision about what to do next, the *caller* owns the backpressure policy. The three
sane policies for a producer that hits a full queue are:
| Policy | Producer does | When it is appropriate |
@@ -115,8 +122,7 @@ it, and the M26 pipeline must honor these assumptions:
1. **Lifetime brackets both threads.** The `SpscRing` must outlive both the producer and the
consumer. Construction happens-before either thread starts; destruction happens-after both have
- been `join()`ed. Destroying the ring while either side may still touch it is undefined behavior
- — the queue does not, and is not meant to, guard its own teardown.
+ been `join()`ed. Destroying the ring while either side may still touch it is undefined behavior, the queue does not, and is not meant to, guard its own teardown.
2. **Drain-then-stop.** Shutdown is signalled out-of-band (e.g. an `atomic stop` flag or a
sentinel/poison value pushed as the last item), never by destroying the queue under a running
thread. The consumer's loop is "while not stopped *or* not empty: try to pop", so it drains
@@ -152,8 +158,8 @@ host-dependent cache-line evidence, not a production throughput claim.
- **SPSC only.** One producer thread, one consumer thread. Not MPMC, not a general container.
Concurrent producers or concurrent consumers are undefined behavior by contract.
- **Bounded, fixed capacity.** No growth; `try_push` returns false when full and the caller decides
- the backpressure policy (spin, drop, or block — see *Backpressure*).
-- **Wait-free per operation — and qualified.** Each `try_push`/`try_pop` has a bounded atomic
+ the backpressure policy (spin, drop, or block, see *Backpressure*).
+- **Wait-free per operation, and qualified.** Each `try_push`/`try_pop` has a bounded atomic
protocol with no lock and no CAS retry loop; for payload types with bounded, non-blocking
copy/move assignment, the operation is wait-free end-to-end. The structural argument is in
`memory_ordering.md` → *Wait-freedom, by construction*. A caller that *spins* on `full()`/
@@ -164,7 +170,7 @@ host-dependent cache-line evidence, not a production throughput claim.
- **Statically reasoned, dynamically checked.** Correctness is argued against the C++ memory
model and exercised by sustained stress/backpressure tests under `make check` and `make asan`.
ASan/UBSan do not detect data races, so the concurrent tests are also run under ThreadSanitizer
- (`make tsan`, M27 — see *ThreadSanitizer* below).
+ (`make tsan`, M27, see *ThreadSanitizer* below).
- This is a correctness-first primitive; **no latency/throughput numbers are claimed here.** Any
such numbers will come only from the committed benchmark harness with full metadata. The M44
false-sharing study is benchmark-only and must not be generalized beyond its recorded host.
@@ -184,19 +190,19 @@ input thread --[inbound SpscRing]--> engine thread --[outbound SpscRing
| Stage | Thread owns exclusively | Consumes | Produces |
| ------------- | ------------------------------------ | ---------------------------- | ------------------------------------- |
-| Input | the command source (`vector`) | — | pushes `Command`s onto inbound |
+| Input | the command source (`vector`) | nothing | pushes `Command`s onto inbound |
| Engine | the `MatchingEngine` + `OrderGateway` | pops `Command`s from inbound | pushes `ProcessedCommand`s onto outbound |
| Publisher/log | the downstream `OutputSink` | pops from outbound | side effects (log append, feed, counters) |
Ownership transfers *with the value*: once the engine thread pushes a `ProcessedCommand` it never
touches it again, and the publisher/log thread owns it until the next pop. Crucially, the **engine
thread is the only thread that ever touches the engine**, so the deterministic single-threaded
-matching semantics are unchanged — the boundary is a value hand-off, never shared mutable state.
+matching semantics are unchanged, the boundary is a value hand-off, never shared mutable state.
### Why the downstream stage is engine-independent
The M6 `MarketDataPublisher` derives top-of-book by *reading the engine*. Running it on the
-publisher/log thread would read the engine concurrently with the engine thread mutating it — a data
+publisher/log thread would read the engine concurrently with the engine thread mutating it, a data
race. The prototype therefore has the downstream `OutputSink` consume only self-contained
`ProcessedCommand` records (the command, its accept/reject outcome, and the events it produced) and
never touch the engine. A downstream consumer that genuinely needs top-of-book would read it from
@@ -234,8 +240,8 @@ interleavings without relying on sleeps or timing-sensitive assertions.
This is a **correctness-first prototype of a concurrency boundary**, not a latency exercise: no
matching-latency or throughput number is claimed (none is measured here). It is SPSC point-to-point,
not MPMC and not a fan-out/fan-in topology. `make check` and `make asan` exercise the threaded paths,
-but ASan/UBSan do not detect data races — dynamic race detection runs under ThreadSanitizer
-(`make tsan`, M27 — see below).
+but ASan/UBSan do not detect data races, dynamic race detection runs under ThreadSanitizer
+(`make tsan`, M27, see below).
## Advanced validation (M33)
@@ -267,7 +273,7 @@ evidence over executed schedules.
ThreadSanitizer (TSan) is the dynamic complement to the static memory-ordering argument and the
ASan/UBSan build: it instruments memory accesses and synchronization at run time and reports any
-**data race** — two threads touching the same location with no happens-before edge between them and
+**data race**, two threads touching the same location with no happens-before edge between them and
at least one write. ASan/UBSan cannot see races, so TSan is the tool that actually exercises the
acquire/release reasoning in [`memory_ordering.md`](memory_ordering.md) on real schedules.
@@ -277,14 +283,14 @@ Run it with:
make tsan # equivalently: ctest --preset tsan -L concurrency
```
-The `tsan` preset builds with `-fsanitize=thread` — a *separate* build from `asan`, because the two
+The `tsan` preset builds with `-fsanitize=thread`, a *separate* build from `asan`, because the two
sanitizers instrument memory incompatibly and cannot be combined (`cmake/Sanitizers.cmake` errors if
both are enabled). It runs the `concurrency`-labelled tests: the SPSC stress and backpressure suites
and the threaded-pipeline suite. Those are the only genuinely multithreaded tests; running TSan over
single-threaded tests would add nothing.
**TSan is a correctness gate, not a performance tool.** It imposes large time/memory overhead and
-perturbs scheduling, so **no benchmark or latency number is ever collected under TSan** — measured
+perturbs scheduling, so **no benchmark or latency number is ever collected under TSan**, measured
numbers come only from the committed benchmark harness. A green TSan run means "no data race was
observed on the schedules that executed": it is dynamic evidence that *strengthens* the static
happens-before proof, not an exhaustive proof over all possible interleavings.
diff --git a/docs/differential_testing.md b/docs/differential_testing.md
index ded31d9..3e7ab58 100644
--- a/docs/differential_testing.md
+++ b/docs/differential_testing.md
@@ -110,7 +110,7 @@ level
- Independent OCaml replay (M16) and C++-vs-OCaml snapshot equality (M17) build on this schema.
-## M17 — differential replay (C++ vs OCaml)
+## M17, differential replay (C++ vs OCaml)
`ocaml/test/test_differential.ml` closes the loop: for each fixture it independently replays the
command stream in OCaml (`Replay_engine`), then compares the OCaml-computed snapshot against the
@@ -120,13 +120,13 @@ level aggregates, resting order counts, `last_seq`, and trade count (compared vi
Fixtures under test:
-- `stream_seed7.txt` — the market-like synthetic flow (GTC/IOC limits, market, cancel, modify,
+- `stream_seed7.txt`, the market-like synthetic flow (GTC/IOC limits, market, cancel, modify,
rejects, 4 symbols);
-- `stream_ioc.txt` — a hand-built scenario from `qsl-export-stream ioc` covering IOC discard
+- `stream_ioc.txt`, a hand-built scenario from `qsl-export-stream ioc` covering IOC discard
(partial and no-cross), market, and partial-maker fills;
-- `stream_bad_snapshot.txt` — a valid command stream with a deliberately corrupted snapshot
+- `stream_bad_snapshot.txt`, a valid command stream with a deliberately corrupted snapshot
section; the test asserts the mismatch **is** detected.
-- `bad_snapshot_level_symbol.txt` — a deliberately malformed snapshot where a `level` record
+- `bad_snapshot_level_symbol.txt`, a deliberately malformed snapshot where a `level` record
claims a different symbol than the surrounding `sym` block; the parser rejects it before
comparison, so malformed per-symbol ownership cannot be normalized into equality.
@@ -134,7 +134,7 @@ The check runs under the existing `ocaml-verifier` CI job via `dune runtest` (no
It is differential testing against the C++ system under test, not formal verification.
-## M18 — property-based command generator
+## M18, property-based command generator
`generate_property_flow(seed)` (C++) produces an enriched, seed-deterministic command stream
that deliberately exercises the full command space: valid limit/market orders, IOC, invalid
@@ -143,15 +143,14 @@ modifies of active and inactive orders, and multi-symbol interleavings. `qsl-exp
` exports one fixture per seed; `prop_seed1..50.txt` are committed.
The committed fifty (`prop_seed1..50`) are the regression floor; the `differential-sweep` CI job
-widens coverage per run by generating seeds `1..64` on the fly (`scripts/seed_sweep.sh`) —
-exporting each with the C++ exporter and checking it against the independent OCaml replay via
+widens coverage per run by generating seeds `1..64` on the fly (`scripts/seed_sweep.sh`), exporting each with the C++ exporter and checking it against the independent OCaml replay via
`diff_report`. New seeds need no committed fixtures, and any divergence uploads the same failure
bundle.
`test_differential.ml` discovers every `prop_*.txt` fixture (via `Sys.readdir`) and runs the
same C++-vs-OCaml snapshot equality plus a no-crossed-book invariant on each, reporting the
failing fixture/seed on divergence. The two engines agree exactly across all committed seeds
-(`prop_seed1..50`); seeds 1–8 alone already exercise every gateway/risk reject reason produced by
+(`prop_seed1..50`); seeds 1-8 alone already exercise every gateway/risk reject reason produced by
the property generator (UnknownSymbol, UnknownOrder, InvalidPrice, InvalidQuantity,
MaxQuantityExceeded, MaxNotionalExceeded, DuplicateOrderId) and real trades. `StorageExhausted`
belongs to the opt-in intrusive storage experiment, so it is not part of the baseline property
@@ -167,7 +166,7 @@ the generator produces and fails CI if any reachable reason stops occurring).
- **Mutation testing:** `test_mutation.ml` takes one representative snapshot and applies a
single-field mutation for every field (last_seq, trade count, symbol id, best bid/ask, order
count, bid
- and ask levels), asserting each changes `snapshot_to_lines` — so no field can silently drop
+ and ask levels), asserting each changes `snapshot_to_lines`, so no field can silently drop
out of the comparison. This covers every field programmatically, complementing the
hand-authored negative fixtures above.
- **Golden regeneration:** `make check-fixtures` (`scripts/check_fixtures.sh`, run in the
@@ -182,11 +181,11 @@ the generator produces and fails CI if any reachable reason stops occurring).
bumping it on a semantic generator change is a documented maintainer convention (the check
cannot infer intent from bytes), not an automatically enforced invariant.
-This is property-based differential testing against the C++ system under test — not formal
+This is property-based differential testing against the C++ system under test, not formal
verification or a proof of correctness.
-## M19 — shrinker + minimal failing fixtures
+## M19, shrinker + minimal failing fixtures
`replay::shrink(commands, predicate)` (C++) reduces a failing command stream to a small,
reviewable counterexample while preserving a failure predicate. It is greedy and deterministic,
@@ -220,7 +219,7 @@ test replays it independently.
## Minimized failing fixture (example)
`shrunk_seed1.txt` is a shrinker output (artificial "produces a trade" predicate) reduced from
-a 123-command flow to 3 commands — the minimal stream that still trades:
+a 123-command flow to 3 commands, the minimal stream that still trades:
```text
# shrink report
@@ -241,7 +240,7 @@ and the order ids to 0/1.)
## Divergence demonstration (issue #37)
The shrinker above is exercised against the artificial "produces a trade" predicate because the
-C++ engine and the OCaml oracle agree on every tested stream — there is no real divergence to
+C++ engine and the OCaml oracle agree on every tested stream, there is no real divergence to
reduce. To show the machinery on a genuine cross-language failure, we inject one: the OCaml
oracle gains a deliberately buggy mode, `replay_snapshot --drop-cancels`, that ignores cancels.
@@ -261,7 +260,7 @@ sym 0 bid - ask - orders 0
```
So a 123-command flow shrinks to a 3-command counterexample that reproduces a real C++-vs-OCaml
-snapshot mismatch — the shrinker working on an actual differential failure, not just the
+snapshot mismatch, the shrinker working on an actual differential failure, not just the
artificial predicate. The bug is confined to the opt-in `--drop-cancels` flag; the normal
differential tests are unaffected.
@@ -289,13 +288,13 @@ negative fixture, are driven by the generator, and are reduced by the shrinker.
Legend: ✓ covered · ◻ not specifically exercised.
-- **Positive** — `expect_match` on `stream_seed7`, `stream_ioc`, `shrunk_seed1`, `prop_seed1..50`;
+- **Positive**, `expect_match` on `stream_seed7`, `stream_ioc`, `shrunk_seed1`, `prop_seed1..50`;
snapshot-line equality compares all fields, so every field is positively covered.
-- **Negative** — a hand-corrupted fixture that perturbs exactly one field; the test asserts the
+- **Negative**, a hand-corrupted fixture that perturbs exactly one field; the test asserts the
divergence is detected (`expect_mismatch`), proving the comparison is not blind to that field.
-- **Property + sweep** — the generator (`generate_property_flow`) and the `differential-sweep`
+- **Property + sweep**, the generator (`generate_property_flow`) and the `differential-sweep`
CI job (seeds 1..64) populate all fields across randomized flows.
-- **Shrink** — the shrinker is field-agnostic (it reduces any failing command stream); ✓ marks
+- **Shrink**, the shrinker is field-agnostic (it reduces any failing command stream); ✓ marks
the fields that actually diverge in the demonstrated minimal counterexample from the oracle
self-test (#34). That case shrinks to a registered symbol, a resting **bid** limit, and a
cancel of it, so the cancel-dropping mutant differs only on `last_seq`, `best_bid`,
@@ -307,8 +306,8 @@ Legend: ✓ covered · ◻ not specifically exercised.
**Proves:**
-- The independent OCaml replay computes the same final snapshot as the C++ engine — best
- bid/ask, per-price level aggregates, resting order counts, `last_seq`, and trade count — over
+- The independent OCaml replay computes the same final snapshot as the C++ engine, best
+ bid/ask, per-price level aggregates, resting order counts, `last_seq`, and trade count, over
the synthetic seed, the IOC scenario, and the seeded property streams (every reject reason and
real trades exercised), so the two implementations agree across the tested command space.
- The comparison genuinely detects divergence (negative fixtures corrupting an ask level,
@@ -331,7 +330,7 @@ Legend: ✓ covered · ◻ not specifically exercised.
When the differential check fails in CI, the `ocaml-verifier` job runs `diff_report` over the
positive fixtures and uploads a `differential-failure-bundle` artifact. For each diverging
fixture it contains `.original` (the fixture), `.computed` (OCaml snapshot),
-`.expected` (C++ snapshot), and `.diff` (a line diff) — so a divergence can be
+`.expected` (C++ snapshot), and `.diff` (a line diff), so a divergence can be
debugged from the CI run without reproducing locally. `diff_report` guards each fixture
independently: a malformed or unreadable fixture is reported as a comparison failure (non-zero
exit), not allowed to abort the batch and lose the remaining fixtures' bundles (#144). The
diff --git a/docs/fix_protocol.md b/docs/fix_protocol.md
index 497d370..3f3ca7f 100644
--- a/docs/fix_protocol.md
+++ b/docs/fix_protocol.md
@@ -53,7 +53,7 @@ adapter uses the standard FIX envelope:
|-----|--------------|----------------|-------|
| 34 | MsgSeqNum | sequence no. | as above |
| 41 | OrigClOrdID | `order_id` | the order being cancelled |
-| 11 | ClOrdID | — | required by FIX; validated on decode, echoes `order_id` on encode (no separate cancel-request id is modelled) |
+| 11 | ClOrdID | (none) | required by FIX; validated on decode, echoes `order_id` on encode (no separate cancel-request id is modelled) |
| 55 | Symbol | `symbol` | decimal `SymbolId` |
## Deliberate simplifications
@@ -61,7 +61,7 @@ adapter uses the standard FIX envelope:
These are documented departures from strict FIX, chosen so the adapter stays a deterministic,
lossless map onto the simulator's internal model:
-- **Symbol (tag 55) carries the numeric `SymbolId`** in decimal, not a ticker string — the engine
+- **Symbol (tag 55) carries the numeric `SymbolId`** in decimal, not a ticker string, the engine
keys on `SymbolId`, so mapping to a string table would only add a lossy layer.
- **Price (tag 44) carries integer ticks and is always present**, including market orders. The
project never represents price as a float, and `NewOrder` always has a `price` field; carrying it
@@ -72,7 +72,7 @@ lossless map onto the simulator's internal model:
Decoding is total and deterministic: it never throws, allocates nothing on the decode path (a
fixed field table, `std::from_chars`, `std::string_view`), and reports every malformed input via
-`FixError` rather than undefined behavior — mirroring the binary codec's `DecodeError` discipline.
+`FixError` rather than undefined behavior, mirroring the binary codec's `DecodeError` discipline.
`FixError`: `None`, `Malformed`, `UnsupportedBeginString`, `UnknownMsgType`, `MissingField`,
`InvalidField`, `BodyLengthMismatch`, `ChecksumMismatch`, `InvalidEnumValue`, `OutOfRange`.
diff --git a/docs/invariants.md b/docs/invariants.md
index b89dfba..56a2fa1 100644
--- a/docs/invariants.md
+++ b/docs/invariants.md
@@ -3,7 +3,7 @@
The properties the deterministic core is expected to hold, and where each is checked. The
randomized checks run the deterministic synthetic flow (`generate_flow`) across multiple
fixed seeds, so failures are reproducible. The sanitizer build (`make asan`, ASan + UBSan)
-runs the whole suite — including the fuzz tests — so these checks also catch undefined
+runs the whole suite, including the fuzz tests, so these checks also catch undefined
behavior and out-of-bounds access.
| # | Invariant | Where tested |
@@ -22,19 +22,19 @@ behavior and out-of-bounds access.
`test_fuzz_protocol` provides layered, sanitizer-backed crash/UB guards (run under `make
asan`). It is hardening coverage, not a proof of correctness.
-- **Uniform-random hostile input** — random buffers fed to every decoder and the `Session`.
+- **Uniform-random hostile input**, random buffers fed to every decoder and the `Session`.
These almost never form a valid header, so they primarily exercise the framing /
header-rejection path and prove it is bounds-safe.
-- **Structure-aware input** — a valid header (correct version, known type, matching
+- **Structure-aware input**, a valid header (correct version, known type, matching
`body_len`) followed by random body bytes. Because the header passes validation, the result
can only be a body-level outcome (success or `InvalidEnumValue`), which proves the random
bytes actually reach the body decoder rather than being rejected at the header.
-- **Mutated valid frames** — a known-good frame corrupted in targeted, deterministic ways
+- **Mutated valid frames**, a known-good frame corrupted in targeted, deterministic ways
(single-byte flips at every offset, enum bytes, the `body_len` field, the sequence-number
bytes, trailing garbage, and truncation at every length). Each must reject deterministically
where assertable and stay crash-free everywhere.
-- **Valid-frame reassembly** — valid frames (single and coalesced) split across arbitrary
- 1–7 byte chunks. Asserts the `Session` never logs out mid-frame, withholds any response
+- **Valid-frame reassembly**, valid frames (single and coalesced) split across arbitrary
+ 1-7 byte chunks. Asserts the `Session` never logs out mid-frame, withholds any response
until a frame is complete, and yields a response byte-identical to whole delivery.
All seeds are fixed, so every randomized test is deterministic and reproducible. Decoders are
diff --git a/docs/linux_performance.md b/docs/linux_performance.md
index 4df3b2f..c8005d0 100644
--- a/docs/linux_performance.md
+++ b/docs/linux_performance.md
@@ -7,7 +7,7 @@ numbers (`results/`) are a reproducible baseline, not a production-latency claim
- Benchmark only the **bench** preset, which inherits the Release configuration
(`-O2`/`-O3`, `NDEBUG`) and disables tests. Debug numbers are meaningless for latency.
-- Sanitizer builds (ASan/UBSan, see `make asan`) are for correctness, not timing — they add
+- Sanitizer builds (ASan/UBSan, see `make asan`) are for correctness, not timing, they add
large, uneven overhead.
## Why numbers are hardware- and environment-dependent
@@ -46,12 +46,12 @@ numbers (`results/`) are a reproducible baseline, not a production-latency claim
- See `docs/perf_analysis.md` for the M29 profiling workflow, artifacts, and caveats.
The current perf artifacts are **partial hardware PMU evidence** from a bare-metal Apple MacBook Air
-(M2, aarch64) running Fedora Asahi Remix — not the earlier Docker Desktop runs. `perf stat` reads
+(M2, aarch64) running Fedora Asahi Remix, not the earlier Docker Desktop runs. `perf stat` reads
real `cycles`/`instructions`/`branches`/`branch-misses` off the Apple Avalanche/Blizzard PMUs; only
`cache-references`/`cache-misses` come back `` because the Apple Silicon PMU driver
does not expose them. Issue #90's remaining ask is therefore the cache-counter set specifically,
which needs a PMU microarchitecture that exposes those events (x86_64 Intel/AMD, or an ARM server
-core) — being bare metal is necessary but not sufficient.
+core), being bare metal is necessary but not sufficient.
## CPU affinity and locality studies
@@ -68,12 +68,12 @@ when available.
The artifact self-classifies its evidence:
-- `full-linux-numa` — NUMA-capable Linux host with `taskset`, `numactl` topology, successful
+- `full-linux-numa`. NUMA-capable Linux host with `taskset`, `numactl` topology, successful
node-local and remote-memory binding attempts, and captured unpinned and pinned scheduler
counters.
-- `linux-constrained` — Linux host where at least one required topology or scheduler signal is
+- `linux-constrained`. Linux host where at least one required topology or scheduler signal is
unavailable. Commit only when intentionally documenting the constraint.
-- `unsupported-host` — non-Linux host; no CPU-affinity, scheduler-migration, or NUMA evidence.
+- `unsupported-host`, non-Linux host; no CPU-affinity, scheduler-migration, or NUMA evidence.
Use `QSL_NUMA_ALLOW_CONSTRAINED=1` only when the committed result is intentionally constrained.
Use `QSL_NUMA_CPU=` to pin a specific CPU; otherwise the script picks the first CPU allowed by
@@ -83,7 +83,7 @@ Unsupported or constrained hosts are valid outcomes. macOS, Docker Desktop, rest
single-NUMA-node Linux machines, and hosts that can pin a CPU but cannot bind local/remote NUMA
memory should be labeled as constrained rather than used to imply full NUMA or production-latency
evidence. The committed `numa_affinity_study.txt` is now from the bare-metal Apple M2 host, which is
-a single-NUMA-node machine — so it is `linux-constrained` for NUMA purposes (real CPU pinning, but
+a single-NUMA-node machine, so it is `linux-constrained` for NUMA purposes (real CPU pinning, but
no cross-node local/remote binding to measure), not because of virtualization.
## False-sharing studies
@@ -132,4 +132,4 @@ timestamp source, queue/IRQ placement, packet shape, drops/backpressure, and sou
These are in-process microbenchmarks on a commodity machine with the standard library and a
general-purpose allocator. They are useful for regression detection and honest, order-of-
-magnitude framing — not evidence of production trading-system latency.
+magnitude framing, not evidence of production trading-system latency.
diff --git a/docs/matching_rules.md b/docs/matching_rules.md
index 127912d..25dc46f 100644
--- a/docs/matching_rules.md
+++ b/docs/matching_rules.md
@@ -4,6 +4,21 @@ Single-symbol, deterministic, price-time-priority limit order book. Implemented
`include/qsl/engine/order_book.hpp` and `src/engine/order_book.cpp`. Prices are integer
ticks; there is no wall-clock dependence (time priority is queue position).
+```mermaid
+flowchart TD
+ in["Incoming order"] --> type{"Limit or market?"}
+ type -->|Market| cross["Cross best opposite level, price-time"]
+ type -->|Limit| px{"Crosses? buy price >= best ask, or sell price <= best bid"}
+ px -->|Yes| cross
+ px -->|No| rest["Rest at price level, FIFO tail"]
+ cross --> fill{"Filled or book depleted?"}
+ fill -->|"Liquidity left"| cross
+ fill -->|"Fully filled"| done["Done, trades emitted"]
+ fill -->|"Depleted, remainder left"| tif{"IOC?"}
+ tif -->|Yes| canc["Cancel remainder"]
+ tif -->|"No, GTC"| rest
+```
+
## Book structure
- Bids are ordered highest-price-first, asks lowest-price-first (`std::map` with the
@@ -50,9 +65,9 @@ structured `DuplicateOrderId` rejection before the command reaches the engine.
| Change | Effect |
|-------------------------------------|-----------------------------------------------------|
-| Same price, **smaller** quantity | Reduced in place — **time priority preserved** |
-| Price change (any) | Re-queued at the new level tail — **priority lost**; may cross and trade immediately |
-| Quantity **increase** | Re-queued at the tail — **priority lost** |
+| Same price, **smaller** quantity | Reduced in place, **time priority preserved** |
+| Price change (any) | Re-queued at the new level tail, **priority lost**; may cross and trade immediately |
+| Quantity **increase** | Re-queued at the tail, **priority lost** |
| `new_quantity == 0` | Treated as a cancel |
Priority loss is implemented as cancel + re-add, so a repriced order that now crosses the
diff --git a/docs/memory_ordering.md b/docs/memory_ordering.md
index adc0d72..e209b09 100644
--- a/docs/memory_ordering.md
+++ b/docs/memory_ordering.md
@@ -27,11 +27,19 @@ with plain loads/stores, and the two indices are the only synchronization. Each
single-writer: `tail_` is written only by the producer, `head_` only by the consumer. That
single-writer property is what makes "load my own cursor relaxed" sound.
+```mermaid
+flowchart LR
+ w["Producer: write payload to slot k"] --> rel["Producer: store tail = k+1, release"]
+ rel ==>|"synchronizes-with"| acq["Consumer: load tail, acquire"]
+ acq --> r["Consumer: read payload from slot k"]
+ w -.->|"happens-before"| r
+```
+
## Per-operation ordering
Each side performs a fixed sequence of accesses. There are no loops and no compare-and-swap.
-### Producer — `try_push` / `emplace`
+### Producer, `try_push` / `emplace`
| Step | Access | Order | Justification |
| ---- | ---------------------------- | --------- | ---------------------------------------------------------------------- |
@@ -40,7 +48,7 @@ Each side performs a fixed sequence of accesses. There are no loops and no compa
| 3 | write `buffer_[tail]` | plain | the slot is producer-owned until step 4 publishes it |
| 4 | store `tail_ = next(tail)` | `release` | publishes step 3's write to the consumer |
-### Consumer — `try_pop`
+### Consumer, `try_pop`
| Step | Access | Order | Justification |
| ---- | ---------------------------- | --------- | ---------------------------------------------------------------------- |
@@ -90,18 +98,18 @@ producer: load head_ (acquire) ─sequenced-before→ write buffer_[h] (a lat
When the producer's full check (step 2) observes that `head_` has advanced past the slot it is
about to reuse, the consumer's read of that slot (C3) *happens-before* the producer's overwrite
(step 3 of a later push). Without the `acquire` here, the producer could overwrite a slot the
-consumer has not finished reading — a data race even though the index arithmetic looks fine. This
+consumer has not finished reading, a data race even though the index arithmetic looks fine. This
is why step 2 is `acquire` and not `relaxed`.
The one spare slot (`kSlots = Capacity + 1`) guarantees the producer can only catch up to, never
pass, the consumer, so "the slot I am about to reuse" is always the slot the consumer most
-recently freed — exactly the slot direction 2 protects.
+recently freed, exactly the slot direction 2 protects.
## Why `relaxed` is enough for the own-cursor loads
`tail_` has a single writer (the producer) and `head_` has a single writer (the consumer). A
thread loading its own cursor is reading a value only it could have changed, in program order, so
-no inter-thread ordering is required — `relaxed` suffices and avoids a needless fence. The
+no inter-thread ordering is required, `relaxed` suffices and avoids a needless fence. The
cross-thread edges are carried entirely by the four `acquire`/`release` accesses above.
## Why not `seq_cst`
@@ -119,8 +127,8 @@ The header describes `try_push`/`try_pop` as **wait-free per operation** for pay
copy/move assignment is itself bounded and non-blocking. The justification is structural, not
benchmarked:
-- Each operation executes a *fixed, bounded* number of steps in the queue protocol itself — two
- atomic loads, one branch, one plain memory access, one atomic store — regardless of what the
+- Each operation executes a *fixed, bounded* number of steps in the queue protocol itself, two
+ atomic loads, one branch, one plain memory access, one atomic store, regardless of what the
other thread is doing.
- There is **no loop** and **no CAS retry** inside either operation, so there is no execution in
which a thread is starved or must retry because of contention.
diff --git a/docs/ocaml_verifier.md b/docs/ocaml_verifier.md
index 8605c92..c46f43b 100644
--- a/docs/ocaml_verifier.md
+++ b/docs/ocaml_verifier.md
@@ -2,23 +2,35 @@
An independent replay-invariant checker for exported exchange event logs, written in OCaml
(`ocaml/`). It is deliberately small and external: this *log verifier* (`verify_replay`, M14) does
-**not** re-implement the matching engine and does **not** prove the engine correct — it re-derives a
+**not** re-implement the matching engine and does **not** prove the engine correct, it re-derives a
set of replay invariants from a normalized event-log fixture and reports pass/fail.
The OCaml side also includes a separate, stronger component: an **independent replay engine**
(`ocaml/lib/replay_engine.ml`, M16) that *does* re-implement price-time matching (GTC/IOC/market/
cancel/modify plus gateway risk) from the command stream and computes its own final snapshot, which
-the differential tests assert equals the C++ snapshot. That engine — not this verifier — is the
+the differential tests assert equals the C++ snapshot. That engine, not this verifier, is the
matching-independent oracle; see [`differential_testing.md`](differential_testing.md). This page
covers only the log-invariant checker.
+```mermaid
+flowchart LR
+ cmds["Command stream, seeded generator"] --> cpp["C++ MatchingEngine"]
+ cmds --> ml["OCaml replay_engine, independent"]
+ cpp --> s1["C++ snapshot"]
+ ml --> s2["OCaml snapshot"]
+ s1 --> cmp{"Equal? best bid/ask, level aggregates, counts, trades, last seq"}
+ s2 --> cmp
+ cmp -->|No| shrink["Shrink to minimal counterexample"]
+ cmp -->|Yes| pass["Differential pass"]
+```
+
## Why a second language
The C++ tests and the engine share code and assumptions; a bug in a shared assumption can hide
from tests written against the same model. A verifier written independently, in a typed
functional language with immutable data, is a cheap cross-check: it parses the *output* of the
C++ pipeline and validates properties that must hold regardless of how the engine is
-implemented. The signal is an independent checker in a typed functional language — not a claim
+implemented. The signal is an independent checker in a typed functional language, not a claim
of OCaml mastery or formal verification.
## Fixture format
@@ -43,13 +55,13 @@ summary last_seq trades # engine-reported totals
Each is recomputed from the raw records, independently of the engine:
-1. **Sequence strictly increasing** — event sequence numbers are monotonic.
-2. **Positive trade quantity** — no zero/negative trade quantities.
-3. **Canceled order cannot later trade** — an id is canceled only for its *current* lifetime;
+1. **Sequence strictly increasing**, event sequence numbers are monotonic.
+2. **Positive trade quantity**, no zero/negative trade quantities.
+3. **Canceled order cannot later trade**, an id is canceled only for its *current* lifetime;
trading it counts as a violation only if there is no later `accept` re-establishing it.
-4. **Rejected order never rests or trades** — a rejected attempt never entered the engine, so
+4. **Rejected order never rests or trades**, a rejected attempt never entered the engine, so
the id must not rest (`cancel`/`modify`) or trade *until* a later `accept` reuses it.
-5. **Summary matches event log** — the reported `last_seq` equals the maximum event sequence
+5. **Summary matches event log**, the reported `last_seq` equals the maximum event sequence
and the reported trade count equals the number of `trade` records.
## OrderId lifetimes
@@ -58,7 +70,7 @@ OrderId uniqueness in this system is scoped to currently-active resting orders,
global history:
- A rejected attempt never enters the engine, so the same numeric id may later be submitted
- and accepted — the rejected attempt does not permanently tombstone the id.
+ and accepted, the rejected attempt does not permanently tombstone the id.
- A canceled (or fully-filled) order leaves the active set, so its id may be reused by a later
accept, which begins a new valid lifetime.
@@ -72,7 +84,7 @@ logs that are valid under the engine's active-order semantics.
Beyond the log-invariant checker above, `ocaml/lib/replay_engine.ml` is an **independent**
matching engine: it consumes an M15 command-stream fixture (`stream_parser.ml` parses the
`meta` risk config and `cmd` lines, ignoring the C++ `evt`/`snapshot` output) and replays it
-immutably to compute its own final snapshot — it does not trust the C++ engine's emitted
+immutably to compute its own final snapshot, it does not trust the C++ engine's emitted
events. It mirrors the C++ semantics so the snapshots can be compared:
- integer ticks; bids best = highest, asks best = lowest; FIFO within a level; fills at the
@@ -113,7 +125,7 @@ transcribes C++ logic, agreement proves nothing. This is an honest audit of wher
**Deliberately mirrored (a *shared* error here would NOT be caught):**
- **Integer notional rule.** `check_limit_values` rejects when `qty > max_notional / price`
- using truncating integer division — the exact C++ formula. A shared off-by-one in this
+ using truncating integer division, the exact C++ formula. A shared off-by-one in this
truncation is invisible to the differential. *(Highest-risk mirror.)*
- **Modify priority rule.** `modify_book` keeps queue priority only for a same-price reduction
(`new_price = price0 && new_qty <= resting_qty`), treats a price change or quantity increase
@@ -121,7 +133,7 @@ transcribes C++ logic, agreement proves nothing. This is an honest audit of wher
matched to C++ by construction.
- **Sequence-number accounting.** `put_book` advances `seq` by `1 + #trades` on accept,
`1` on cancel, `1 + #trades` on modify, and `0` on reject. `last_seq` is compared, so a
- divergence is caught — but the *rule itself* was written to match, not re-specified.
+ divergence is caught, but the *rule itself* was written to match, not re-specified.
- **Validation gating and snapshot shape.** The check order (unknown symbol → duplicate active
id → value checks), the active-lifetime id scoping, and "every registered symbol appears,
ordered by id ascending" are all mirrored conventions.
@@ -147,14 +159,14 @@ snapshot-equality oracle.
- This checks **invariants over the exported log**, not full book-state re-computation. It does
not independently re-run price-time matching, so it cannot by itself confirm best bid/ask or
- resting quantities — those are covered on the C++ side (`docs/invariants.md`, replay-equivalence
+ resting quantities, those are covered on the C++ side (`docs/invariants.md`, replay-equivalence
tests). The OCaml side ties the event log to the engine's reported summary.
- It is **not** formal verification and makes **no** correctness proof; it is reproducible,
deterministic invariant checking on fixed-seed fixtures.
## Build and run
-Local toolchain: OCaml + dune (e.g. `brew install ocaml dune`; no opam required — only the
+Local toolchain: OCaml + dune (e.g. `brew install ocaml dune`; no opam required, only the
standard library is used).
```bash
diff --git a/docs/perf_analysis.md b/docs/perf_analysis.md
index cb12958..5dbd1a6 100644
--- a/docs/perf_analysis.md
+++ b/docs/perf_analysis.md
@@ -9,14 +9,14 @@ The Linux `perf` workflow provides Linux-only tooling, metadata-rich artifacts,
handling, PMU preflight/validation, a three-way evidence classification (full / partial / none),
CI validation, and a reproducible command path.
-The committed artifacts are now generated on a **bare-metal Linux host** — an Apple MacBook Air
+The committed artifacts are now generated on a **bare-metal Linux host**, an Apple MacBook Air
(M2, aarch64) running Fedora Asahi Remix, directly on the hardware (`systemd-detect-virt` reports
`none`, no `hypervisor` CPU flag). On this heterogeneous SoC `perf` opens each event against both
-PMU instances — the Apple Avalanche (P-core) and Blizzard (E-core) PMUs — but the single-threaded
+PMU instances, the Apple Avalanche (P-core) and Blizzard (E-core) PMUs, but the single-threaded
benchmark is scheduled on the performance cores, so **the Avalanche counters carry the real
counts**: `cycles`, `instructions`, `branches`, and `branch-misses` are live there. The
corresponding `apple_blizzard_pmu/...` rows read `` in `results/perf_stat_linux.txt`
-because the workload never ran on the E-cores — that is expected scheduling behavior, not a missing
+because the workload never ran on the E-cores, that is expected scheduling behavior, not a missing
counter. The artifact is therefore classified **partial hardware PMU evidence**, not
constrained-environment validation: the counters that are present are real, not emulated.
@@ -24,9 +24,9 @@ The residual gap is specific and is what issue #90 now tracks: the Apple Silicon
by the current Asahi kernel driver, does **not** implement the generic `cache-references` /
`cache-misses` events (it whitelists only `cycles`/`instructions`/branch events; the
`/sys/bus/event_source/devices/apple_avalanche_pmu/events/` directory lists only `cycles` and
-`instructions`). So a *full* counter set — including cache events — is unavailable here. Closing
+`instructions`). So a *full* counter set, including cache events, is unavailable here. Closing
#90 needs a PMU **microarchitecture** that exposes cache counters to Linux (e.g. an x86_64
-Intel/AMD host, or an ARM server core such as Graviton/Ampere) — not "more bare metal."
+Intel/AMD host, or an ARM server core such as Graviton/Ampere), not "more bare metal."
## Commands
@@ -69,28 +69,28 @@ folds the samples and renders an SVG to `results/flamegraph.svg` plus a text com
`results/flamegraph.txt` (provenance, classification, and the top folded stacks). Frame-pointer
unwinding is used because it produces clean, fully-symbolized stacks: the earlier DWARF default left
`[unknown]` gaps (the Release build omits frame pointers and DWARF unwinding truncated deep stacks).
-The dedicated build keeps the latency `bench` numbers in `results/latest.txt` untouched — they come
+The dedicated build keeps the latency `bench` numbers in `results/latest.txt` untouched, they come
from the unmodified Release `bench` preset.
Two design points address common flamegraph problems:
-- **Sample density and duration.** The artifact profiles `qsl-bench profile [seconds]` — a warm,
+- **Sample density and duration.** The artifact profiles `qsl-bench profile [seconds]`, a warm,
bounded, deterministic steady-state order flow (add / cross / cancel / modify, book held ~512
- deep) — for 5s by default, so the capture carries tens of thousands of samples instead of the
+ deep), for 5s by default, so the capture carries tens of thousands of samples instead of the
~80ms (~329-sample) one-shot benchmark suite. `QSL_FLAMEGRAPH_SECONDS` tunes the duration and
`QSL_BENCH_STORAGE={baseline,pooled,intrusive,contiguous}` selects the order-book storage mode.
- **No `[unknown]` frames.** Frame-pointer unwinding resolves the whole application and C-runtime
startup chain. The one residual unresolvable frame is the glibc allocator boundary (Fedora's libc
is built without frame pointers), so `flamegraph.py` folds a lone `[unknown]` frame into its
- caller by default — the sample is preserved and the real neighbours (the app frame and the named
+ caller by default, the sample is preserved and the real neighbours (the app frame and the named
libc symbol such as `cfree` / `operator new`) stay in the stack. Pass `--keep-unknown` to disable
the fold.
The folding and SVG rendering live in `scripts/flamegraph.py`, a dependency-free Python script
(standard library only) that reimplements the `stackcollapse` + flamegraph data model rather than
vendoring Brendan Gregg's Perl toolkit, so the artifact is reproducible from this repository alone.
-The renderer is deterministic — frames are sorted by name and colors are a pure function of the
-frame name (no RNG, no timestamps in the drawn body) — and is unit-tested in
+The renderer is deterministic, frames are sorted by name and colors are a pure function of the
+frame name (no RNG, no timestamps in the drawn body), and is unit-tested in
`tests/shell/test_flamegraph.sh` (registered with CTest, runs under `make check`). Frame width is
proportional to on-CPU samples; this is a software cpu-clock sampling profile for **hot-symbol
investigation**, not a latency or throughput measurement. Set `QSL_FLAMEGRAPH_EVENT=cycles` to
@@ -108,8 +108,8 @@ unless the kernel and permissions expose the requested PMU events. On many syste
this Apple Silicon host is bare metal yet its PMU driver still does not expose cache events.
Full hardware-counter evidence requires a host whose PMU exposes the **whole** requested event set
-(including `cache-references`/`cache-misses`) through `perf_event` — e.g. an x86_64 Intel/AMD box or
-an ARM server core — not merely any bare-metal Linux machine. Before trusting a `perf stat` artifact
+(including `cache-references`/`cache-misses`) through `perf_event`, e.g. an x86_64 Intel/AMD box or
+an ARM server core, not merely any bare-metal Linux machine. Before trusting a `perf stat` artifact
as full evidence,
verify:
@@ -123,20 +123,20 @@ perf stat -e cycles,instructions,branches,branch-misses,cache-references,cache-m
The script classifies the artifact three ways via its `Artifact:` and `Hardware counters
supported:` fields:
-- **`hardware PMU evidence`** (`Hardware counters supported: yes`) — every requested counter,
+- **`hardware PMU evidence`** (`Hardware counters supported: yes`), every requested counter,
including cache events, was captured. This is *full* evidence.
-- **`partial hardware PMU evidence`** (`Hardware counters supported: partial`) — at least one real
+- **`partial hardware PMU evidence`** (`Hardware counters supported: partial`), at least one real
hardware counter was captured but the requested set is incomplete. This is the **current state on
the Apple Silicon host**: real `cycles`/`instructions`/`branches`/`branch-misses`, with
`cache-references`/`cache-misses` reported ``.
- **`constrained-environment validation (no hardware PMU access)`** (`Hardware counters supported:
- no`) — no hardware counter produced a value at all (a VM/container with no PMU, or a
+ no`), no hardware counter produced a value at all (a VM/container with no PMU, or a
permission-denied host). This was the state of the earlier Docker Desktop artifacts.
A *full* artifact is acceptable only when it reports `Artifact: hardware PMU evidence`,
`Unsupported counters detected: no`, `Hardware counters supported: yes`, and `Dirty inputs: no`.
On Apple Silicon that bar cannot be met for the cache events, so the honest current label is
-**partial**, not full — and that is recorded in the artifact rather than papered over.
+**partial**, not full, and that is recorded in the artifact rather than papered over.
If the host reports counters as unsupported or permission-denied, do not substitute other numbers.
The scripts only write a partial/constrained artifact with `QSL_PERF_ALLOW_PARTIAL=1`, and the
@@ -174,7 +174,7 @@ itself, so a two-artifact run can remain honest while still detecting real sourc
The committed artifacts are now bare-metal Apple Silicon Linux runs labeled **partial hardware PMU
evidence**: real `cycles`/`instructions`/`branches`/`branch-misses` plus `` cache
events. Do not describe them as *full* hardware-counter evidence (the cache events are missing), and
-do not relabel them back to "constrained validation" either — the counters that are present are
+do not relabel them back to "constrained validation" either, the counters that are present are
genuine bare-metal hardware counters.
## What To Look For
diff --git a/docs/performance/after.svg b/docs/performance/after.svg
index 7552c32..658eff5 100644
--- a/docs/performance/after.svg
+++ b/docs/performance/after.svg
@@ -1,5 +1,5 @@
-
+]]>QSL matching-engine hot path: AFTER (try_emplace + index load-factor 0.25)qsl-perfeval steady-state deep book, baseline storage, aarch64 Apple M2, fp unwinding, every frame resolvedSearchall (21846 samples, 100.00%)allqsl-perfeval (21846 samples, 100.00%)qsl-perfeval_start (21846 samples, 100.00%)_start__libc_start_main@@GLIBC_2.34 (21843 samples, 99.99%)__libc_start_main@@GLIBC_2.34__libc_start_call_main (21843 samples, 99.99%)__libc_start_call_mainmain (21843 samples, 99.99%)maincfree@GLIBC_2.17 (65 samples, 0.30%)qsl::engine::MatchingEngine::cancel(unsigned int, unsigned long) (3015 samples, 13.80%)qsl::engine::Matchin..qsl::engine::OrderBook::cancel(unsigned long) (2669 samples, 12.22%)qsl::engine::Orde..decltype(auto) qsl::engine::OrderBook::dispatch_storage<qsl::engine::OrderBook::cancel(unsigned long)::{lambda()#1}, qsl::engine::OrderBook::cancel(unsigned long)::{lambda(qsl::engine::OrderBook::IntrusiveStore&)#1}, qsl::engine::OrderBook::cancel(unsigned long)::{lambda(qsl::engine::OrderBook::ContiguousStore&)#1}>(qsl::engine::OrderBook::cancel(unsigned long)::{lambda()#1}&&, qsl::engine::OrderBook::cancel(unsigned long)::{lambda(qsl::engine::OrderBook::IntrusiveStore&)#1}&&, qsl::engine::OrderBook::cancel(unsigned long)::{lambda(qsl::engine::OrderBook::ContiguousStore&)#1}&&) [clone .isra.0] (2622 samples, 12.00%)decltype(auto) qs..cfree@GLIBC_2.17 (58 samples, 0.27%)qsl::engine::OrderBook::erase_resting_order(qsl::engine::OrderBook::Locator const&) (1256 samples, 5.75%)qsl::e..cfree@GLIBC_2.17 (87 samples, 0.40%)free@plt (28 samples, 0.13%)operator delete(void*, std::align_val_t)@plt (34 samples, 0.16%)operator delete(void*, unsigned long, std::align_val_t)@plt (30 samples, 0.14%)std::__detail::_List_node_base::_M_unhook() (25 samples, 0.11%)std::__detail::_List_node_base::_M_unhook()@plt (38 samples, 0.17%)std::pmr::(anonymous namespace)::newdel_res_t::do_deallocate(void*, unsigned long, unsigned long) (10 samples, 0.05%)std::_Hashtable<unsigned long, std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, std::pmr::polymorphic_allocator<std::pair<unsigned long const, qsl::engine::OrderBook::Locator> >, std::__detail::_Select1st, std::equal_to<unsigned long>, std::hash<unsigned long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_erase(unsigned long, std::__detail::_Hash_node_base*, std::__detail::_Hash_node<std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, false>*) (335 samples, 1.53%)cfree@GLIBC_2.17 (95 samples, 0.43%)free@plt (41 samples, 0.19%)operator delete(void*, std::align_val_t)@plt (28 samples, 0.13%)operator delete(void*, unsigned long, std::align_val_t)@plt (62 samples, 0.28%)std::pmr::(anonymous namespace)::newdel_res_t::do_deallocate(void*, unsigned long, unsigned long) (28 samples, 0.13%)qsl::engine::MatchingEngine::contains(unsigned int, unsigned long) const (307 samples, 1.41%)qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce) (17918 samples, 82.02%)qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce)_int_free_chunk (8 samples, 0.04%)_int_free_merge_chunk (7 samples, 0.03%)_int_free_create_chunk (3 samples, 0.01%)cfree@GLIBC_2.17 (254 samples, 1.16%)free@plt (49 samples, 0.22%)qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce) (15966 samples, 73.08%)qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce)__memcpy_generic (177 samples, 0.81%)_int_free_chunk (4 samples, 0.02%)cfree@GLIBC_2.17 (412 samples, 1.89%)decltype(auto) qsl::engine::OrderBook::dispatch_storage<qsl::engine::OrderBook::contains(unsigned long) const::{lambda()#1}, qsl::engine::OrderBook::contains(unsigned long) const::{lambda(qsl::engine::OrderBook::IntrusiveStore const&)#1}, qsl::engine::OrderBook::contains(unsigned long) const::{lambda(qsl::engine::OrderBook::ContiguousStore const&)#1}>(qsl::engine::OrderBook::contains(unsigned long) const::{lambda()#1}&&, qsl::engine::OrderBook::contains(unsigned long) const::{lambda(qsl::engine::OrderBook::IntrusiveStore const&)#1}&&, qsl::engine::OrderBook::contains(unsigned long) const::{lambda(qsl::engine::OrderBook::ContiguousStore const&)#1}&&) const [clone .isra.0] (277 samples, 1.27%)free@plt (102 samples, 0.47%)memcpy@plt (31 samples, 0.14%)operator delete(void*, unsigned long) (4 samples, 0.02%)operator new(unsigned long) (520 samples, 2.38%)o..__aarch64_ldadd8_relax (110 samples, 0.50%)__libc_malloc2 (27 samples, 0.12%)_int_malloc (23 samples, 0.11%)malloc (269 samples, 1.23%)malloc@plt (47 samples, 0.22%)operator new(unsigned long, std::align_val_t) (2 samples, 0.01%)qsl::engine::OrderBook::level_for[abi:cxx11](qsl::core::Side, long) (2 samples, 0.01%)qsl::engine::OrderBook::match_baseline(qsl::core::Side, qsl::engine::OrderBook::MatchContext&) (6912 samples, 31.64%)qsl::engine::OrderBook::match_baseline(qsl::core::..__memcpy_generic (57 samples, 0.26%)_int_free_chunk (43 samples, 0.20%)_int_free_maybe_trim (3 samples, 0.01%)_int_free_merge_chunk (33 samples, 0.15%)_int_free_create_chunk (17 samples, 0.08%)unlink_chunk.isra.0 (3 samples, 0.01%)_int_free_maybe_trim (2 samples, 0.01%)cfree@GLIBC_2.17 (449 samples, 2.06%)free@plt (99 samples, 0.45%)memcpy@plt (19 samples, 0.09%)operator delete(void*, std::align_val_t)@plt (88 samples, 0.40%)operator delete(void*, unsigned long, std::align_val_t)@plt (125 samples, 0.57%)operator new(unsigned long) (377 samples, 1.73%)__aarch64_ldadd8_relax (82 samples, 0.38%)__libc_malloc2 (15 samples, 0.07%)_int_malloc (14 samples, 0.06%)malloc (178 samples, 0.81%)malloc@plt (33 samples, 0.15%)qsl::engine::OrderBook::fill_front_order(std::__cxx11::list<qsl::engine::Order, std::pmr::polymorphic_allocator<qsl::engine::Order> >&, long, qsl::engine::OrderBook::MatchContext&) (1746 samples, 7.99%)qsl::engin..__memcpy_generic (59 samples, 0.27%)_int_free_chunk (42 samples, 0.19%)_int_free_create_chunk (4 samples, 0.02%)_int_free_maybe_trim (3 samples, 0.01%)_int_free_merge_chunk (26 samples, 0.12%)_int_free_create_chunk (15 samples, 0.07%)cfree@GLIBC_2.17 (270 samples, 1.24%)free@plt (45 samples, 0.21%)memcpy@plt (11 samples, 0.05%)operator delete(void*, std::align_val_t)@plt (36 samples, 0.16%)operator delete(void*, unsigned long, std::align_val_t)@plt (55 samples, 0.25%)operator new(unsigned long) (317 samples, 1.45%)__aarch64_ldadd8_relax (50 samples, 0.23%)__libc_malloc2 (5 samples, 0.02%)_int_malloc (4 samples, 0.02%)malloc (173 samples, 0.79%)malloc@plt (29 samples, 0.13%)std::_Hashtable<unsigned long, std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, std::pmr::polymorphic_allocator<std::pair<unsigned long const, qsl::engine::OrderBook::Locator> >, std::__detail::_Select1st, std::equal_to<unsigned long>, std::hash<unsigned long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_erase(unsigned long, std::__detail::_Hash_node_base*, std::__detail::_Hash_node<std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, false>*) (300 samples, 1.37%)_int_free_chunk (14 samples, 0.06%)_int_free_create_chunk (2 samples, 0.01%)_int_free_merge_chunk (10 samples, 0.05%)_int_free_create_chunk (9 samples, 0.04%)cfree@GLIBC_2.17 (93 samples, 0.43%)free@plt (25 samples, 0.11%)operator delete(void*, std::align_val_t)@plt (29 samples, 0.13%)operator delete(void*, unsigned long, std::align_val_t) (6 samples, 0.03%)operator delete(void*, unsigned long, std::align_val_t)@plt (44 samples, 0.20%)std::pmr::(anonymous namespace)::newdel_res_t::do_deallocate(void*, unsigned long, unsigned long) (9 samples, 0.04%)std::__detail::_List_node_base::_M_unhook() (30 samples, 0.14%)std::__detail::_List_node_base::_M_unhook()@plt (22 samples, 0.10%)std::pmr::(anonymous namespace)::newdel_res_t::do_deallocate(void*, unsigned long, unsigned long) (19 samples, 0.09%)std::_Hashtable<unsigned long, std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, std::pmr::polymorphic_allocator<std::pair<unsigned long const, qsl::engine::OrderBook::Locator> >, std::__detail::_Select1st, std::equal_to<unsigned long>, std::hash<unsigned long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_erase(unsigned long, std::__detail::_Hash_node_base*, std::__detail::_Hash_node<std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, false>*) (353 samples, 1.62%)_int_free_chunk (14 samples, 0.06%)_int_free_maybe_trim (2 samples, 0.01%)_int_free_merge_chunk (11 samples, 0.05%)_int_free_create_chunk (7 samples, 0.03%)cfree@GLIBC_2.17 (114 samples, 0.52%)free@plt (29 samples, 0.13%)operator delete(void*, std::align_val_t)@plt (29 samples, 0.13%)operator delete(void*, unsigned long, std::align_val_t)@plt (47 samples, 0.22%)std::pmr::(anonymous namespace)::newdel_res_t::do_deallocate(void*, unsigned long, unsigned long) (14 samples, 0.06%)std::_Rb_tree_rebalance_for_erase(std::_Rb_tree_node_base*, std::_Rb_tree_node_base&) (678 samples, 3.10%)st..std::_Rb_tree_rebalance_for_erase(std::_Rb_tree_node_base*, std::_Rb_tree_node_base&)@plt (13 samples, 0.06%)std::__detail::_List_node_base::_M_unhook() (28 samples, 0.13%)std::__detail::_List_node_base::_M_unhook()@plt (17 samples, 0.08%)std::pmr::(anonymous namespace)::newdel_res_t::do_deallocate(void*, unsigned long, unsigned long) (41 samples, 0.19%)qsl::engine::OrderBook::rest(unsigned long, qsl::core::Side, long, unsigned int) (6680 samples, 30.58%)qsl::engine::OrderBook::rest(unsigned long, qsl:..__posix_memalign (4 samples, 0.02%)operator new(unsigned long, std::align_val_t) (877 samples, 4.01%)ope..__posix_memalign (565 samples, 2.59%)_..__libc_malloc2 (75 samples, 0.34%)_int_malloc (67 samples, 0.31%)unlink_chunk.isra.0 (2 samples, 0.01%)_mid_memalign (86 samples, 0.39%)malloc (233 samples, 1.07%)_mid_memalign (44 samples, 0.20%)posix_memalign@plt (53 samples, 0.24%)operator new(unsigned long, std::align_val_t)@plt (101 samples, 0.46%)qsl::engine::OrderBook::level_for[abi:cxx11](qsl::core::Side, long) (3713 samples, 17.00%)qsl::engine::OrderBook::l..operator new(unsigned long, std::align_val_t) (397 samples, 1.82%)__posix_memalign (246 samples, 1.13%)__libc_malloc2 (3 samples, 0.01%)_int_malloc (2 samples, 0.01%)_mid_memalign (50 samples, 0.23%)malloc (106 samples, 0.49%)_mid_memalign (27 samples, 0.12%)posix_memalign@plt (25 samples, 0.11%)operator new(unsigned long, std::align_val_t)@plt (81 samples, 0.37%)std::_Rb_tree_decrement(std::_Rb_tree_node_base*) (81 samples, 0.37%)std::_Rb_tree_decrement(std::_Rb_tree_node_base*)@plt (6 samples, 0.03%)std::_Rb_tree_insert_and_rebalance(bool, std::_Rb_tree_node_base*, std::_Rb_tree_node_base*, std::_Rb_tree_node_base&) (542 samples, 2.48%)s..std::_Rb_tree_insert_and_rebalance(bool, std::_Rb_tree_node_base*, std::_Rb_tree_node_base*, std::_Rb_tree_node_base&)@plt (53 samples, 0.24%)std::pmr::(anonymous namespace)::newdel_res_t::do_allocate(unsigned long, unsigned long) (22 samples, 0.10%)std::__detail::_List_node_base::_M_hook(std::__detail::_List_node_base*) (46 samples, 0.21%)std::__detail::_List_node_base::_M_hook(std::__detail::_List_node_base*)@plt (58 samples, 0.27%)std::__detail::_Map_base<unsigned long, std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, std::pmr::polymorphic_allocator<std::pair<unsigned long const, qsl::engine::OrderBook::Locator> >, std::__detail::_Select1st, std::equal_to<unsigned long>, std::hash<unsigned long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true>, true>::operator[](unsigned long const&) (1679 samples, 7.69%)std::__det..__posix_memalign (33 samples, 0.15%)operator new(unsigned long, std::align_val_t) (814 samples, 3.73%)ope..__posix_memalign (520 samples, 2.38%)_..__libc_malloc2 (17 samples, 0.08%)_int_malloc (15 samples, 0.07%)_mid_memalign (107 samples, 0.49%)malloc (261 samples, 1.19%)_mid_memalign (53 samples, 0.24%)posix_memalign@plt (45 samples, 0.21%)operator new(unsigned long, std::align_val_t)@plt (153 samples, 0.70%)std::_Hashtable<unsigned long, std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, std::pmr::polymorphic_allocator<std::pair<unsigned long const, qsl::engine::OrderBook::Locator> >, std::__detail::_Select1st, std::equal_to<unsigned long>, std::hash<unsigned long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_insert_unique_node(unsigned long, unsigned long, std::__detail::_Hash_node<std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, false>*, unsigned long) (343 samples, 1.57%)std::__detail::_Prime_rehash_policy::_M_need_rehash(unsigned long, unsigned long, unsigned long) const (61 samples, 0.28%)std::__detail::_Prime_rehash_policy::_M_need_rehash(unsigned long, unsigned long, unsigned long) const@plt (50 samples, 0.23%)std::pmr::(anonymous namespace)::newdel_res_t::do_allocate(unsigned long, unsigned long) (32 samples, 0.15%)qsl::engine::OrderBook::can_store_limit(qsl::core::Side, long, unsigned int, qsl::core::TimeInForce) const (286 samples, 1.31%)qsl::engine::OrderBook::contains(unsigned long) const (253 samples, 1.16%)qsl::engine::OrderBook::contains(unsigned long) const (319 samples, 1.46%)_dl_start (3 samples, 0.01%)_dl_sysdep_start (3 samples, 0.01%)dl_main (3 samples, 0.01%)_dl_relocate_object (2 samples, 0.01%)_dl_relocate_object_no_relro (2 samples, 0.01%)_dl_lookup_symbol_x (2 samples, 0.01%)do_lookup_x (2 samples, 0.01%)
diff --git a/docs/performance/before.svg b/docs/performance/before.svg
index 4a0e422..0a53819 100644
--- a/docs/performance/before.svg
+++ b/docs/performance/before.svg
@@ -1,5 +1,5 @@
-
+]]>QSL matching-engine hot path: v0.1.0 baseline (first release)qsl-perfeval steady-state deep book, baseline storage, aarch64 Apple M2, fp unwinding, every frame resolvedSearchall (22888 samples, 100.00%)allqsl-perfeval (22888 samples, 100.00%)qsl-perfeval_start (22888 samples, 100.00%)_start__libc_start_main@@GLIBC_2.34 (22885 samples, 99.99%)__libc_start_main@@GLIBC_2.34__libc_start_call_main (22885 samples, 99.99%)__libc_start_call_maincfree@GLIBC_2.17 (128 samples, 0.56%)main (22743 samples, 99.37%)main_int_free_chunk (4 samples, 0.02%)cfree@GLIBC_2.17 (736 samples, 3.22%)cf..free@plt (65 samples, 0.28%)operator new(unsigned long) (6 samples, 0.03%)qsl::engine::MatchingEngine::cancel(unsigned int, unsigned long) (3535 samples, 15.44%)qsl::engine::MatchingEn..cfree@GLIBC_2.17 (26 samples, 0.11%)operator new(unsigned long) (202 samples, 0.88%)__aarch64_ldadd8_relax (37 samples, 0.16%)malloc (88 samples, 0.38%)malloc@plt (18 samples, 0.08%)qsl::engine::OrderBook::cancel(unsigned long) (2878 samples, 12.57%)qsl::engine::Order.._int_free_chunk (2 samples, 0.01%)_int_free_merge_chunk (2 samples, 0.01%)cfree@GLIBC_2.17 (120 samples, 0.52%)free@plt (41 samples, 0.18%)std::_Hashtable<unsigned long, std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, std::allocator<std::pair<unsigned long const, qsl::engine::OrderBook::Locator> >, std::__detail::_Select1st, std::equal_to<unsigned long>, std::hash<unsigned long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_erase(unsigned long, std::__detail::_Hash_node_base*, std::__detail::_Hash_node<std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, false>*) (191 samples, 0.83%)_int_free_chunk (2 samples, 0.01%)cfree@GLIBC_2.17 (95 samples, 0.42%)free@plt (33 samples, 0.14%)std::__detail::_List_node_base::_M_unhook() (24 samples, 0.10%)std::__detail::_List_node_base::_M_unhook()@plt (36 samples, 0.16%)qsl::engine::MatchingEngine::contains(unsigned int, unsigned long) const (171 samples, 0.75%)qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce) (17569 samples, 76.76%)qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce)_int_free_chunk (41 samples, 0.18%)_int_free_maybe_trim (5 samples, 0.02%)_int_free_merge_chunk (31 samples, 0.14%)_int_free_create_chunk (15 samples, 0.07%)cfree@GLIBC_2.17 (502 samples, 2.19%)free@plt (88 samples, 0.38%)operator new(unsigned long) (1482 samples, 6.48%)operator..__aarch64_ldadd8_relax (252 samples, 1.10%)malloc (660 samples, 2.88%)ma..malloc@plt (133 samples, 0.58%)qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce) (12861 samples, 56.19%)qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::..cfree@GLIBC_2.17 (129 samples, 0.56%)qsl::engine::OrderBook::rest(unsigned long, qsl::core::Side, long, unsigned int) (5809 samples, 25.38%)qsl::engine::OrderBook::rest(unsigned l..operator new(unsigned long) (1027 samples, 4.49%)oper..__aarch64_ldadd8_relax (224 samples, 0.98%)__libc_malloc2 (64 samples, 0.28%)_int_malloc (58 samples, 0.25%)unlink_chunk.isra.0 (3 samples, 0.01%)malloc (480 samples, 2.10%)malloc@plt (67 samples, 0.29%)std::_Rb_tree_decrement(std::_Rb_tree_node_base*) (111 samples, 0.48%)std::_Rb_tree_decrement(std::_Rb_tree_node_base*)@plt (11 samples, 0.05%)std::_Rb_tree_insert_and_rebalance(bool, std::_Rb_tree_node_base*, std::_Rb_tree_node_base*, std::_Rb_tree_node_base&) (485 samples, 2.12%)std::_Rb_tree_insert_and_rebalance(bool, std::_Rb_tree_node_base*, std::_Rb_tree_node_base*, std::_Rb_tree_node_base&)@plt (37 samples, 0.16%)std::__detail::_List_node_base::_M_hook(std::__detail::_List_node_base*) (79 samples, 0.35%)std::__detail::_List_node_base::_M_hook(std::__detail::_List_node_base*)@plt (57 samples, 0.25%)std::__detail::_Map_base<unsigned long, std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, std::allocator<std::pair<unsigned long const, qsl::engine::OrderBook::Locator> >, std::__detail::_Select1st, std::equal_to<unsigned long>, std::hash<unsigned long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true>, true>::operator[](unsigned long const&) [clone .isra.0] (1599 samples, 6.99%)std::__d..operator new(unsigned long) (630 samples, 2.75%)o..__aarch64_ldadd8_relax (112 samples, 0.49%)__libc_malloc2 (31 samples, 0.14%)_int_malloc (28 samples, 0.12%)malloc (344 samples, 1.50%)malloc@plt (55 samples, 0.24%)std::_Hashtable<unsigned long, std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, std::allocator<std::pair<unsigned long const, qsl::engine::OrderBook::Locator> >, std::__detail::_Select1st, std::equal_to<unsigned long>, std::hash<unsigned long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_insert_unique_node(unsigned long, unsigned long, std::__detail::_Hash_node<std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, false>*, unsigned long) (536 samples, 2.34%)s..std::__detail::_Prime_rehash_policy::_M_need_rehash(unsigned long, unsigned long, unsigned long) const (154 samples, 0.67%)std::__detail::_Prime_rehash_policy::_M_need_rehash(unsigned long, unsigned long, unsigned long) const@plt (92 samples, 0.40%)void qsl::engine::OrderBook::match_against<std::map<long, std::__cxx11::list<qsl::engine::Order, std::allocator<qsl::engine::Order> >, std::greater<long>, std::allocator<std::pair<long const, std::__cxx11::list<qsl::engine::Order, std::allocator<qsl::engine::Order> > > > > >(std::map<long, std::__cxx11::list<qsl::engine::Order, std::allocator<qsl::engine::Order> >, std::greater<long>, std::allocator<std::pair<long const, std::__cxx11::list<qsl::engine::Order, std::allocator<qsl::engine::Order> > > > >&, unsigned long, bool, long, bool, unsigned int&, std::vector<qsl::engine::Trade, std::allocator<qsl::engine::Trade> >&) (2991 samples, 13.07%)void qsl::engine::O..__memcpy_generic (46 samples, 0.20%)_int_free_chunk (19 samples, 0.08%)_int_free_maybe_trim (4 samples, 0.02%)_int_free_merge_chunk (11 samples, 0.05%)_int_free_create_chunk (4 samples, 0.02%)cfree@GLIBC_2.17 (304 samples, 1.33%)free@plt (95 samples, 0.42%)memcpy@plt (13 samples, 0.06%)operator new(unsigned long) (291 samples, 1.27%)__aarch64_ldadd8_relax (51 samples, 0.22%)__libc_malloc2 (16 samples, 0.07%)_int_malloc (15 samples, 0.07%)malloc (138 samples, 0.60%)malloc@plt (19 samples, 0.08%)std::_Hashtable<unsigned long, std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, std::allocator<std::pair<unsigned long const, qsl::engine::OrderBook::Locator> >, std::__detail::_Select1st, std::equal_to<unsigned long>, std::hash<unsigned long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_erase(unsigned long, std::__detail::_Hash_node_base*, std::__detail::_Hash_node<std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, false>*) (402 samples, 1.76%)_int_free_chunk (24 samples, 0.10%)_int_free_merge_chunk (18 samples, 0.08%)_int_free_create_chunk (9 samples, 0.04%)unlink_chunk.isra.0 (2 samples, 0.01%)_int_free_maybe_trim (2 samples, 0.01%)cfree@GLIBC_2.17 (93 samples, 0.41%)free@plt (25 samples, 0.11%)operator delete(void*, unsigned long) (3 samples, 0.01%)std::_Rb_tree_rebalance_for_erase(std::_Rb_tree_node_base*, std::_Rb_tree_node_base&) (351 samples, 1.53%)std::_Rb_tree_rebalance_for_erase(std::_Rb_tree_node_base*, std::_Rb_tree_node_base&)@plt (13 samples, 0.06%)std::__detail::_List_node_base::_M_unhook() (13 samples, 0.06%)std::__detail::_List_node_base::_M_unhook()@plt (15 samples, 0.07%)void qsl::engine::OrderBook::match_against<std::map<long, std::__cxx11::list<qsl::engine::Order, std::allocator<qsl::engine::Order> >, std::less<long>, std::allocator<std::pair<long const, std::__cxx11::list<qsl::engine::Order, std::allocator<qsl::engine::Order> > > > > >(std::map<long, std::__cxx11::list<qsl::engine::Order, std::allocator<qsl::engine::Order> >, std::less<long>, std::allocator<std::pair<long const, std::__cxx11::list<qsl::engine::Order, std::allocator<qsl::engine::Order> > > > >&, unsigned long, bool, long, bool, unsigned int&, std::vector<qsl::engine::Trade, std::allocator<qsl::engine::Trade> >&) (2949 samples, 12.88%)void qsl::engine::..__memcpy_generic (49 samples, 0.21%)_int_free_chunk (21 samples, 0.09%)_int_free_maybe_trim (2 samples, 0.01%)_int_free_merge_chunk (12 samples, 0.05%)_int_free_create_chunk (6 samples, 0.03%)unlink_chunk.isra.0 (2 samples, 0.01%)_int_free_maybe_trim (2 samples, 0.01%)cfree@GLIBC_2.17 (279 samples, 1.22%)free@plt (90 samples, 0.39%)memcpy@plt (4 samples, 0.02%)operator new(unsigned long) (335 samples, 1.46%)__aarch64_ldadd8_relax (57 samples, 0.25%)__libc_malloc2 (18 samples, 0.08%)_int_malloc (17 samples, 0.07%)malloc (156 samples, 0.68%)malloc@plt (25 samples, 0.11%)std::_Hashtable<unsigned long, std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, std::allocator<std::pair<unsigned long const, qsl::engine::OrderBook::Locator> >, std::__detail::_Select1st, std::equal_to<unsigned long>, std::hash<unsigned long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_erase(unsigned long, std::__detail::_Hash_node_base*, std::__detail::_Hash_node<std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, false>*) (369 samples, 1.61%)_int_free_chunk (13 samples, 0.06%)_int_free_merge_chunk (10 samples, 0.04%)_int_free_create_chunk (5 samples, 0.02%)cfree@GLIBC_2.17 (84 samples, 0.37%)free@plt (14 samples, 0.06%)operator delete(void*, unsigned long) (3 samples, 0.01%)std::_Rb_tree_rebalance_for_erase(std::_Rb_tree_node_base*, std::_Rb_tree_node_base&) (341 samples, 1.49%)std::_Rb_tree_rebalance_for_erase(std::_Rb_tree_node_base*, std::_Rb_tree_node_base&)@plt (7 samples, 0.03%)std::__detail::_List_node_base::_M_unhook() (10 samples, 0.04%)std::__detail::_List_node_base::_M_unhook()@plt (16 samples, 0.07%)qsl::engine::OrderBook::contains(unsigned long) const (805 samples, 3.52%)qsl..qsl::engine::OrderBook::contains(unsigned long) const (360 samples, 1.57%)qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce) (14 samples, 0.06%)_dl_start (3 samples, 0.01%)_dl_sysdep_start (3 samples, 0.01%)dl_main (3 samples, 0.01%)_dl_relocate_object (3 samples, 0.01%)_dl_relocate_object_no_relro (3 samples, 0.01%)_dl_lookup_symbol_x (3 samples, 0.01%)do_lookup_x (2 samples, 0.01%)
diff --git a/docs/performance/perf-stat.txt b/docs/performance/perf-stat.txt
index 660a66d..4cd6d30 100644
--- a/docs/performance/perf-stat.txt
+++ b/docs/performance/perf-stat.txt
@@ -1,87 +1,87 @@
-QSL matching-engine hot path — perf stat (hardware counters), before vs after
-==============================================================================
+QSL matching-engine hot path: perf stat (hardware counters), v0.1.0 baseline vs v0.2.2
+======================================================================================
-Optimizations under test (order-book insertion + matching hot path):
- - #138 OrderBook::level_for: std::map::emplace -> try_emplace
- - #145 OrderBook ctor: index_ unordered_map max_load_factor 1.0 -> 0.25
+Comparison: first release (v0.1.0) to current (v0.2.2), baseline storage on both.
+
+Two build targets, on purpose:
+ - qsl-perfeval : system allocator untouched -> used for cycles/instructions/branches.
+ - qsl-perfeval-allocs : overrides every operator new variant to count ALL allocations (plain and
+ over-aligned). The override adds a little work per aligned allocation, so
+ it is NOT used for the cycle numbers, only for allocations/order.
Workload / benchmark command:
- qsl-perfeval 60000000
- (steady-state deep book, baseline storage; each "order" = one new_limit that may
- match resting liquidity and rest its remainder; the book is held ~512 deep by
- cancelling the oldest order each cycle. 60,000,000 orders.)
+ qsl-perfeval 60000000 (and qsl-perfeval-allocs 60000000 for the allocation count)
+ (steady-state deep book, baseline storage. Each "order" is one new_limit that may match resting
+ liquidity and rest its remainder, or fully fill. The book is held ~512 deep by cancelling the
+ oldest RESTING order each cycle; only orders that actually rested are tracked. 60,000,000 orders.)
Build:
Compiler: GCC (c++) 16.1.1 20260515 (Red Hat 16.1.1-2)
- Flags: Release (-O3 -DNDEBUG) + -fno-omit-frame-pointer -g (CMake "flamegraph" preset)
- Binary: build/flamegraph/qsl-perfeval
+ Flags: Release (-O3 -DNDEBUG), CMake "release" preset, both versions
+ v0.1.0: same harness ported into a git worktree at the v0.1.0 tag (identical MatchingEngine API)
Hardware / OS:
- CPU: Apple M2 (aarch64); scheduled on the Avalanche performance cores
- (MIDR CPU part 0x032). Heterogeneous SoC: perf opens each event against
- both apple_avalanche_pmu (P-core) and apple_blizzard_pmu (E-core); the
- single-threaded workload runs on the P-cores, so the Avalanche rows carry
- the real counts and the Blizzard rows read .
- Kernel: Linux 6.19.14-400.asahi.fc44.aarch64+16k (Fedora Asahi Remix, bare metal)
- Governor: schedutil (/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor)
- perf: perf version 6.19.14-400.asahi.fc44.aarch64
- paranoid: 2
+ CPU: Apple M2 (aarch64), Avalanche performance cores (MIDR CPU part 0x032), bare metal.
+ perf opens each event against apple_avalanche_pmu (P-core) and apple_blizzard_pmu
+ (E-core); the single-threaded workload runs on the P-cores, so Avalanche carries the
+ real counts and Blizzard reads .
+ Kernel: Linux 6.19.14-400.asahi.fc44.aarch64+16k (Fedora Asahi Remix)
+ Governor: schedutil
+ perf: perf version 6.19.14-400.asahi.fc44.aarch64 ; kernel.perf_event_paranoid = 2
Counter availability (issue #90):
- The Apple Silicon PMU exposed by the Asahi driver implements cycles, instructions,
- branches, and branch-misses, but NOT the generic cache-references / cache-misses
- events. Cache-miss rate is therefore UNMEASURABLE on this host and is reported as
- "unavailable" — never estimated. (A PMU microarchitecture that exposes cache events,
- e.g. x86_64 or an ARM server core, is needed to close that gap.)
+ The Apple Silicon PMU implements cycles, instructions, branches, and branch-misses, but NOT the
+ generic cache-references / cache-misses events. Cache-miss rate is UNMEASURABLE here and is
+ reported as "unavailable", never estimated.
+
+Note: cycles/instructions/allocations per order are frequency-independent counts and are the
+load-bearing metrics. Raw wall-clock throughput (~10.5 to 11 M orders/sec) is governed by schedutil
+frequency scaling and is too thermally noisy to quote a precise delta.
-------------------------------------------------------------------------------
-BEFORE (std::map::emplace + default index load-factor 1.0)
-------------------------------------------------------------------------------
+--------------------------------------------------------------------------------------
+v0.1.0 (first release baseline)
+--------------------------------------------------------------------------------------
perf stat -e cycles,instructions,branches,branch-misses -- qsl-perfeval 60000000
- 20,894,373,501 apple_avalanche_pmu/cycles/u
- apple_blizzard_pmu/cycles/u
- 74,351,477,053 apple_avalanche_pmu/instructions/u
- apple_blizzard_pmu/instructions/u
- 14,635,813,879 apple_avalanche_pmu/branches/u
- apple_blizzard_pmu/branches/u
- 295,293,764 apple_avalanche_pmu/branch-misses/u
- apple_blizzard_pmu/branch-misses/u
+ 18,643,248,999 apple_avalanche_pmu/cycles/u
+ 72,868,263,291 apple_avalanche_pmu/instructions/u
+ 14,442,756,162 apple_avalanche_pmu/branches/u
+ 289,942,740 apple_avalanche_pmu/branch-misses/u
Derived (per 60,000,000 orders):
- cycles/order 348.2
- instructions/order 1239.2
- IPC 3.56
- branches/order 243.9
- branch-miss rate 2.018 % (295,293,764 / 14,635,813,879)
- cache-miss rate unavailable (PMU does not expose cache counters; #90)
+ allocations/order 4.094 (qsl-perfeval-allocs)
+ cycles/order 310.7
+ instructions/order 1214.5
+ IPC 3.91
+ branches/order 240.7
+ branch-miss rate 2.008 % (289,942,740 / 14,442,756,162)
+ p50 / p99 latency 83 ns / 209 ns (new_limit, includes ~12 ns timer overhead)
+ cache-miss rate unavailable (#90)
-------------------------------------------------------------------------------
-AFTER (try_emplace + index load-factor 0.25)
-------------------------------------------------------------------------------
+--------------------------------------------------------------------------------------
+v0.2.2 (current)
+--------------------------------------------------------------------------------------
perf stat -e cycles,instructions,branches,branch-misses -- qsl-perfeval 60000000
- 17,303,430,508 apple_avalanche_pmu/cycles/u (99.50%)
- apple_blizzard_pmu/cycles/u
- 68,572,470,652 apple_avalanche_pmu/instructions/u (99.50%)
- apple_blizzard_pmu/instructions/u
- 13,741,483,668 apple_avalanche_pmu/branches/u (99.50%)
- apple_blizzard_pmu/branches/u
- 249,062,345 apple_avalanche_pmu/branch-misses/u (99.50%)
- apple_blizzard_pmu/branch-misses/u
+ 17,370,897,443 apple_avalanche_pmu/cycles/u
+ 69,419,645,517 apple_avalanche_pmu/instructions/u
+ 14,246,421,285 apple_avalanche_pmu/branches/u
+ 239,493,400 apple_avalanche_pmu/branch-misses/u
Derived (per 60,000,000 orders):
- cycles/order 288.4 (-17.2 % vs before)
- instructions/order 1142.9 ( -7.8 %)
- IPC 3.96 (+11.4 %)
- branches/order 229.0 ( -6.1 %)
- branch-miss rate 1.813 % (-0.205 pp; -10.2 % relative)
+ allocations/order 2.670 (-34.8 % vs v0.1.0; qsl-perfeval-allocs)
+ cycles/order 289.5 ( -6.8 %)
+ instructions/order 1157.0 ( -4.7 %)
+ IPC 4.00 ( +2.3 %)
+ branches/order 237.4
+ branch-miss rate 1.681 % (-0.33 pp; -16.3 % relative)
+ p50 / p99 latency 83 ns / 209 ns (unchanged)
cache-miss rate unavailable (#90)
-Reading: the ~+25% throughput is explained by ~-17% cycles/order. That cycle drop
-is part fewer instructions/order (-7.8%: shorter index probe chains + no throwaway
-per-insert pmr::list construction) and part higher IPC (+11.4%: shorter probe chains
-stall the pipeline/memory system less). The branch-miss rate also falls, consistent
-with fewer mispredicted bucket-traversal loop-back branches. Allocations/order are
-UNCHANGED (1.106 -> 1.106) — the win is instruction/cycle efficiency, not allocation
-count (libstdc++ std::map::emplace checks the key before allocating a node).
+Reading: the dominant cumulative change since v0.1.0 is allocations/order, cut ~35 % (4.094 -> 2.670)
+by the storage/PMR work, even on the default baseline-storage path. That cuts memory traffic and
+branch mispredictions (-16 % relative); cycles/order falls 6.8 % and instructions/order 4.7 %. The
+per-order new_limit latency distribution is unchanged (p50 83 ns, p99 209 ns). An earlier draft
+claimed a 73 % allocation cut; that was a counting bug (the override missed the ~1.56 over-aligned
+allocations/order that v0.2.2 makes and v0.1.0 does not). Counting every operator new variant gives
+the true 2.670.
diff --git a/docs/persistence.md b/docs/persistence.md
index 8639c26..c899452 100644
--- a/docs/persistence.md
+++ b/docs/persistence.md
@@ -14,6 +14,16 @@ holds under the failures we can actually inject.
An `EventLogWriter::append` moves a record through up to three layers of buffering. Each
durability mode stops at a different layer, and each layer dies with a different failure:
+```mermaid
+flowchart TD
+ app["EventLogWriter::append"] --> buf["Application buffer"]
+ buf -->|write| pc["Kernel page cache"]
+ pc -->|"fsync / fdatasync"| disk[("Disk platter / flash")]
+ buf -.->|"crash here"| l1["Lost: never reached the kernel"]
+ pc -.->|"crash here"| l2["Lost unless already fsync'd"]
+ disk --> dur["On disk (best-effort durability; fsync semantics vary, see below)"]
+```
+
| Layer | Reached by | Lost on |
|-------------------------|---------------------------------------|-----------------------|
| user-space stdio buffer | `BufferedOnly` | process crash |
@@ -52,7 +62,7 @@ Classification rules:
a full header has declared a payload size, a truncated frame is `Corrupt`: that size is
untrusted and could span later valid records, so automated repair must not truncate it.
- A `BadChecksum` record is a torn tail only when its frame ends exactly at the end of the
- file — consistent with an interrupted final append (for example, out-of-order page
+ file, consistent with an interrupted final append (for example, out-of-order page
writeback inside the last record). A checksum failure *followed by more bytes* means valid
acknowledged records may sit beyond the damage, so it is `Corrupt`.
- A `PayloadTooLarge` header is never trusted: its declared size is garbage, so the extent
@@ -67,7 +77,7 @@ a human, not automation.
The resulting contract, per mode:
- **`FsyncOnAppend`:** an append acknowledged as successful is in the valid prefix and is
- never removed by tail repair — unless the storage stack lied about flushing, which the
+ never removed by tail repair, unless the storage stack lied about flushing, which the
simulator cannot detect or fix.
- **`FlushOnAppend`:** an acknowledged append survives any process crash, and tail repair
preserves it; an OS crash or power loss may lose it from the page cache.
@@ -96,7 +106,7 @@ recovery. The honest differences:
| Recovery objective | bounded restart time (snapshot + log tail) | unbounded (proportional to log length); measured by `make bench-recovery` (`results/recovery_benchmarks.txt`) |
The deepest gap is the first two rows. Because the gateway acknowledges orders before
-anything is persisted, a crash can lose commands the client believes were accepted — the
+anything is persisted, a crash can lose commands the client believes were accepted, the
simulator's recovery guarantee is "replay reproduces whatever reached the log," not "replay
reproduces everything that was acknowledged." Closing that gap would mean routing the
pipeline through a log-then-apply-then-ack sequence and was deliberately left out of M45:
@@ -106,7 +116,7 @@ recovery semantics explicit there.
Segmentation, rotation, and snapshot-bounded recovery are likewise documented as gaps, not
built; M46 (recovery benchmarking, `make bench-recovery`) measures the cost of the current
-full-replay design before any of that is added — see the recovery-cost section of
+full-replay design before any of that is added, see the recovery-cost section of
[replay_and_recovery.md](replay_and_recovery.md).
## Validation
@@ -129,7 +139,7 @@ Two layers of automated evidence, each labeled with what it does and does not pr
What this validation **cannot** show: SIGKILL leaves the kernel page cache intact, so the
harness validates crash-mid-append recovery and process-death retention only. Power-loss
and OS-crash durability would require fault injection below the filesystem (or pulling
-plugs), neither of which this repo performs — so the `FsyncOnAppend` stable-storage
+plugs), neither of which this repo performs, so the `FsyncOnAppend` stable-storage
guarantee is exercised but not falsified here, and no such durability is claimed.
## Limits
diff --git a/docs/property_testing.md b/docs/property_testing.md
index 966fac0..acb27fe 100644
--- a/docs/property_testing.md
+++ b/docs/property_testing.md
@@ -59,7 +59,7 @@ original/minimized length, reduction %, shrink iterations, failure reason); see
- Both engines are wall-clock independent, so replay and comparison are reproducible.
- An explicit cross-compiler determinism check (`make determinism`, in the `determinism` CI job)
builds the exporter with both gcc and clang and asserts every generated fixture is byte-identical
- between them and to the committed copies — covering compiler and platform reproducibility, not
+ between them and to the committed copies, covering compiler and platform reproducibility, not
just same-toolchain regeneration. Because generation is integer-only and wall-clock independent,
the committed bytes are identical regardless of which host produced them (the fixtures were
byte-stable across the project's macOS→Linux host move).
diff --git a/docs/recruiting_notes.md b/docs/recruiting_notes.md
index fa42665..9fd4bab 100644
--- a/docs/recruiting_notes.md
+++ b/docs/recruiting_notes.md
@@ -1,7 +1,7 @@
# Recruiting Notes
> Internal notes on how to position this project. Conservative and technically defensible by
-> design — every claim maps to code or a measured result. No fake metrics, no overclaiming.
+> design, every claim maps to code or a measured result. No fake metrics, no overclaiming.
## What this project demonstrates
@@ -20,7 +20,7 @@
- Not a production exchange, trading bot, or connected to real markets
- Not making profitability, "production-grade", or "battle-tested" claims
-## Résumé bullets — Software Engineering (conservative)
+## Résumé bullets. Software Engineering (conservative)
- Built a deterministic C++20 multi-symbol matching engine (price-time priority, partial
fills, cancel/modify) emitting a strictly-increasing event stream with integer-tick prices.
@@ -33,7 +33,7 @@
- Set up the full toolchain: CMake/Ninja, clang-format/clang-tidy, GitHub Actions with a
sanitizer job, and a reproducible benchmark harness committing results with metadata.
- Wrote an independent replay-invariant verifier in OCaml (typed, immutable) that re-checks
- exported C++ event-log fixtures against replay invariants — a cross-language check, not a
+ exported C++ event-log fixtures against replay invariants, a cross-language check, not a
re-implementation of the engine.
- Built a cross-language differential testing system: an independent OCaml engine replays
seeded, property-generated command streams and its final snapshot is asserted equal to the
@@ -42,7 +42,7 @@
counterexample, plus golden-regenerated fixtures so the comparison cannot drift from current
C++ output.
-## Résumé bullets — Linux Engineering (conservative)
+## Résumé bullets. Linux Engineering (conservative)
- Implemented TCP order-gateway transports and a UDP market-data feed on POSIX sockets
(loopback), with bounded receive timeouts, sequence-gap detection, UDP send-error counting,
@@ -54,11 +54,11 @@
- Hardened with ASan/UBSan, fixed RNG seeds for reproducibility, and a benchmark harness that
records hardware/OS/compiler/build/commit metadata alongside results.
-## Benchmark bullets (measured — cite with the caveat)
+## Benchmark bullets (measured, cite with the caveat)
Single-machine synthetic, in-process microbenchmark (aarch64 Fedora Asahi Linux, GCC 16.1.1,
Release, seed 42; from `results/latest.txt`). **Excludes** network I/O, disk fsync, the
-kernel/socket path, and allocator tuning — not production throughput or end-to-end latency:
+kernel/socket path, and allocator tuning, not production throughput or end-to-end latency:
- matching-engine flow ~98 ns/command (~10.2M commands/sec)
- order-book add/modify/cancel ~87 ns/op
@@ -88,14 +88,14 @@ kernel/socket path, and allocator tuning — not production throughput or end-to
server core). See the README Limitations section.
- **What would you do next?** The CPU-affinity/scheduler-migration study, ingress false-sharing
validation, contiguous order-book storage/cache-locality study, and persistence/recovery
- benchmarking are already done (M43–M47). The genuinely-remaining moves are *not more features*:
+ benchmarking are already done (M43-M47). The genuinely-remaining moves are *not more features*:
independent external review (issue #94) and full cache-counter PMU evidence on a host whose PMU
- exposes it (issue #90). DPDK/NIC work stays research-only — the stronger signal is tightening what
+ exposes it (issue #90). DPDK/NIC work stays research-only, the stronger signal is tightening what
the simulator already proves, not adding surface area.
- **Why OCaml, and what does it actually prove?** It's an independent cross-check: a small
typed/immutable OCaml engine (`ocaml/lib/replay_engine.ml`) re-derives the final book state from
the exported *command stream* using its own price-time matching (GTC/IOC/market/cancel/modify plus
- gateway risk) — it does **not** consume the C++ event stream while replaying — then asserts
+ gateway risk), it does **not** consume the C++ event stream while replaying, then asserts
snapshot equality (best bid/ask, level aggregates, order counts, `last_seq`, trade count) against
the C++ engine across 50+ committed property fixtures and a CI seed sweep. So it is a genuinely
independent matching model, not just a log re-reader; it is empirical differential testing, not
diff --git a/docs/release_readiness.md b/docs/release_readiness.md
index e148c63..2eaf811 100644
--- a/docs/release_readiness.md
+++ b/docs/release_readiness.md
@@ -1,14 +1,14 @@
# Release Readiness Audit
A pre-release pass verifying the repo builds, demos, reproduces, and reads honestly. This audit
-covers **M0–M49, the v0.2.0 evidence refresh** (bare-metal Linux artifact regeneration and the
+covers **M0-M49, the v0.2.0 evidence refresh** (bare-metal Linux artifact regeneration and the
documentation/staleness sweep), **the v0.2.1 content** (the FIX-like text protocol adapter #29, the
perf call-graph flamegraph + `make flamegraph` #32, and a Codex resume-anchor/PMU consistency sweep),
-**and the post-v0.2.1 hardening + perf wave being cut as v0.2.2** (#135–#146): out-of-domain enum
-rejection in the decoders (#136), network-path hardening — EINTR retry, accept fairness, connection
+**and the post-v0.2.1 hardening + perf wave being cut as v0.2.2** (#135, #146): out-of-domain enum
+rejection in the decoders (#136), network-path hardening. EINTR retry, accept fairness, connection
cap, UDP send-error tracking, transient-accept survival, and fd-exhaustion handling (#137/#140/#143),
CLI argument validation (#141), a real UBSan abort gate (#142), OCaml `diff_report` robustness (#144),
-and two measured order-book perf wins — `try_emplace` (~+5%, #138) and an index load-factor cap
+and two measured order-book perf wins, `try_emplace` (~+5%, #138) and an index load-factor cap
(~+18.6%, #145). It supersedes the v0.1.0-era audit; the actual GitHub release is cut by a human
after squash-merge.
@@ -40,17 +40,17 @@ The committed `results/*.txt` artifacts are now generated on a **bare-metal** Ap
(M2, aarch64) running Fedora Asahi Remix, not the earlier Docker Desktop Linux. What that does and
does not buy:
-- **Perf** — `results/perf_stat_linux.txt` is **partial hardware PMU evidence**: real `cycles` /
+- **Perf**, `results/perf_stat_linux.txt` is **partial hardware PMU evidence**: real `cycles` /
`instructions` / `branches` / `branch-misses` off the Apple Avalanche/Blizzard PMUs, with
`cache-references` / `cache-misses` reported `` (Apple Silicon PMU limitation).
Not full PMU evidence; issue #90 tracks the cache-counter set, which needs a different PMU.
`results/flamegraph.svg`/`.txt` (v0.2.1) is a **software cpu-clock sampling** hot-symbol profile
- from the same host — a hot-symbol investigation aid, not a latency or throughput claim.
-- **Sockets** — `socket_profile_loopback.txt`, `socket_stress_summary.txt`, and
+ from the same host, a hot-symbol investigation aid, not a latency or throughput claim.
+- **Sockets**, `socket_profile_loopback.txt`, `socket_stress_summary.txt`, and
`socket_load_summary.txt` are bare-metal but **loopback-only**: no NIC/driver/routing.
-- **NUMA** — `numa_affinity_study.txt` is bare-metal but the M2 is a **single-NUMA-node** machine,
+- **NUMA**, `numa_affinity_study.txt` is bare-metal but the M2 is a **single-NUMA-node** machine,
so it is `linux-constrained` for NUMA (real CPU pinning, no cross-node binding to measure).
-- **Benchmarks** — `latest.txt`, `pool_backed_storage.txt`, `recovery_benchmarks.txt`,
+- **Benchmarks**, `latest.txt`, `pool_backed_storage.txt`, `recovery_benchmarks.txt`,
`allocator_experiment.txt`, `false_sharing_study.txt`, `differential.txt` are bare-metal but
**synthetic, single-process microbenchmarks**.
@@ -65,7 +65,7 @@ artifact leaks host identifiers (a publish-time MAC sanitizer redacts every non-
`docs/benchmarking.md`).
- **No overclaiming.** A scan for forbidden phrases (production-grade, formal verification, HFT
platform, low-latency trading, real exchange, trading bot, production exchange) finds only
- negations and the project's own avoid-lists/specs — no positive claims.
+ negations and the project's own avoid-lists/specs, no positive claims.
- **Benchmark language** remains measured, synthetic, hardware-dependent, and reproducible from the
committed harness; core numbers are cited from `results/latest.txt` and the differential-harness
numbers from `results/differential.txt` only.
@@ -74,12 +74,12 @@ artifact leaks host identifiers (a publish-time MAC sanitizer redacts every non-
separation, constrained/partial perf artifacts, loopback socket evidence, PMR node allocation,
epoll prototype, durability modes and tail repair).
- **No stale milestone references**: PROGRESS, HANDOFF, and the milestone tables reflect the merged
- M0–M49 state, the v0.2.0 artifact refresh, and the v0.2.1 content (#29/#32 closed; resume anchors
+ M0-M49 state, the v0.2.0 artifact refresh, and the v0.2.1 content (#29/#32 closed; resume anchors
consistent across PROGRESS/HANDOFF/AGENTS/CLAUDE).
## Scope and honesty
-This is a deterministic exchange-systems lab / research portfolio project — not a production
+This is a deterministic exchange-systems lab / research portfolio project, not a production
exchange, not connected to real markets, and making no latency or profitability claims. The demo
network services are unauthenticated and loopback-only (`SECURITY.md`). The cross-language
differential layer is property-based testing against the C++ system under test, **not** formal
@@ -87,15 +87,15 @@ verification.
## Standing credibility gaps (open, not blockers)
-- **Issue #94** — no independent external technical review yet. The repo is self-certified.
-- **Issue #90** — full cache-counter PMU evidence still absent; the bare-metal Apple PMU provides a
+- **Issue #94**, no independent external technical review yet. The repo is self-certified.
+- **Issue #90**, full cache-counter PMU evidence still absent; the bare-metal Apple PMU provides a
partial counter set only.
## Outcome
Release-ready as a portfolio artifact. `v0.2.1` is already tagged (FIX adapter #29, perf flamegraph
-#32, anchor sweep) on top of `v0.2.0` (Phase III/IV systems work — M24–M49 — plus the bare-metal
+issue #32, anchor sweep) on top of `v0.2.0` (Phase III/IV systems work, M24-M49, plus the bare-metal
evidence refresh). The next GitHub-only release is **`v0.2.2`**, bundling the post-v0.2.1
-hardening + perf wave merged to `main` (#135–#146): decoder enum rejection, network/CLI hardening, a
+hardening + perf wave merged to `main` (#135, #146): decoder enum rejection, network/CLI hardening, a
real UBSan abort gate, OCaml diff_report robustness, and the two measured order-book perf wins. It
requires explicit human approval and a squash-merge before tagging.
diff --git a/docs/replay_and_recovery.md b/docs/replay_and_recovery.md
index 2f1caef..0ce8990 100644
--- a/docs/replay_and_recovery.md
+++ b/docs/replay_and_recovery.md
@@ -14,7 +14,7 @@ flowchart LR
Accepted commands and emitted engine events are persisted as framed records in an
append-only log (`include/qsl/replay/event_log.hpp`, `src/replay/event_log.cpp`). The
-writer opens the file in binary append mode and only ever appends — it never seeks back or
+writer opens the file in binary append mode and only ever appends, it never seeks back or
rewrites existing records.
### Record format
@@ -72,7 +72,7 @@ cleanly as zero records.
## Deterministic replay and recovery (M8)
The log is a stream of `Command` records (`include/qsl/replay/command.hpp`). The recordable
-command set is `RegisterSymbol`, `NewLimit`, `NewMarket`, `Cancel`, `Modify` — a
+command set is `RegisterSymbol`, `NewLimit`, `NewMarket`, `Cancel`, `Modify`, a
`std::variant` serialized with a 1-byte tag plus fixed-width fields (`RegisterSymbol`
carries a variable-length name). Including `RegisterSymbol` makes a log **self-contained**:
registering the same names in the same order reproduces identical `SymbolId`s, so the
@@ -126,7 +126,7 @@ re-applies every command, so restart time grows with history length, not with li
`make bench-recovery` (`qsl-bench recovery`, artifact
`results/recovery_benchmarks.txt`) measures that cost on the generating host.
-**Recovery objective measured.** Restart cost — the wall time from a clean log file to a
+**Recovery objective measured.** Restart cost, the wall time from a clean log file to a
rebuilt engine (RTO-style), split into its two phases:
1. `recover_log_file`: read the file and verify/classify every frame (the M45 crash-safe
@@ -141,7 +141,7 @@ M45 durability modes and is exercised by `make crash-recovery`, not by this benc
the benchmark also measures rebuilding the book directly from captured live state:
`MatchingEngine::resting_orders` enumerates every resting order in priority order (bids
best-first, then asks, FIFO within a level), and re-adding that sequence into a fresh
-engine reproduces levels and intra-level time priority exactly — verified against the
+engine reproduces levels and intra-level time priority exactly, verified against the
reference snapshot on every run, including a synthetic-depth sweep because the realistic
flow leaves only a few dozen resting orders (per-order timings on such small books are
noisy; the controlled-depth section is the reliable scaling signal). This prototype is
@@ -154,7 +154,7 @@ design would also have to persist sequencing state.
in log length while book rebuild cost is linear in resting-order count; for the measured
synthetic flows the live state is orders of magnitude smaller than the history that
produced it. That is the case for snapshot-bounded recovery *if* restart time ever
-matters — and no more: the numbers are single-machine, warm-cache, synthetic-flow
+matters, and no more: the numbers are single-machine, warm-cache, synthetic-flow
measurements, not a production recovery-time claim.
### Limitations
diff --git a/docs/review_feedback.md b/docs/review_feedback.md
index e336b66..885e56c 100644
--- a/docs/review_feedback.md
+++ b/docs/review_feedback.md
@@ -9,7 +9,7 @@ It is the companion to the [external review request](review_request.md).
>
> This repository therefore contains **no externally-validated claims**. Everything is
> self-certified by the maintainer (see [review_request.md → Review status](review_request.md#review-status)).
-> When real feedback arrives it will be recorded below — accepted *and* rejected — with rationale,
+> When real feedback arrives it will be recorded below, accepted *and* rejected, with rationale,
> so the review history is auditable. Entries are added only for feedback that actually happened;
> this file must never contain invented reviewers or endorsements.
@@ -17,20 +17,20 @@ It is the companion to the [external review request](review_request.md).
Each item gets one entry with these fields:
-- **Date** — when the feedback was received.
-- **Reviewer** — name/handle, or "anonymous" if not disclosed. Never fabricated.
-- **Area** — which review area (see [review_request.md](review_request.md)) or file.
-- **Criticism** — the concrete issue raised.
-- **Disposition** — `accepted`, `partially accepted`, or `rejected`.
-- **Rationale** — why it was accepted or rejected.
-- **Follow-up** — the issue / PR / commit that actions it (or a note that none was needed).
+- **Date**, when the feedback was received.
+- **Reviewer**, name/handle, or "anonymous" if not disclosed. Never fabricated.
+- **Area**, which review area (see [review_request.md](review_request.md)) or file.
+- **Criticism**, the concrete issue raised.
+- **Disposition**, `accepted`, `partially accepted`, or `rejected`.
+- **Rationale**, why it was accepted or rejected.
+- **Follow-up**, the issue / PR / commit that actions it (or a note that none was needed).
## Entry template
Copy this block per piece of feedback. It is a **template, not a real review.**
```markdown
-### YYYY-MM-DD —
+### YYYY-MM-DD,
- **Area:**
- **Criticism:**
diff --git a/docs/review_request.md b/docs/review_request.md
index d6f8d45..eac7b6f 100644
--- a/docs/review_request.md
+++ b/docs/review_request.md
@@ -1,7 +1,7 @@
# External review request
This is an **open request for technical criticism** of Quant Systems Lab. As of this writing
-**no external review has taken place** — every claim in this repository is **self-certified** by
+**no external review has taken place**, every claim in this repository is **self-certified** by
the maintainer (see [Review status](#review-status)). This document exists to make the project
easy to review well, and to invite reviewers to attack the parts most likely to be wrong.
@@ -38,7 +38,7 @@ to confirm what looks right.
### 2. Backpressure semantics
-- **Claim:** bounded-queue backpressure is **lossless** — under a full queue the producer spins/
+- **Claim:** bounded-queue backpressure is **lossless**, under a full queue the producer spins/
yields rather than dropping, and capacity changes affect only timing, never the result.
- **Where:** [`concurrency_model.md`](concurrency_model.md),
`tests/concurrency/test_backpressure.cpp`, the gated-consumer/probe test in the pipeline suite.
@@ -155,5 +155,5 @@ Process and security context: [`CONTRIBUTING.md`](../CONTRIBUTING.md),
until a real review is recorded in [`review_feedback.md`](review_feedback.md).
Dynamic analysis (sanitizers, TSan, stress, differential testing) is **empirical evidence over
-executed cases**, not a proof of correctness over all inputs or thread interleavings — a point a
+executed cases**, not a proof of correctness over all inputs or thread interleavings, a point a
reviewer is explicitly invited to push on.
diff --git a/docs/socket_gateway.md b/docs/socket_gateway.md
index ef914d2..af79566 100644
--- a/docs/socket_gateway.md
+++ b/docs/socket_gateway.md
@@ -3,18 +3,33 @@
The TCP order gateway exposes the in-process `OrderGateway` (M5) over a stream socket using
the M2 binary protocol. It is split into two pieces:
-- **`Session`** (`include/qsl/gateway/session.hpp`) — a *pure* byte processor with no socket
+- **`Session`** (`include/qsl/gateway/session.hpp`), a *pure* byte processor with no socket
calls. It buffers inbound bytes, frames whole messages, drives the `OrderGateway`, and
returns response bytes. This is where all protocol logic lives, so it is fully unit-tested
deterministically.
-- **`TcpServer`** (`include/qsl/gateway/tcp_server.hpp`) — a thin POSIX-socket transport:
+- **`TcpServer`** (`include/qsl/gateway/tcp_server.hpp`), a thin POSIX-socket transport:
`serve_connection(fd)` runs a `Session` over one connected socket; `run(host, port)` binds,
listens, accepts connections, and serves each accepted connection on a worker thread while
serializing gateway mutation.
-- **`EpollServer`** (`include/qsl/gateway/epoll_server.hpp`) — a Linux-only event-driven
+- **`EpollServer`** (`include/qsl/gateway/epoll_server.hpp`), a Linux-only event-driven
transport prototype: one `epoll` loop accepts multiple clients and drives one `Session` per
connection with nonblocking reads and writes.
+The threaded `TcpServer` accept loop tolerates transient and resource-exhaustion errors instead of
+tearing the listener down, and sheds load at the connection cap:
+
+```mermaid
+flowchart TD
+ run["run(host, port)"] --> bind["socket, bind, listen"]
+ bind --> acc["accept_retry"]
+ acc -->|"transient errno (EINTR, ECONNABORTED, ...)"| acc
+ acc -->|"fd exhausted (EMFILE, ENFILE, ...)"| backoff["back off 10ms"] --> acc
+ acc -->|"fatal (EBADF, EINVAL)"| stop["Stop listener"]
+ acc -->|"connected fd"| cap{"At connection cap?"}
+ cap -->|Yes| shed["close fd, shed load"] --> acc
+ cap -->|No| worker["Spawn worker thread: Session over fd"] --> acc
+```
+
## Message flow
```text
@@ -39,7 +54,7 @@ The Linux `EpollServer` keeps a per-client outbound buffer and leaves the connec
for `EPOLLOUT` until all pending response bytes are accepted by the kernel. Both write paths use
`send(..., MSG_NOSIGNAL)` where available, and the platform socket option where available, so a
client that drops before reading a response cannot terminate the gateway through `SIGPIPE`. Both the
-read and write paths retry on `EINTR` — a signal interruption is treated as retryable, not a
+read and write paths retry on `EINTR`, a signal interruption is treated as retryable, not a
disconnect.
The epoll path treats `EAGAIN` / `EWOULDBLOCK` as normal nonblocking backpressure:
@@ -65,7 +80,7 @@ up in the kernel receive buffer and TCP flow control pushes back on the sender.
the backlog drains below the mark.
That soft mark bounds how many *further* requests a non-reading peer induces, but a single
-request's response can fan out — a market order sweeping a deep book returns one fill per resting
+request's response can fan out, a market order sweeping a deep book returns one fill per resting
maker. So a **hard cap** (`EpollServerOptions::max_outbuf_hard_bytes`, default 8 MiB) is the
absolute ceiling. The epoll path asks `Session` to append responses directly into the per-client
buffer under that byte budget; before a `NewOrder` reaches the gateway, the session previews the
@@ -96,7 +111,7 @@ induces an over-cap response is disconnected.
The default demo uses `TcpServer` because it is portable and easiest to inspect. The accept loop
spawns one worker per accepted connection, so a slow or still-open client no longer prevents the
server from accepting a later client. A connection cap (`TcpServerOptions::max_active_connections`,
-default `0` = unbounded) load-sheds — a freshly accepted connection at the cap is closed immediately
+default `0` = unbounded) load-sheds, a freshly accepted connection at the cap is closed immediately
rather than spawning another worker. The accept loop also survives transient `accept()` errors
(`EINTR`/`ECONNABORTED`, retried) and file-descriptor exhaustion (`EMFILE`/`ENFILE`, a brief back-off
retry) instead of tearing the listener down; the `EpollServer` handles the same conditions by
@@ -134,7 +149,7 @@ and no real venue connectivity.
There is **no authentication or authorization**. The server binds to **`127.0.0.1` only**, so
it is reachable only from the local machine in the default demo. M9 accepts numeric IPv4 bind
hosts only; invalid hosts such as `"localhost"` or typos fail startup and must not fall back
-to `0.0.0.0`. Do not expose it on a routable interface or an untrusted network — it accepts
+to `0.0.0.0`. Do not expose it on a routable interface or an untrusted network, it accepts
and acts on any order from any local connection. This is a local simulator, not a real venue.
## Local demo
diff --git a/docs/socket_hardening.md b/docs/socket_hardening.md
index c806f5f..af113c4 100644
--- a/docs/socket_hardening.md
+++ b/docs/socket_hardening.md
@@ -59,7 +59,7 @@ The [`make socket-stress`](socket_profiling.md#udp-socket-buffer--burst-loss-exp
experiment measures the effect: an undersized buffer drops datagrams under burst (measured as
`published − received`, which also captures end-of-burst tail drops the sequence-gap counter
misses), while the default and larger buffers absorb the same burst. This is the honest,
-measured justification for the knob — not a guess.
+measured justification for the knob, not a guess.
### Loss model: detected, not recovered
diff --git a/docs/socket_profiling.md b/docs/socket_profiling.md
index 1daf83d..3267028 100644
--- a/docs/socket_profiling.md
+++ b/docs/socket_profiling.md
@@ -1,7 +1,7 @@
# Socket / kernel-path profiling
This documents how the gateway and market-data feed are profiled at the socket / kernel
-boundary, what the committed artifacts mean, and — just as important — what they do **not**
+boundary, what the committed artifacts mean, and, just as important, what they do **not**
prove. It complements [`socket_gateway.md`](socket_gateway.md) (the design),
[`socket_hardening.md`](socket_hardening.md) (the defensive posture), and
[`linux_performance.md`](linux_performance.md) / [`perf_analysis.md`](perf_analysis.md) (the
@@ -31,21 +31,21 @@ records which OS produced the artifact.
Artifact: [`results/socket_profile_loopback.txt`](../results/socket_profile_loopback.txt).
-The script drives a fixed load — `CONNECTIONS` sequential `qsl-client` round trips, each a
-`connect` → `NewOrder` + `Heartbeat` → read replies → `close` — against `qsl-gateway`, in two
+The script drives a fixed load, `CONNECTIONS` sequential `qsl-client` round trips, each a
+`connect` → `NewOrder` + `Heartbeat` → read replies → `close`, against `qsl-gateway`, in two
passes over the same workload:
-1. **rusage pass** — reads the gateway's `/proc//{stat,status}`: user (engine-side) vs
+1. **rusage pass**, reads the gateway's `/proc//{stat,status}`: user (engine-side) vs
system (kernel/socket) CPU time, voluntary/involuntary context switches, minor/major page
faults, and peak RSS (`VmHWM`). This is the pass that **distinguishes user-space matching cost
from kernel/socket overhead**. No tracer is attached, so perturbation is minimal.
-2. **`strace -f -c` pass** — per-syscall call counts and time-in-kernel, i.e. *which* syscalls
+2. **`strace -f -c` pass**, per-syscall call counts and time-in-kernel, i.e. *which* syscalls
the socket path spends its time in (expected: `accept`, `read`/`recvfrom`, `write`/`sendto`,
`close`, plus connection setup). `strace` multiplies syscall cost dramatically, so this pass
is read for the **syscall mix**, never for wall-clock seconds.
Pass 1 starts the gateway **directly** (the script owns its PID) and reads its procfs rusage;
-pass 2 runs the gateway **under** `strace -f -c` so the gateway is strace's *descendant* — that
+pass 2 runs the gateway **under** `strace -f -c` so the gateway is strace's *descendant*, that
relationship is what lets tracing work under the common Yama `ptrace_scope=1` default, where a
tracer may only trace its own descendants (attaching to a sibling would need `CAP_SYS_PTRACE`).
Each pass waits for the gateway to accept a loopback connection, drives the load, then stops the
@@ -62,9 +62,8 @@ gateway; the two passes use adjacent ports to avoid `TIME_WAIT` reuse stalls.
and that there is no surprising syscall (e.g. unexpected `fcntl`/`poll` churn).
On a representative loopback run (500 round trips, bare-metal Apple M2 Fedora Asahi Linux), the
-gateway's *measurable* CPU was effectively all in the kernel/socket path — user-space matching fell
-below the clock-tick (10 ms) granularity, with roughly one voluntary context switch per connection —
-and the syscall mix was dominated by the per-request `accept` / `read` / `sendto` / `close`
+gateway's *measurable* CPU was effectively all in the kernel/socket path, user-space matching fell
+below the clock-tick (10 ms) granularity, with roughly one voluntary context switch per connection, and the syscall mix was dominated by the per-request `accept` / `read` / `sendto` / `close`
(alongside one-time process and socket setup such as `execve` / `socket` / `bind` / `listen`).
The honest takeaway: for this trivial-per-order loopback workload the socket servicing dominates,
not the matching. The
@@ -89,11 +88,10 @@ clamp the request to a system maximum, so the **effective** granted size is read
A too-small receive buffer overflows during the burst and the kernel **silently drops**
datagrams. Read `published − received` (the `lost/trial` column) as the true loss: a datagram
dropped at the very end of the burst leaves no later sequence number to reveal it, so the
-interior `seq-gaps` count can read 0 even when datagrams were lost — which is exactly why the
+interior `seq-gaps` count can read 0 even when datagrams were lost, which is exactly why the
artifact reports loss as `published − received` and treats the sequence-gap count as a secondary,
interior-only signal. The OS default and larger buffers absorb the same burst with little or no
-loss. Because UDP loss is timing/OS/load dependent, per-trial counts vary between trials and runs
-— a small buffer does **not** lose on every trial — so the mechanism (`SO_RCVBUF` bounds
+loss. Because UDP loss is timing/OS/load dependent, per-trial counts vary between trials and runs, a small buffer does **not** lose on every trial, so the mechanism (`SO_RCVBUF` bounds
in-kernel queueing) is the point, not any specific number. A representative loopback run on the
development machine showed loss only at the smallest buffer (from a handful to ~a thousand
datagrams across trials) and none at the default and large buffers; the committed artifact
@@ -105,15 +103,15 @@ This directly motivates the receive-buffer tuning knob documented in
## Reproduce
```bash
-# Linux only — syscall/rusage profile of the gateway path:
+# Linux only, syscall/rusage profile of the gateway path:
make profile-io
# tunables: QSL_PROFILE_CONNECTIONS (default 500), QSL_PROFILE_PORT
-# Linux or macOS — UDP buffer/loss experiment:
+# Linux or macOS. UDP buffer/loss experiment:
make socket-stress
# tunables: QSL_STRESS_ORDERS, QSL_STRESS_TRIALS, QSL_STRESS_SMALL_BUF, QSL_STRESS_LARGE_BUF
-# Linux only — multi-client threaded-vs-epoll connection-scaling load:
+# Linux only, multi-client threaded-vs-epoll connection-scaling load:
make socket-load
# tunables: QSL_LOAD_COUNTS, QSL_LOAD_TRIALS, QSL_LOAD_PORT, QSL_LOAD_ALLOW_PARTIAL
```
@@ -121,7 +119,7 @@ make socket-load
The committed gateway artifact is now generated on a **bare-metal** Apple M2 (aarch64) Fedora Asahi
Linux host (which has `strace` and procfs natively); the earlier macOS development host had no
`strace`, so prior versions were produced in containerized Linux. It remains **loopback-only**
-evidence — real NIC/driver/routing behaviour is still not exercised — and its metadata records the
+evidence, real NIC/driver/routing behaviour is still not exercised, and its metadata records the
OS, kernel, compiler, source digest, and working-tree state it was produced from.
## Limitations
@@ -144,7 +142,7 @@ Artifact: [`results/socket_load_summary.txt`](../results/socket_load_summary.txt
`scripts/socket_load.sh` (Linux-only) drives **N concurrent** short-lived clients (`qsl-client`:
connect → `NewOrder` + `Heartbeat` → read replies → close) against `qsl-gateway` in **both**
-transport modes — the portable threaded TCP server and the epoll event loop (M34) — across a
+transport modes, the portable threaded TCP server and the epoll event loop (M34), across a
sweep of client counts, reporting the best (minimum) wall time and an approximate
connections/second per cell.
diff --git a/include/qsl/concurrency/pipeline.hpp b/include/qsl/concurrency/pipeline.hpp
index 2025c10..834c2c8 100644
--- a/include/qsl/concurrency/pipeline.hpp
+++ b/include/qsl/concurrency/pipeline.hpp
@@ -81,7 +81,7 @@ struct PipelineResult {
/// Optional live observation hook. The pipeline increments these counters *as backpressure happens*
/// (not only at the end), so a test can deterministically wait until backpressure has provably
-/// occurred — e.g. before releasing a gated consumer — instead of asserting on a timing-dependent
+/// occurred, e.g. before releasing a gated consumer, instead of asserting on a timing-dependent
/// final spin count. Production callers pass `nullptr`.
struct PipelineProbe {
std::atomic inbound_spins{0};
@@ -304,7 +304,7 @@ class ThreadedPipeline {
public:
/// Run `commands` through the pipeline to completion and return the engine's final snapshot
/// plus stats. Blocks until all three threads are joined (so the queues, which live on this
- /// stack frame, outlive both the producer and the consumer of each — the lifetime bracket the
+ /// stack frame, outlive both the producer and the consumer of each, the lifetime bracket the
/// SPSC contract requires). Deterministic given `commands` and `risk`.
static PipelineResult run(const std::vector &commands, engine::RiskConfig risk,
OutputSink &sink, PipelineRunOptions options = {}) {
diff --git a/include/qsl/concurrency/spsc_ring.hpp b/include/qsl/concurrency/spsc_ring.hpp
index 3755630..95364e0 100644
--- a/include/qsl/concurrency/spsc_ring.hpp
+++ b/include/qsl/concurrency/spsc_ring.hpp
@@ -11,7 +11,7 @@ namespace qsl::concurrency {
//
// Exactly one thread may call the producer side (`try_push`) and exactly one *different* thread
// may call the consumer side (`try_pop`). Concurrent producers, or concurrent consumers, are
-// undefined behavior — this is intentionally **not** an MPMC queue (see
+// undefined behavior, this is intentionally **not** an MPMC queue (see
// docs/concurrency_model.md for why SPSC is sufficient here). Capacity is fixed at compile time
// and storage is inline, so `try_push`/`try_pop` never allocate.
//
diff --git a/include/qsl/feed/sequence_tracker.hpp b/include/qsl/feed/sequence_tracker.hpp
index 81ef381..1ddc854 100644
--- a/include/qsl/feed/sequence_tracker.hpp
+++ b/include/qsl/feed/sequence_tracker.hpp
@@ -9,7 +9,7 @@ namespace qsl::feed {
using core::SeqNo;
/// Detects forward gaps in a market-data sequence (e.g. UDP datagram loss). It does not
-/// recover lost messages — it only reports how many were missed. Duplicates and reordered
+/// recover lost messages, it only reports how many were missed. Duplicates and reordered
/// (lower) sequence numbers are ignored rather than reported as gaps.
class SequenceTracker {
public:
diff --git a/include/qsl/feed/udp_feed.hpp b/include/qsl/feed/udp_feed.hpp
index 78a03ec..896572d 100644
--- a/include/qsl/feed/udp_feed.hpp
+++ b/include/qsl/feed/udp_feed.hpp
@@ -11,7 +11,7 @@
namespace qsl::feed {
/// Sends each market-data message as one UDP datagram (encoded with the binary protocol).
-/// Plugs into MarketDataPublisher as a subscriber. UDP is connectionless and lossy — there
+/// Plugs into MarketDataPublisher as a subscriber. UDP is connectionless and lossy, there
/// is no retransmit; receivers detect loss via the message sequence number.
class UdpPublisher : public MarketDataSubscriber {
public:
diff --git a/include/qsl/gateway/tcp_server.hpp b/include/qsl/gateway/tcp_server.hpp
index 8abff12..95e1106 100644
--- a/include/qsl/gateway/tcp_server.hpp
+++ b/include/qsl/gateway/tcp_server.hpp
@@ -29,7 +29,7 @@ struct TcpServerOptions {
/// Minimal threaded TCP front end for the order gateway. It binds to a host/port (intended for
/// localhost), accepts connections in one thread, and serves each connection on a worker thread.
/// Gateway mutation is serialized internally so the deterministic matching engine remains
-/// single-owner. There is no authentication — this is a local simulator, not a venue.
+/// single-owner. There is no authentication, this is a local simulator, not a venue.
class TcpServer {
public:
explicit TcpServer(OrderGateway &gateway, TcpServerOptions options = {})
diff --git a/include/qsl/protocol/endian.hpp b/include/qsl/protocol/endian.hpp
index d3a40a5..a171e4d 100644
--- a/include/qsl/protocol/endian.hpp
+++ b/include/qsl/protocol/endian.hpp
@@ -7,7 +7,7 @@
namespace qsl::protocol {
// Explicit big-endian (network byte order) serialization via byte shifts.
-// No reinterpret_cast, no memcpy, no struct overlay — well-defined on any host.
+// No reinterpret_cast, no memcpy, no struct overlay, well-defined on any host.
template void store_be(std::byte *p, UInt v) noexcept {
static_assert(std::is_unsigned_v, "store_be requires an unsigned type");
diff --git a/include/qsl/protocol/fix.hpp b/include/qsl/protocol/fix.hpp
index 7c15562..c76ad27 100644
--- a/include/qsl/protocol/fix.hpp
+++ b/include/qsl/protocol/fix.hpp
@@ -4,15 +4,15 @@
//
// A human-readable `tag=value` wire format alongside the binary codec
// (qsl/protocol/codec.hpp), mapping the same internal message structs. It is
-// "FIX-like": it uses genuine FIX framing — SOH-delimited tag=value fields, the
-// 8/9/35/.../10 envelope, a BodyLength (tag 9) and a mod-256 CheckSum (tag 10) —
-// for the client->gateway order path: NewOrderSingle (35=D) -> NewOrder and
-// OrderCancelRequest (35=F) -> CancelOrder.
+// "FIX-like": it uses genuine FIX framing. SOH-delimited tag=value fields, the
+// 8/9/35/.../10 envelope, a BodyLength (tag 9) and a mod-256 CheckSum (tag 10), // for the
+// client->gateway order path: NewOrderSingle (35=D) -> NewOrder and OrderCancelRequest (35=F) ->
+// CancelOrder.
//
// Deliberate, documented simplifications for a deterministic simulator (see
// docs/fix_protocol.md):
// * Symbol (tag 55) carries the numeric SymbolId in decimal, not a ticker
-// string — the internal model keys on SymbolId.
+// string, the internal model keys on SymbolId.
// * Price (tag 44) carries integer ticks, never a decimal/float, and is always
// present (including market orders). This keeps NewOrder<->FIX a lossless
// bijection over the internal struct, exactly like the binary codec, so a
@@ -21,7 +21,7 @@
//
// Decoding is total and deterministic: it never throws, allocates only the
// returned string on encode, and reports every malformed input through FixError
-// rather than undefined behavior — mirroring the binary codec's DecodeError
+// rather than undefined behavior, mirroring the binary codec's DecodeError
// discipline.
#include "qsl/protocol/messages.hpp"
diff --git a/ocaml/lib/diff_report.ml b/ocaml/lib/diff_report.ml
index 8e27c9c..e8d6f65 100644
--- a/ocaml/lib/diff_report.ml
+++ b/ocaml/lib/diff_report.ml
@@ -1,6 +1,6 @@
(* Differential failure artifact bundle (issue #40). On a C++/OCaml snapshot divergence for a
- fixture, write a small reviewable bundle — the original fixture, the OCaml-computed snapshot,
- the C++ expected snapshot, and a unified line diff — so CI can upload it for debugging. *)
+ fixture, write a small reviewable bundle, the original fixture, the OCaml-computed snapshot,
+ the C++ expected snapshot, and a unified line diff, so CI can upload it for debugging. *)
open Replay_engine
diff --git a/ocaml/test/test_mutation.ml b/ocaml/test/test_mutation.ml
index 4a4bed9..9b19cd1 100644
--- a/ocaml/test/test_mutation.ml
+++ b/ocaml/test/test_mutation.ml
@@ -1,7 +1,7 @@
(* Oracle mutation testing (issue #48). The differential layer detects a divergence iff the two
snapshots render to different lines (`snapshot_to_lines`, the exact comparison M17 uses). This
test takes one representative snapshot and applies a single-field mutation for every snapshot
- field — last_seq, trade count, symbol id, best bid/ask, order count, and bid/ask levels — asserting each
+ field, last_seq, trade count, symbol id, best bid/ask, order count, and bid/ask levels, asserting each
mutation changes the rendered lines. If any field stopped contributing to the comparison, a
real C++/OCaml divergence in it could pass silently; this fails instead.
diff --git a/regressions/README.md b/regressions/README.md
index 5694e03..5fd1004 100644
--- a/regressions/README.md
+++ b/regressions/README.md
@@ -8,7 +8,7 @@ generator or shrinker later changes (these entries are intentionally **not** gol
## Status
-The C++ engine and the OCaml oracle currently agree on every tested stream — **no real bug has
+The C++ engine and the OCaml oracle currently agree on every tested stream, **no real bug has
been found.** This archive is therefore seeded with one *synthetic* entry so the structure and
workflow exist for when a real divergence appears.
diff --git a/results/README.md b/results/README.md
index 8515411..e3bce86 100644
--- a/results/README.md
+++ b/results/README.md
@@ -2,59 +2,59 @@
Benchmark results produced by `make bench` and scripts under `scripts/`.
-- `latest.txt` — core microbenchmarks (`make bench`, `apps/qsl-bench`).
-- `differential.txt` — differential-testing harness benchmarks: command-stream generation,
+- `latest.txt`, core microbenchmarks (`make bench`, `apps/qsl-bench`).
+- `differential.txt`, differential-testing harness benchmarks: command-stream generation,
gateway replay, and shrinking (`make bench-diff`, `qsl-bench diff`).
-- `allocator_experiment.txt` — isolated M28 allocation experiment comparing `engine::Order`
+- `allocator_experiment.txt`, isolated M28 allocation experiment comparing `engine::Order`
`new/delete` with fixed-pool acquire/release (`make bench-allocator`, `qsl-bench pool`).
-- `pool_backed_storage.txt` — engine-level storage experiment comparing baseline order-book node
+- `pool_backed_storage.txt`, engine-level storage experiment comparing baseline order-book node
allocation against PMR-backed `std::list`/`std::map`/`std::unordered_map` node allocation, the
opt-in intrusive `OrderPool`-backed resting-order storage mode, and the M47 fixed-band
contiguous direct-price-indexed storage mode (`make bench-storage`, `qsl-bench storage`). Treat
the contiguous row as a bounded-domain cache/locality study, not general sparse-price-book
evidence.
-- `perf_stat_linux.txt` — Linux `perf stat` output for the benchmark harness (`make perf-stat`).
- Its `Artifact:` field classifies the run three ways: `hardware PMU evidence` (full — every
+- `perf_stat_linux.txt`. Linux `perf stat` output for the benchmark harness (`make perf-stat`).
+ Its `Artifact:` field classifies the run three ways: `hardware PMU evidence` (full, every
requested counter including cache events captured), `partial hardware PMU evidence` (real
- hardware counters present but the requested set is incomplete — the **current** bare-metal Apple
+ hardware counters present but the requested set is incomplete, the **current** bare-metal Apple
Silicon state, where `cache-references`/`cache-misses` are ``), or
`constrained-environment validation (no hardware PMU access)` (no hardware counter at all). Only
the first is full evidence; issue #90 tracks the missing cache-counter set.
-- `perf_report_linux.txt` — Linux `perf record/report` hot-symbol output for the benchmark
+- `perf_report_linux.txt`. Linux `perf record/report` hot-symbol output for the benchmark
harness (`make perf-record`). It is useful as a hot-symbol profile only when the file says
`No samples: no`, `Insufficient samples: no`, and the sample count meets the reported minimum.
-- `flamegraph.svg` / `flamegraph.txt` — Linux `perf` call-graph flamegraph (`make flamegraph`,
+- `flamegraph.svg` / `flamegraph.txt`. Linux `perf` call-graph flamegraph (`make flamegraph`,
issue #32) rendered by the dependency-free `scripts/flamegraph.py`. The `.svg` is the visual
(frame width ∝ on-CPU samples) with provenance in a leading XML comment; the `.txt` carries
provenance, the `Artifact:` classification, and the top folded stacks. It is a software cpu-clock
- sampling profile for hot-symbol investigation, not a latency/throughput claim — trust frame widths
+ sampling profile for hot-symbol investigation, not a latency/throughput claim, trust frame widths
only when the `.txt` reports a `flamegraph (...)` artifact with enough samples.
-- `numa_affinity_study.txt` — Linux CPU-affinity / scheduler-migration / NUMA-locality study
+- `numa_affinity_study.txt`. Linux CPU-affinity / scheduler-migration / NUMA-locality study
output (`make numa-study`). It must self-classify as `full-linux-numa`, `linux-constrained`, or
`unsupported-host`; only `full-linux-numa` is full NUMA evidence.
-- `false_sharing_study.txt` — benchmark-only packed-vs-padded SPSC queue-cursor contention study
+- `false_sharing_study.txt`, benchmark-only packed-vs-padded SPSC queue-cursor contention study
(`make false-sharing-study`). It is research-note evidence about cache-line sharing shape, not
a production throughput or latency claim.
-- `socket_load_summary.txt` — Linux multi-client TCP connection-scaling load experiment
+- `socket_load_summary.txt`. Linux multi-client TCP connection-scaling load experiment
(`make socket-load`, `scripts/socket_load.sh`): N concurrent `qsl-client`s against the threaded and
epoll gateways. Constrained loopback connection-setup shape only, not a production-capacity claim.
-- `socket_profile_loopback.txt` — Linux syscall/socket-path profiling of the gateway I/O path
+- `socket_profile_loopback.txt`. Linux syscall/socket-path profiling of the gateway I/O path
(`make profile-io`, `scripts/profile_gateway_io.sh`). Loopback, constrained evidence.
-- `socket_stress_summary.txt` — UDP socket-buffer / burst-loss experiment (`make socket-stress`):
+- `socket_stress_summary.txt`. UDP socket-buffer / burst-loss experiment (`make socket-stress`):
receive-buffer sizing vs observed sequence-gap loss on loopback. Research-note evidence only.
-- `crash_recovery_validation.txt` — M45 SIGKILL crash / torn-tail recovery validation for the
+- `crash_recovery_validation.txt`. M45 SIGKILL crash / torn-tail recovery validation for the
append-only event log across durability modes (`make crash-recovery`). It is process-kill
evidence only: it validates crash-mid-append recovery and acknowledged-record retention across
process death, not power-loss or OS-crash durability (see `docs/persistence.md`).
-- `recovery_benchmarks.txt` — M46 recovery benchmarking (`make bench-recovery`,
+- `recovery_benchmarks.txt`. M46 recovery benchmarking (`make bench-recovery`,
`qsl-bench recovery`): full-replay restart cost (log read/verify/classify plus replay into a
fresh engine) at several log lengths, against a benchmark-only in-memory snapshot-restoration
prototype at several live-state depths. Restart cost on this host (RTO-style) only; no
production recovery-time claim (see `docs/replay_and_recovery.md`).
-- `dpdk_environment.txt` — M48 non-mutating DPDK environment support check (`make dpdk-check`).
+- `dpdk_environment.txt`. M48 non-mutating DPDK environment support check (`make dpdk-check`).
It records whether the host can build or potentially run a DPDK prototype, but it never binds
devices, reserves hugepages, sends packets, or supports a kernel-bypass performance claim.
-- `nic_offload_environment.txt` — M49 non-mutating NIC offload/timestamping capability check
+- `nic_offload_environment.txt`. M49 non-mutating NIC offload/timestamping capability check
(`make nic-offload-check`). It records read-only host and interface context where Linux NICs are
visible, but it never changes offload flags, RSS tables, timestamp filters, driver bindings, IRQ
placement, or packet traffic, and it does not support a latency claim.
@@ -71,7 +71,7 @@ Benchmark results produced by `make bench` and scripts under `scripts/`.
- `Dirty inputs: no|yes`
- `Generated output: ...`
- No estimated or fabricated numbers. They are synthetic microbenchmarks, hardware/compiler/
- build-dependent — not production throughput.
+ build-dependent, not production throughput.
- For migrated artifacts, `Source digest` is the authoritative provenance identity. `Git commit
(informational)` may change after rebase or squash merge; reviewers should treat a source-digest
mismatch or `Dirty inputs: yes` as stale evidence, not commit-hash drift alone.
diff --git a/results/dpdk_environment.txt b/results/dpdk_environment.txt
index 1e3abf8..cb7552c 100644
--- a/results/dpdk_environment.txt
+++ b/results/dpdk_environment.txt
@@ -8,12 +8,12 @@ CPU: Avalanche-M2
Compiler: c++ (GCC) 16.1.1 20260515 (Red Hat 16.1.1-2)
Build type: n/a
Provenance version: 1
-Git commit (informational): 081e1ec
-Source digest: sha256:ab13cbebe013b05626085319748c5fb9e6d51383be00d78ed7337542d99d67c0
+Git commit (informational): 9754c47
+Source digest: sha256:4eeb3d9575ffa2b564ada7616151a490c17102294b3e8f45e424c02dd13fb8df
Source digest scope: dpdk-environment-check
Dirty inputs: no
Generated output: results/dpdk_environment.txt
-Date: 2026-06-25T02:37:40Z
+Date: 2026-06-25T04:16:39Z
pkg-config: /usr/bin/pkg-config
libdpdk pkg-config status: not-available
libdpdk version: not-found
diff --git a/results/flamegraph.svg b/results/flamegraph.svg
index 8ceef27..0606193 100644
--- a/results/flamegraph.svg
+++ b/results/flamegraph.svg
@@ -2,20 +2,20 @@
-
+]]>QSL Matching-Engine Flame Graph (qsl-bench)flamegraph (software cpu-clock sampling hot-symbol profile) | Linux aarch64 | cpu-clock @ 4000Hz | 20007 samples | 359 stacks | 2026-06-25T03:44:52ZSearchall (20007 cpu-clock samples, 100.00%)allqsl-bench (20007 cpu-clock samples, 100.00%)qsl-bench_start (20007 cpu-clock samples, 100.00%)_start__libc_start_main@@GLIBC_2.34 (20000 cpu-clock samples, 99.97%)__libc_start_main@@GLIBC_2.34__libc_start_call_main (20000 cpu-clock samples, 99.97%)__libc_start_call_mainmain (19978 cpu-clock samples, 99.86%)maincfree@GLIBC_2.17 (62 cpu-clock samples, 0.31%)qsl::engine::MatchingEngine::cancel(unsigned int, unsigned long) (2409 cpu-clock samples, 12.04%)qsl::engine::Matc..qsl::engine::OrderBook::cancel(unsigned long) (1971 cpu-clock samples, 9.85%)qsl::engine::..decltype(auto) qsl::engine::OrderBook::dispatch_storage<qsl::engine::OrderBook::cancel(unsigned long)::{lambda()#1}, qsl::engine::OrderBook::cancel(unsigned long)::{lambda(qsl::engine::OrderBook::IntrusiveStore&)#1}, qsl::engine::OrderBook::cancel(unsigned long)::{lambda(qsl::engine::OrderBook::ContiguousStore&)#1}>(qsl::engine::OrderBook::cancel(unsigned long)::{lambda()#1}&&, qsl::engine::OrderBook::cancel(unsigned long)::{lambda(qsl::engine::OrderBook::IntrusiveStore&)#1}&&, qsl::engine::OrderBook::cancel(unsigned long)::{lambda(qsl::engine::OrderBook::ContiguousStore&)#1}&&) [clone .isra.0] (1897 cpu-clock samples, 9.48%)decltype(auto..cfree@GLIBC_2.17 (44 cpu-clock samples, 0.22%)qsl::engine::OrderBook::erase_resting_order(qsl::engine::OrderBook::Locator const&) (878 cpu-clock samples, 4.39%)qsl:.._int_free_chunk (11 cpu-clock samples, 0.05%)_int_free_maybe_trim (2 cpu-clock samples, 0.01%)_int_free_merge_chunk (9 cpu-clock samples, 0.04%)_int_free_create_chunk (6 cpu-clock samples, 0.03%)cfree@GLIBC_2.17 (78 cpu-clock samples, 0.39%)free@plt (21 cpu-clock samples, 0.10%)operator delete(void*, std::align_val_t)@plt (20 cpu-clock samples, 0.10%)operator delete(void*, unsigned long, std::align_val_t)@plt (18 cpu-clock samples, 0.09%)std::_Rb_tree_rebalance_for_erase(std::_Rb_tree_node_base*, std::_Rb_tree_node_base&) (14 cpu-clock samples, 0.07%)std::__detail::_List_node_base::_M_unhook() (23 cpu-clock samples, 0.11%)std::__detail::_List_node_base::_M_unhook()@plt (23 cpu-clock samples, 0.11%)std::_Hashtable<unsigned long, std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, std::pmr::polymorphic_allocator<std::pair<unsigned long const, qsl::engine::OrderBook::Locator> >, std::__detail::_Select1st, std::equal_to<unsigned long>, std::hash<unsigned long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_erase(unsigned long, std::__detail::_Hash_node_base*, std::__detail::_Hash_node<std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, false>*) (268 cpu-clock samples, 1.34%)_int_free_chunk (12 cpu-clock samples, 0.06%)_int_free_merge_chunk (10 cpu-clock samples, 0.05%)_int_free_create_chunk (6 cpu-clock samples, 0.03%)cfree@GLIBC_2.17 (86 cpu-clock samples, 0.43%)free@plt (20 cpu-clock samples, 0.10%)operator delete(void*, std::align_val_t)@plt (23 cpu-clock samples, 0.11%)operator delete(void*, unsigned long, std::align_val_t)@plt (44 cpu-clock samples, 0.22%)std::pmr::(anonymous namespace)::newdel_res_t::do_deallocate(void*, unsigned long, unsigned long) (10 cpu-clock samples, 0.05%)qsl::engine::MatchingEngine::modify(unsigned int, unsigned long, long, unsigned int) (769 cpu-clock samples, 3.84%)qsl.._int_free_chunk (3 cpu-clock samples, 0.01%)cfree@GLIBC_2.17 (3 cpu-clock samples, 0.01%)free@plt (2 cpu-clock samples, 0.01%)operator delete(void*, unsigned long)@plt (2 cpu-clock samples, 0.01%)qsl::engine::OrderBook::can_apply_modify(unsigned long, long, unsigned int) const (5 cpu-clock samples, 0.02%)qsl::engine::OrderBook::contains(unsigned long) const (116 cpu-clock samples, 0.58%)qsl::engine::OrderBook::modify(unsigned long, long, unsigned int) (551 cpu-clock samples, 2.75%)q..decltype(auto) qsl::engine::OrderBook::dispatch_storage<qsl::engine::OrderBook::cancel(unsigned long)::{lambda()#1}, qsl::engine::OrderBook::cancel(unsigned long)::{lambda(qsl::engine::OrderBook::IntrusiveStore&)#1}, qsl::engine::OrderBook::cancel(unsigned long)::{lambda(qsl::engine::OrderBook::ContiguousStore&)#1}>(qsl::engine::OrderBook::cancel(unsigned long)::{lambda()#1}&&, qsl::engine::OrderBook::cancel(unsigned long)::{lambda(qsl::engine::OrderBook::IntrusiveStore&)#1}&&, qsl::engine::OrderBook::cancel(unsigned long)::{lambda(qsl::engine::OrderBook::ContiguousStore&)#1}&&) [clone .isra.0] (186 cpu-clock samples, 0.93%)cfree@GLIBC_2.17 (5 cpu-clock samples, 0.02%)qsl::engine::OrderBook::erase_resting_order(qsl::engine::OrderBook::Locator const&) (126 cpu-clock samples, 0.63%)_int_free_chunk (3 cpu-clock samples, 0.01%)_int_free_merge_chunk (3 cpu-clock samples, 0.01%)_int_free_create_chunk (2 cpu-clock samples, 0.01%)cfree@GLIBC_2.17 (12 cpu-clock samples, 0.06%)free@plt (2 cpu-clock samples, 0.01%)operator delete(void*, std::align_val_t)@plt (3 cpu-clock samples, 0.01%)std::_Rb_tree_rebalance_for_erase(std::_Rb_tree_node_base*, std::_Rb_tree_node_base&) (2 cpu-clock samples, 0.01%)std::__detail::_List_node_base::_M_unhook()@plt (4 cpu-clock samples, 0.02%)std::_Hashtable<unsigned long, std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, std::pmr::polymorphic_allocator<std::pair<unsigned long const, qsl::engine::OrderBook::Locator> >, std::__detail::_Select1st, std::equal_to<unsigned long>, std::hash<unsigned long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_erase(unsigned long, std::__detail::_Hash_node_base*, std::__detail::_Hash_node<std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, false>*) (38 cpu-clock samples, 0.19%)_int_free_chunk (3 cpu-clock samples, 0.01%)_int_free_merge_chunk (2 cpu-clock samples, 0.01%)_int_free_create_chunk (2 cpu-clock samples, 0.01%)cfree@GLIBC_2.17 (13 cpu-clock samples, 0.06%)free@plt (3 cpu-clock samples, 0.01%)operator delete(void*, std::align_val_t)@plt (7 cpu-clock samples, 0.03%)operator delete(void*, unsigned long, std::align_val_t)@plt (3 cpu-clock samples, 0.01%)qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce) (353 cpu-clock samples, 1.76%)cfree@GLIBC_2.17 (8 cpu-clock samples, 0.04%)decltype(auto) qsl::engine::OrderBook::dispatch_storage<qsl::engine::OrderBook::contains(unsigned long) const::{lambda()#1}, qsl::engine::OrderBook::contains(unsigned long) const::{lambda(qsl::engine::OrderBook::IntrusiveStore const&)#1}, qsl::engine::OrderBook::contains(unsigned long) const::{lambda(qsl::engine::OrderBook::ContiguousStore const&)#1}>(qsl::engine::OrderBook::contains(unsigned long) const::{lambda()#1}&&, qsl::engine::OrderBook::contains(unsigned long) const::{lambda(qsl::engine::OrderBook::IntrusiveStore const&)#1}&&, qsl::engine::OrderBook::contains(unsigned long) const::{lambda(qsl::engine::OrderBook::ContiguousStore const&)#1}&&) const [clone .isra.0] (3 cpu-clock samples, 0.01%)memcpy@plt (2 cpu-clock samples, 0.01%)operator delete(void*)@plt (6 cpu-clock samples, 0.03%)operator delete(void*, unsigned long) (4 cpu-clock samples, 0.02%)operator new(unsigned long) (11 cpu-clock samples, 0.05%)malloc (5 cpu-clock samples, 0.02%)malloc@plt (5 cpu-clock samples, 0.02%)qsl::engine::OrderBook::match_baseline(qsl::core::Side, qsl::engine::OrderBook::MatchContext&) (184 cpu-clock samples, 0.92%)cfree@GLIBC_2.17 (16 cpu-clock samples, 0.08%)free@plt (4 cpu-clock samples, 0.02%)operator delete(void*, unsigned long, std::align_val_t)@plt (4 cpu-clock samples, 0.02%)operator new(unsigned long) (9 cpu-clock samples, 0.04%)malloc (5 cpu-clock samples, 0.02%)malloc@plt (3 cpu-clock samples, 0.01%)qsl::engine::OrderBook::fill_front_order(std::__cxx11::list<qsl::engine::Order, std::pmr::polymorphic_allocator<qsl::engine::Order> >&, long, qsl::engine::OrderBook::MatchContext&) (58 cpu-clock samples, 0.29%)__memcpy_generic (3 cpu-clock samples, 0.01%)_int_free_chunk (2 cpu-clock samples, 0.01%)_int_free_merge_chunk (2 cpu-clock samples, 0.01%)cfree@GLIBC_2.17 (7 cpu-clock samples, 0.03%)free@plt (2 cpu-clock samples, 0.01%)operator delete(void*)@plt (2 cpu-clock samples, 0.01%)operator delete(void*, std::align_val_t)@plt (3 cpu-clock samples, 0.01%)operator delete(void*, unsigned long) (2 cpu-clock samples, 0.01%)operator new(unsigned long) (14 cpu-clock samples, 0.07%)malloc (4 cpu-clock samples, 0.02%)malloc@plt (6 cpu-clock samples, 0.03%)std::_Hashtable<unsigned long, std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, std::pmr::polymorphic_allocator<std::pair<unsigned long const, qsl::engine::OrderBook::Locator> >, std::__detail::_Select1st, std::equal_to<unsigned long>, std::hash<unsigned long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_erase(unsigned long, std::__detail::_Hash_node_base*, std::__detail::_Hash_node<std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, false>*) (5 cpu-clock samples, 0.02%)std::_Hashtable<unsigned long, std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, std::pmr::polymorphic_allocator<std::pair<unsigned long const, qsl::engine::OrderBook::Locator> >, std::__detail::_Select1st, std::equal_to<unsigned long>, std::hash<unsigned long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_erase(unsigned long, std::__detail::_Hash_node_base*, std::__detail::_Hash_node<std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, false>*) (9 cpu-clock samples, 0.04%)cfree@GLIBC_2.17 (2 cpu-clock samples, 0.01%)operator delete(void*, unsigned long, std::align_val_t)@plt (3 cpu-clock samples, 0.01%)std::_Rb_tree_rebalance_for_erase(std::_Rb_tree_node_base*, std::_Rb_tree_node_base&) (20 cpu-clock samples, 0.10%)std::_Rb_tree_rebalance_for_erase(std::_Rb_tree_node_base*, std::_Rb_tree_node_base&)@plt (3 cpu-clock samples, 0.01%)std::__detail::_List_node_base::_M_unhook() (2 cpu-clock samples, 0.01%)qsl::engine::OrderBook::rest(unsigned long, qsl::core::Side, long, unsigned int) (119 cpu-clock samples, 0.59%)operator new(unsigned long, std::align_val_t) (21 cpu-clock samples, 0.10%)__posix_memalign (8 cpu-clock samples, 0.04%)malloc (5 cpu-clock samples, 0.02%)_mid_memalign (3 cpu-clock samples, 0.01%)posix_memalign@plt (5 cpu-clock samples, 0.02%)qsl::engine::OrderBook::level_for[abi:cxx11](qsl::core::Side, long) (51 cpu-clock samples, 0.25%)operator new(unsigned long, std::align_val_t) (3 cpu-clock samples, 0.01%)__posix_memalign (2 cpu-clock samples, 0.01%)std::_Rb_tree_insert_and_rebalance(bool, std::_Rb_tree_node_base*, std::_Rb_tree_node_base*, std::_Rb_tree_node_base&) (3 cpu-clock samples, 0.01%)std::__detail::_Map_base<unsigned long, std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, std::pmr::polymorphic_allocator<std::pair<unsigned long const, qsl::engine::OrderBook::Locator> >, std::__detail::_Select1st, std::equal_to<unsigned long>, std::hash<unsigned long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true>, true>::operator[](unsigned long const&) (44 cpu-clock samples, 0.22%)operator new(unsigned long, std::align_val_t) (23 cpu-clock samples, 0.11%)__posix_memalign (17 cpu-clock samples, 0.08%)_mid_memalign (5 cpu-clock samples, 0.02%)malloc (6 cpu-clock samples, 0.03%)operator new(unsigned long, std::align_val_t)@plt (4 cpu-clock samples, 0.02%)std::_Hashtable<unsigned long, std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, std::pmr::polymorphic_allocator<std::pair<unsigned long const, qsl::engine::OrderBook::Locator> >, std::__detail::_Select1st, std::equal_to<unsigned long>, std::hash<unsigned long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_insert_unique_node(unsigned long, unsigned long, std::__detail::_Hash_node<std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, false>*, unsigned long) (8 cpu-clock samples, 0.04%)std::__detail::_Prime_rehash_policy::_M_need_rehash(unsigned long, unsigned long, unsigned long) const (2 cpu-clock samples, 0.01%)qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce) (15469 cpu-clock samples, 77.32%)qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce)_int_free_chunk (6 cpu-clock samples, 0.03%)_int_free_maybe_trim (2 cpu-clock samples, 0.01%)_int_free_merge_chunk (2 cpu-clock samples, 0.01%)cfree@GLIBC_2.17 (204 cpu-clock samples, 1.02%)free@plt (31 cpu-clock samples, 0.15%)operator delete(void*)@plt (31 cpu-clock samples, 0.15%)operator delete(void*, unsigned long)@plt (30 cpu-clock samples, 0.15%)operator new(unsigned long) (25 cpu-clock samples, 0.12%)qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce) (13920 cpu-clock samples, 69.58%)qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce)__memcpy_generic (110 cpu-clock samples, 0.55%)cfree@GLIBC_2.17 (255 cpu-clock samples, 1.27%)decltype(auto) qsl::engine::OrderBook::dispatch_storage<qsl::engine::OrderBook::contains(unsigned long) const::{lambda()#1}, qsl::engine::OrderBook::contains(unsigned long) const::{lambda(qsl::engine::OrderBook::IntrusiveStore const&)#1}, qsl::engine::OrderBook::contains(unsigned long) const::{lambda(qsl::engine::OrderBook::ContiguousStore const&)#1}>(qsl::engine::OrderBook::contains(unsigned long) const::{lambda()#1}&&, qsl::engine::OrderBook::contains(unsigned long) const::{lambda(qsl::engine::OrderBook::IntrusiveStore const&)#1}&&, qsl::engine::OrderBook::contains(unsigned long) const::{lambda(qsl::engine::OrderBook::ContiguousStore const&)#1}&&) const [clone .isra.0] (243 cpu-clock samples, 1.21%)free@plt (44 cpu-clock samples, 0.22%)memcpy@plt (25 cpu-clock samples, 0.12%)operator delete(void*)@plt (85 cpu-clock samples, 0.42%)operator delete(void*, unsigned long) (93 cpu-clock samples, 0.46%)operator delete(void*, unsigned long)@plt (67 cpu-clock samples, 0.33%)operator new(unsigned long) (343 cpu-clock samples, 1.71%)__libc_malloc2 (17 cpu-clock samples, 0.08%)_int_malloc (13 cpu-clock samples, 0.06%)malloc (177 cpu-clock samples, 0.88%)malloc@plt (54 cpu-clock samples, 0.27%)operator new(unsigned long)@plt (50 cpu-clock samples, 0.25%)qsl::engine::OrderBook::match_baseline(qsl::core::Side, qsl::engine::OrderBook::MatchContext&) (5837 cpu-clock samples, 29.17%)qsl::engine::OrderBook::match_baseline(qsl::co..__memcpy_generic (51 cpu-clock samples, 0.25%)_int_free_chunk (33 cpu-clock samples, 0.16%)_int_free_maybe_trim (6 cpu-clock samples, 0.03%)_int_free_merge_chunk (20 cpu-clock samples, 0.10%)_int_free_create_chunk (11 cpu-clock samples, 0.05%)unlink_chunk.isra.0 (2 cpu-clock samples, 0.01%)unlink_chunk.isra.0 (3 cpu-clock samples, 0.01%)_int_free_maybe_trim (2 cpu-clock samples, 0.01%)cfree@GLIBC_2.17 (358 cpu-clock samples, 1.79%)free@plt (82 cpu-clock samples, 0.41%)operator delete(void*)@plt (21 cpu-clock samples, 0.10%)operator delete(void*, std::align_val_t)@plt (73 cpu-clock samples, 0.36%)operator delete(void*, unsigned long) (36 cpu-clock samples, 0.18%)operator delete(void*, unsigned long)@plt (4 cpu-clock samples, 0.02%)operator delete(void*, unsigned long, std::align_val_t) (2 cpu-clock samples, 0.01%)operator delete(void*, unsigned long, std::align_val_t)@plt (116 cpu-clock samples, 0.58%)operator new(unsigned long) (304 cpu-clock samples, 1.52%)__libc_malloc2 (15 cpu-clock samples, 0.07%)_int_malloc (13 cpu-clock samples, 0.06%)malloc (155 cpu-clock samples, 0.77%)malloc@plt (64 cpu-clock samples, 0.32%)operator new(unsigned long)@plt (58 cpu-clock samples, 0.29%)qsl::engine::OrderBook::fill_front_order(std::__cxx11::list<qsl::engine::Order, std::pmr::polymorphic_allocator<qsl::engine::Order> >&, long, qsl::engine::OrderBook::MatchContext&) (1261 cpu-clock samples, 6.30%)qsl::en..__memcpy_generic (42 cpu-clock samples, 0.21%)_int_free_chunk (18 cpu-clock samples, 0.09%)_int_free_maybe_trim (3 cpu-clock samples, 0.01%)_int_free_merge_chunk (13 cpu-clock samples, 0.06%)_int_free_create_chunk (8 cpu-clock samples, 0.04%)_int_free_merge_chunk (2 cpu-clock samples, 0.01%)cfree@GLIBC_2.17 (141 cpu-clock samples, 0.70%)free@plt (29 cpu-clock samples, 0.14%)operator delete(void*)@plt (32 cpu-clock samples, 0.16%)operator delete(void*, std::align_val_t)@plt (19 cpu-clock samples, 0.09%)operator delete(void*, unsigned long) (39 cpu-clock samples, 0.19%)operator delete(void*, unsigned long)@plt (7 cpu-clock samples, 0.03%)operator delete(void*, unsigned long, std::align_val_t)@plt (34 cpu-clock samples, 0.17%)operator new(unsigned long) (208 cpu-clock samples, 1.04%)__libc_malloc2 (20 cpu-clock samples, 0.10%)_int_malloc (19 cpu-clock samples, 0.09%)malloc (105 cpu-clock samples, 0.52%)malloc@plt (47 cpu-clock samples, 0.23%)operator new(unsigned long)@plt (30 cpu-clock samples, 0.15%)std::_Hashtable<unsigned long, std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, std::pmr::polymorphic_allocator<std::pair<unsigned long const, qsl::engine::OrderBook::Locator> >, std::__detail::_Select1st, std::equal_to<unsigned long>, std::hash<unsigned long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_erase(unsigned long, std::__detail::_Hash_node_base*, std::__detail::_Hash_node<std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, false>*) (239 cpu-clock samples, 1.19%)_int_free_chunk (7 cpu-clock samples, 0.03%)_int_free_maybe_trim (2 cpu-clock samples, 0.01%)_int_free_merge_chunk (4 cpu-clock samples, 0.02%)_int_free_create_chunk (2 cpu-clock samples, 0.01%)cfree@GLIBC_2.17 (87 cpu-clock samples, 0.43%)free@plt (21 cpu-clock samples, 0.10%)operator delete(void*, std::align_val_t)@plt (25 cpu-clock samples, 0.12%)operator delete(void*, unsigned long, std::align_val_t)@plt (27 cpu-clock samples, 0.13%)std::pmr::(anonymous namespace)::newdel_res_t::do_deallocate(void*, unsigned long, unsigned long) (3 cpu-clock samples, 0.01%)std::__detail::_List_node_base::_M_unhook() (13 cpu-clock samples, 0.06%)std::__detail::_List_node_base::_M_unhook()@plt (13 cpu-clock samples, 0.06%)std::pmr::(anonymous namespace)::newdel_res_t::do_deallocate(void*, unsigned long, unsigned long) (6 cpu-clock samples, 0.03%)std::_Hashtable<unsigned long, std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, std::pmr::polymorphic_allocator<std::pair<unsigned long const, qsl::engine::OrderBook::Locator> >, std::__detail::_Select1st, std::equal_to<unsigned long>, std::hash<unsigned long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_erase(unsigned long, std::__detail::_Hash_node_base*, std::__detail::_Hash_node<std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, false>*) (333 cpu-clock samples, 1.66%)_int_free_chunk (13 cpu-clock samples, 0.06%)_int_free_maybe_trim (2 cpu-clock samples, 0.01%)_int_free_merge_chunk (9 cpu-clock samples, 0.04%)_int_free_create_chunk (2 cpu-clock samples, 0.01%)cfree@GLIBC_2.17 (88 cpu-clock samples, 0.44%)free@plt (35 cpu-clock samples, 0.17%)operator delete(void*, std::align_val_t)@plt (26 cpu-clock samples, 0.13%)operator delete(void*, unsigned long, std::align_val_t)@plt (59 cpu-clock samples, 0.29%)std::pmr::(anonymous namespace)::newdel_res_t::do_deallocate(void*, unsigned long, unsigned long) (11 cpu-clock samples, 0.05%)std::_Rb_tree_rebalance_for_erase(std::_Rb_tree_node_base*, std::_Rb_tree_node_base&) (600 cpu-clock samples, 3.00%)st..std::_Rb_tree_rebalance_for_erase(std::_Rb_tree_node_base*, std::_Rb_tree_node_base&)@plt (17 cpu-clock samples, 0.08%)std::__detail::_List_node_base::_M_unhook() (21 cpu-clock samples, 0.10%)std::__detail::_List_node_base::_M_unhook()@plt (20 cpu-clock samples, 0.10%)std::pmr::(anonymous namespace)::newdel_res_t::do_deallocate(void*, unsigned long, unsigned long) (22 cpu-clock samples, 0.11%)qsl::engine::OrderBook::rest(unsigned long, qsl::core::Side, long, unsigned int) (6053 cpu-clock samples, 30.25%)qsl::engine::OrderBook::rest(unsigned long, qsl:..__posix_memalign (7 cpu-clock samples, 0.03%)operator new(unsigned long, std::align_val_t) (846 cpu-clock samples, 4.23%)oper..__libc_malloc2 (2 cpu-clock samples, 0.01%)__posix_memalign (597 cpu-clock samples, 2.98%)__..__libc_malloc2 (76 cpu-clock samples, 0.38%)_int_malloc (67 cpu-clock samples, 0.33%)unlink_chunk.isra.0 (2 cpu-clock samples, 0.01%)_int_malloc (2 cpu-clock samples, 0.01%)_mid_memalign (102 cpu-clock samples, 0.51%)malloc (248 cpu-clock samples, 1.24%)_mid_memalign (42 cpu-clock samples, 0.21%)posix_memalign@plt (50 cpu-clock samples, 0.25%)operator new(unsigned long, std::align_val_t)@plt (91 cpu-clock samples, 0.45%)qsl::engine::OrderBook::level_for[abi:cxx11](qsl::core::Side, long) (3372 cpu-clock samples, 16.85%)qsl::engine::OrderBook::l..operator new(unsigned long, std::align_val_t) (422 cpu-clock samples, 2.11%)__posix_memalign (275 cpu-clock samples, 1.37%)__libc_malloc2 (5 cpu-clock samples, 0.02%)_int_malloc (5 cpu-clock samples, 0.02%)_mid_memalign (62 cpu-clock samples, 0.31%)malloc (118 cpu-clock samples, 0.59%)_mid_memalign (19 cpu-clock samples, 0.09%)posix_memalign@plt (24 cpu-clock samples, 0.12%)operator new(unsigned long, std::align_val_t)@plt (78 cpu-clock samples, 0.39%)std::_Rb_tree_decrement(std::_Rb_tree_node_base*) (91 cpu-clock samples, 0.45%)std::_Rb_tree_decrement(std::_Rb_tree_node_base*)@plt (5 cpu-clock samples, 0.02%)std::_Rb_tree_insert_and_rebalance(bool, std::_Rb_tree_node_base*, std::_Rb_tree_node_base*, std::_Rb_tree_node_base&) (544 cpu-clock samples, 2.72%)s..std::_Rb_tree_insert_and_rebalance(bool, std::_Rb_tree_node_base*, std::_Rb_tree_node_base*, std::_Rb_tree_node_base&)@plt (43 cpu-clock samples, 0.21%)std::pmr::(anonymous namespace)::newdel_res_t::do_allocate(unsigned long, unsigned long) (19 cpu-clock samples, 0.09%)std::__detail::_List_node_base::_M_hook(std::__detail::_List_node_base*) (50 cpu-clock samples, 0.25%)std::__detail::_List_node_base::_M_hook(std::__detail::_List_node_base*)@plt (33 cpu-clock samples, 0.16%)std::__detail::_Map_base<unsigned long, std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, std::pmr::polymorphic_allocator<std::pair<unsigned long const, qsl::engine::OrderBook::Locator> >, std::__detail::_Select1st, std::equal_to<unsigned long>, std::hash<unsigned long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true>, true>::operator[](unsigned long const&) (1491 cpu-clock samples, 7.45%)std::__de..__posix_memalign (27 cpu-clock samples, 0.13%)operator new(unsigned long, std::align_val_t) (730 cpu-clock samples, 3.65%)ope..__posix_memalign (502 cpu-clock samples, 2.51%)_..__libc_malloc2 (30 cpu-clock samples, 0.15%)_int_malloc (28 cpu-clock samples, 0.14%)unlink_chunk.isra.0 (2 cpu-clock samples, 0.01%)_mid_memalign (75 cpu-clock samples, 0.37%)malloc (240 cpu-clock samples, 1.20%)_mid_memalign (42 cpu-clock samples, 0.21%)posix_memalign@plt (45 cpu-clock samples, 0.22%)operator new(unsigned long, std::align_val_t)@plt (133 cpu-clock samples, 0.66%)std::_Hashtable<unsigned long, std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, std::pmr::polymorphic_allocator<std::pair<unsigned long const, qsl::engine::OrderBook::Locator> >, std::__detail::_Select1st, std::equal_to<unsigned long>, std::hash<unsigned long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_insert_unique_node(unsigned long, unsigned long, std::__detail::_Hash_node<std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, false>*, unsigned long) (305 cpu-clock samples, 1.52%)std::__detail::_Prime_rehash_policy::_M_need_rehash(unsigned long, unsigned long, unsigned long) const (65 cpu-clock samples, 0.32%)std::__detail::_Prime_rehash_policy::_M_need_rehash(unsigned long, unsigned long, unsigned long) const@plt (50 cpu-clock samples, 0.25%)std::pmr::(anonymous namespace)::newdel_res_t::do_allocate(unsigned long, unsigned long) (34 cpu-clock samples, 0.17%)std::pmr::(anonymous namespace)::newdel_res_t::do_allocate(unsigned long, unsigned long) (6 cpu-clock samples, 0.03%)qsl::engine::OrderBook::can_store_limit(qsl::core::Side, long, unsigned int, qsl::core::TimeInForce) const (193 cpu-clock samples, 0.96%)qsl::engine::OrderBook::contains(unsigned long) const (231 cpu-clock samples, 1.15%)qsl::engine::MatchingEngine::new_market(unsigned int, unsigned long, qsl::core::Side, unsigned int) (695 cpu-clock samples, 3.47%)qs.._int_free_chunk (2 cpu-clock samples, 0.01%)_int_free_merge_chunk (2 cpu-clock samples, 0.01%)cfree@GLIBC_2.17 (33 cpu-clock samples, 0.16%)free@plt (9 cpu-clock samples, 0.04%)operator delete(void*)@plt (11 cpu-clock samples, 0.05%)operator delete(void*, unsigned long) (2 cpu-clock samples, 0.01%)qsl::engine::OrderBook::add_market(unsigned long, qsl::core::Side, unsigned int) (567 cpu-clock samples, 2.83%)q..cfree@GLIBC_2.17 (5 cpu-clock samples, 0.02%)decltype(auto) qsl::engine::OrderBook::dispatch_storage<qsl::engine::OrderBook::contains(unsigned long) const::{lambda()#1}, qsl::engine::OrderBook::contains(unsigned long) const::{lambda(qsl::engine::OrderBook::IntrusiveStore const&)#1}, qsl::engine::OrderBook::contains(unsigned long) const::{lambda(qsl::engine::OrderBook::ContiguousStore const&)#1}>(qsl::engine::OrderBook::contains(unsigned long) const::{lambda()#1}&&, qsl::engine::OrderBook::contains(unsigned long) const::{lambda(qsl::engine::OrderBook::IntrusiveStore const&)#1}&&, qsl::engine::OrderBook::contains(unsigned long) const::{lambda(qsl::engine::OrderBook::ContiguousStore const&)#1}&&) const [clone .isra.0] (11 cpu-clock samples, 0.05%)qsl::engine::OrderBook::match_baseline(qsl::core::Side, qsl::engine::OrderBook::MatchContext&) (521 cpu-clock samples, 2.60%)q..cfree@GLIBC_2.17 (18 cpu-clock samples, 0.09%)free@plt (2 cpu-clock samples, 0.01%)operator delete(void*, std::align_val_t)@plt (2 cpu-clock samples, 0.01%)operator delete(void*, unsigned long, std::align_val_t)@plt (7 cpu-clock samples, 0.03%)operator new(unsigned long) (5 cpu-clock samples, 0.02%)qsl::engine::OrderBook::fill_front_order(std::__cxx11::list<qsl::engine::Order, std::pmr::polymorphic_allocator<qsl::engine::Order> >&, long, qsl::engine::OrderBook::MatchContext&) (345 cpu-clock samples, 1.72%)__memcpy_generic (5 cpu-clock samples, 0.02%)_int_free_chunk (8 cpu-clock samples, 0.04%)_int_free_merge_chunk (5 cpu-clock samples, 0.02%)_int_free_create_chunk (2 cpu-clock samples, 0.01%)_int_free_merge_chunk (2 cpu-clock samples, 0.01%)cfree@GLIBC_2.17 (35 cpu-clock samples, 0.17%)free@plt (10 cpu-clock samples, 0.05%)operator delete(void*)@plt (6 cpu-clock samples, 0.03%)operator delete(void*, std::align_val_t)@plt (4 cpu-clock samples, 0.02%)operator delete(void*, unsigned long) (6 cpu-clock samples, 0.03%)operator delete(void*, unsigned long, std::align_val_t)@plt (13 cpu-clock samples, 0.06%)operator new(unsigned long) (81 cpu-clock samples, 0.40%)__libc_malloc2 (4 cpu-clock samples, 0.02%)_int_malloc (4 cpu-clock samples, 0.02%)malloc (44 cpu-clock samples, 0.22%)malloc@plt (18 cpu-clock samples, 0.09%)operator new(unsigned long)@plt (13 cpu-clock samples, 0.06%)std::_Hashtable<unsigned long, std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, std::pmr::polymorphic_allocator<std::pair<unsigned long const, qsl::engine::OrderBook::Locator> >, std::__detail::_Select1st, std::equal_to<unsigned long>, std::hash<unsigned long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_erase(unsigned long, std::__detail::_Hash_node_base*, std::__detail::_Hash_node<std::pair<unsigned long const, qsl::engine::OrderBook::Locator>, false>*) (46 cpu-clock samples, 0.23%)_int_free_chunk (2 cpu-clock samples, 0.01%)_int_free_merge_chunk (2 cpu-clock samples, 0.01%)cfree@GLIBC_2.17 (17 cpu-clock samples, 0.08%)free@plt (3 cpu-clock samples, 0.01%)operator delete(void*, std::align_val_t)@plt (3 cpu-clock samples, 0.01%)operator delete(void*, unsigned long, std::align_val_t)@plt (9 cpu-clock samples, 0.04%)std::__detail::_List_node_base::_M_unhook() (2 cpu-clock samples, 0.01%)std::__detail::_List_node_base::_M_unhook()@plt (3 cpu-clock samples, 0.01%)std::_Rb_tree_rebalance_for_erase(std::_Rb_tree_node_base*, std::_Rb_tree_node_base&) (55 cpu-clock samples, 0.27%)qsl::engine::OrderBook::contains(unsigned long) const (11 cpu-clock samples, 0.05%)qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce) (22 cpu-clock samples, 0.11%)_dl_start (7 cpu-clock samples, 0.03%)_dl_sysdep_start (7 cpu-clock samples, 0.03%)dl_main (7 cpu-clock samples, 0.03%)_dl_relocate_object (6 cpu-clock samples, 0.03%)_dl_relocate_object_no_relro (6 cpu-clock samples, 0.03%)_dl_lookup_symbol_x (5 cpu-clock samples, 0.02%)do_lookup_x (2 cpu-clock samples, 0.01%)
diff --git a/results/flamegraph.txt b/results/flamegraph.txt
index 2a7ae73..fb1aedb 100644
--- a/results/flamegraph.txt
+++ b/results/flamegraph.txt
@@ -8,20 +8,20 @@ Perf: perf version 6.19.14-400.asahi.fc44.aarch64
Perf paranoid: 2
Build type: Release
Provenance version: 1
-Git commit (informational): ebc1c95
-Source digest: sha256:201221d4d6344c0dd55d1f497b67337503e2876208e65dfd5db4f450f568226d
+Git commit (informational): 4e7c3fe
+Source digest: sha256:63c2f5e12ee6dd59dd5d01bb1698984a3de6566010ac8058a63fcf021b2b9189
Source digest scope: flamegraph-benchmark
Dirty inputs: no
Generated output: results/flamegraph.svg
-Date: 2026-06-25T01:57:35Z
+Date: 2026-06-25T03:44:52Z
Benchmark binary: build/flamegraph/qsl-bench
Dataset: qsl-bench profile workload (warm bounded order flow, 5s)
Call graph: fp
Record event: cpu-clock
Sample freq: 4000 Hz
-Sample count (folded total): 19991
-Sample count (perf record est.): 19991
-Folded stacks: 338
+Sample count (folded total): 20007
+Sample count (perf record est.): 20007
+Folded stacks: 359
Minimum samples for hot profile: 200
Insufficient samples: no
Record status: 0
@@ -35,21 +35,21 @@ investigation. Frame width is proportional to on-CPU samples, not wall-clock
latency or throughput, and is hardware/kernel/compiler/build dependent.
Top 15 folded stacks (count stack):
- 2385 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::match_baseline(qsl::core::Side, qsl::engine::OrderBook::MatchContext&)
- 2076 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::rest(unsigned long, qsl::core::Side, long, unsigned int);qsl::engine::OrderBook::level_for[abi:cxx11](qsl::core::Side, long)
- 862 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce)
- 820 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::cancel(unsigned int, unsigned long);qsl::engine::OrderBook::cancel(unsigned long);decltype(auto) qsl::engine::OrderBook::dispatch_storage(qsl::engine::OrderBook::cancel(unsigned long)::{lambda()#1}&&, qsl::engine::OrderBook::cancel(unsigned long)::{lambda(qsl::engine::OrderBook::IntrusiveStore&)#1}&&, qsl::engine::OrderBook::cancel(unsigned long)::{lambda(qsl::engine::OrderBook::ContiguousStore&)#1}&&) [clone .isra.0]
- 677 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::cancel(unsigned int, unsigned long);qsl::engine::OrderBook::cancel(unsigned long);decltype(auto) qsl::engine::OrderBook::dispatch_storage(qsl::engine::OrderBook::cancel(unsigned long)::{lambda()#1}&&, qsl::engine::OrderBook::cancel(unsigned long)::{lambda(qsl::engine::OrderBook::IntrusiveStore&)#1}&&, qsl::engine::OrderBook::cancel(unsigned long)::{lambda(qsl::engine::OrderBook::ContiguousStore&)#1}&&) [clone .isra.0];qsl::engine::OrderBook::erase_resting_order(qsl::engine::OrderBook::Locator const&)
- 676 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce)
- 630 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::match_baseline(qsl::core::Side, qsl::engine::OrderBook::MatchContext&);std::_Rb_tree_rebalance_for_erase(std::_Rb_tree_node_base*, std::_Rb_tree_node_base&)
- 614 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main
- 542 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::rest(unsigned long, qsl::core::Side, long, unsigned int);qsl::engine::OrderBook::level_for[abi:cxx11](qsl::core::Side, long);std::_Rb_tree_insert_and_rebalance(bool, std::_Rb_tree_node_base*, std::_Rb_tree_node_base*, std::_Rb_tree_node_base&)
- 439 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::cancel(unsigned int, unsigned long)
- 382 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::match_baseline(qsl::core::Side, qsl::engine::OrderBook::MatchContext&);cfree@GLIBC_2.17
- 365 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::match_baseline(qsl::core::Side, qsl::engine::OrderBook::MatchContext&);qsl::engine::OrderBook::fill_front_order(std::__cxx11::list >&, long, qsl::engine::OrderBook::MatchContext&)
- 278 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);cfree@GLIBC_2.17
- 278 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::rest(unsigned long, qsl::core::Side, long, unsigned int);std::__detail::_Map_base, std::pmr::polymorphic_allocator >, std::__detail::_Select1st, std::equal_to, std::hash, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits, true>::operator[](unsigned long const&)
- 257 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::rest(unsigned long, qsl::core::Side, long, unsigned int);std::__detail::_Map_base, std::pmr::polymorphic_allocator >, std::__detail::_Select1st, std::equal_to, std::hash, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits, true>::operator[](unsigned long const&);operator new(unsigned long, std::align_val_t);__posix_memalign;malloc
+ 2423 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::match_baseline(qsl::core::Side, qsl::engine::OrderBook::MatchContext&)
+ 2170 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::rest(unsigned long, qsl::core::Side, long, unsigned int);qsl::engine::OrderBook::level_for[abi:cxx11](qsl::core::Side, long)
+ 796 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce)
+ 709 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce)
+ 706 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::cancel(unsigned int, unsigned long);qsl::engine::OrderBook::cancel(unsigned long);decltype(auto) qsl::engine::OrderBook::dispatch_storage(qsl::engine::OrderBook::cancel(unsigned long)::{lambda()#1}&&, qsl::engine::OrderBook::cancel(unsigned long)::{lambda(qsl::engine::OrderBook::IntrusiveStore&)#1}&&, qsl::engine::OrderBook::cancel(unsigned long)::{lambda(qsl::engine::OrderBook::ContiguousStore&)#1}&&) [clone .isra.0]
+ 669 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::cancel(unsigned int, unsigned long);qsl::engine::OrderBook::cancel(unsigned long);decltype(auto) qsl::engine::OrderBook::dispatch_storage(qsl::engine::OrderBook::cancel(unsigned long)::{lambda()#1}&&, qsl::engine::OrderBook::cancel(unsigned long)::{lambda(qsl::engine::OrderBook::IntrusiveStore&)#1}&&, qsl::engine::OrderBook::cancel(unsigned long)::{lambda(qsl::engine::OrderBook::ContiguousStore&)#1}&&) [clone .isra.0];qsl::engine::OrderBook::erase_resting_order(qsl::engine::OrderBook::Locator const&)
+ 600 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::match_baseline(qsl::core::Side, qsl::engine::OrderBook::MatchContext&);std::_Rb_tree_rebalance_for_erase(std::_Rb_tree_node_base*, std::_Rb_tree_node_base&)
+ 572 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main
+ 544 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::rest(unsigned long, qsl::core::Side, long, unsigned int);qsl::engine::OrderBook::level_for[abi:cxx11](qsl::core::Side, long);std::_Rb_tree_insert_and_rebalance(bool, std::_Rb_tree_node_base*, std::_Rb_tree_node_base*, std::_Rb_tree_node_base&)
+ 438 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::cancel(unsigned int, unsigned long)
+ 387 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::match_baseline(qsl::core::Side, qsl::engine::OrderBook::MatchContext&);qsl::engine::OrderBook::fill_front_order(std::__cxx11::list >&, long, qsl::engine::OrderBook::MatchContext&)
+ 358 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::match_baseline(qsl::core::Side, qsl::engine::OrderBook::MatchContext&);cfree@GLIBC_2.17
+ 262 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::rest(unsigned long, qsl::core::Side, long, unsigned int);std::__detail::_Map_base, std::pmr::polymorphic_allocator >, std::__detail::_Select1st, std::equal_to, std::hash, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits, true>::operator[](unsigned long const&)
+ 255 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);cfree@GLIBC_2.17
+ 248 qsl-bench;_start;__libc_start_main@@GLIBC_2.34;__libc_start_call_main;main;qsl::engine::MatchingEngine::new_limit(unsigned int, unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::add_limit(unsigned long, qsl::core::Side, long, unsigned int, qsl::core::TimeInForce);qsl::engine::OrderBook::rest(unsigned long, qsl::core::Side, long, unsigned int);operator new(unsigned long, std::align_val_t);__posix_memalign;malloc
Benchmark output:
-profile workload: storage=baseline ops=50659328 elapsed=5.000s (10131572 ops/sec) resting~512
+profile workload: storage=baseline ops=50593792 elapsed=5.000s (10118159 ops/sec) resting~512
diff --git a/results/nic_offload_environment.txt b/results/nic_offload_environment.txt
index 14c7f86..0d1327b 100644
--- a/results/nic_offload_environment.txt
+++ b/results/nic_offload_environment.txt
@@ -8,12 +8,12 @@ CPU: Avalanche-M2
Compiler: c++ (GCC) 16.1.1 20260515 (Red Hat 16.1.1-2)
Build type: n/a
Provenance version: 1
-Git commit (informational): 081e1ec
-Source digest: sha256:088a8ba85f514bc6264b43b5754e95f6a8f2e0a239936492f50f800031f8f782
+Git commit (informational): 9754c47
+Source digest: sha256:84058a6774d56bdf9117a9d6925ec0c9e2077f8ece5d8263659caed8e1d56a0e
Source digest scope: nic-offload-environment-check
Dirty inputs: no
Generated output: results/nic_offload_environment.txt
-Date: 2026-06-25T02:37:40Z
+Date: 2026-06-25T04:16:39Z
ethtool: /usr/bin/ethtool
ip: /usr/bin/ip
lspci: /usr/bin/lspci
@@ -49,7 +49,7 @@ tx queues: 1
== ip -details link show dev docker0 ==
4: docker0: mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default
link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff promiscuity 0 allmulti 0 minmtu 68 maxmtu 65535 netns-immutable
- bridge forward_delay 1500 hello_time 200 max_age 2000 ageing_time 30000 stp_state 0 priority 32768 vlan_filtering 0 vlan_protocol 802.1Q bridge_id 8000.xx:xx:xx:xx:xx:xx designated_root 8000.xx:xx:xx:xx:xx:xx root_port 0 root_path_cost 0 topology_change 0 topology_change_detected 0 hello_timer 0.00 tcn_timer 0.00 topology_change_timer 0.00 gc_timer 0.00 fdb_n_learned 0 fdb_max_learned 0 vlan_default_pvid 1 vlan_stats_enabled 0 vlan_stats_per_port 0 group_fwd_mask 0 group_address xx:xx:xx:xx:xx:xx mcast_snooping 1 no_linklocal_learn 0 mcast_vlan_snooping 0 mst_enabled 0 mdb_offload_fail_notification 0 mcast_router 1 mcast_query_use_ifaddr 0 mcast_querier 0 mcast_hash_elasticity 16 mcast_hash_max 4096 mcast_last_member_count 2 mcast_startup_query_count 2 mcast_last_member_interval 100 mcast_membership_interval 26000 mcast_querier_interval 25500 mcast_query_interval 12500 mcast_query_response_interval 1000 mcast_startup_query_interval 3125 mcast_stats_enabled 0 mcast_igmp_version 2 mcast_mld_version 1 nf_call_iptables 0 nf_call_ip6tables 0 nf_call_arptables 0 addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 tso_max_size 65536 tso_max_segs 65535 gro_max_size 65536 gso_ipv4_max_size 65536 gro_ipv4_max_size 65536
+ bridge forward_delay 1500 hello_time 200 max_age 2000 ageing_time 30000 stp_state 0 priority 32768 vlan_filtering 0 vlan_protocol 802.1Q bridge_id 8000.xx:xx:xx:xx:xx:xx designated_root 8000.xx:xx:xx:xx:xx:xx root_port 0 root_path_cost 0 topology_change 0 topology_change_detected 0 hello_timer 0.00 tcn_timer 0.00 topology_change_timer 0.00 gc_timer 276.30 fdb_n_learned 0 fdb_max_learned 0 vlan_default_pvid 1 vlan_stats_enabled 0 vlan_stats_per_port 0 group_fwd_mask 0 group_address xx:xx:xx:xx:xx:xx mcast_snooping 1 no_linklocal_learn 0 mcast_vlan_snooping 0 mst_enabled 0 mdb_offload_fail_notification 0 mcast_router 1 mcast_query_use_ifaddr 0 mcast_querier 0 mcast_hash_elasticity 16 mcast_hash_max 4096 mcast_last_member_count 2 mcast_startup_query_count 2 mcast_last_member_interval 100 mcast_membership_interval 26000 mcast_querier_interval 25500 mcast_query_interval 12500 mcast_query_response_interval 1000 mcast_startup_query_interval 3125 mcast_stats_enabled 0 mcast_igmp_version 2 mcast_mld_version 1 nf_call_iptables 0 nf_call_ip6tables 0 nf_call_arptables 0 addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 tso_max_size 65536 tso_max_segs 65535 gro_max_size 65536 gso_ipv4_max_size 65536 gro_ipv4_max_size 65536
== ethtool -i docker0 ==
driver: bridge
diff --git a/scripts/determinism_check.sh b/scripts/determinism_check.sh
index 8518f7c..91f0e01 100755
--- a/scripts/determinism_check.sh
+++ b/scripts/determinism_check.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# Cross-compiler / platform determinism (issue #45): build the fixture exporter with two
# compilers and assert every generated differential fixture is byte-identical between them, and
-# byte-identical to the committed copies (which were produced on macOS/AppleClang — so this also
+# byte-identical to the committed copies (which were produced on macOS/AppleClang, so this also
# exercises cross-platform reproducibility). Generation is integer-only and uses a standard
# mt19937_64, so the bytes must not depend on the toolchain.
#
diff --git a/scripts/divergence_demo.sh b/scripts/divergence_demo.sh
index 18c197f..a87012b 100755
--- a/scripts/divergence_demo.sh
+++ b/scripts/divergence_demo.sh
@@ -30,7 +30,7 @@ grep -vE '^evt ' "$TMP"
echo "=== honest OCaml replay vs C++ snapshot ==="
if diff -q "$TMP.emb" "$TMP.real" >/dev/null; then
- echo "AGREE — no real divergence between C++ and OCaml"
+ echo "AGREE, no real divergence between C++ and OCaml"
else
echo "UNEXPECTED: honest OCaml replay disagrees with C++" >&2
exit 1
@@ -41,7 +41,7 @@ if diff -q "$TMP.emb" "$TMP.mut" >/dev/null; then
echo "UNEXPECTED: injected bug produced no divergence" >&2
exit 1
fi
-echo "DIVERGE — shrinker reproduced the injected C++/OCaml mismatch at minimal size:"
+echo "DIVERGE, shrinker reproduced the injected C++/OCaml mismatch at minimal size:"
diff "$TMP.emb" "$TMP.mut" || true
echo "divergence demo OK"
diff --git a/scripts/flamegraph.py b/scripts/flamegraph.py
index b10a145..75ec697 100755
--- a/scripts/flamegraph.py
+++ b/scripts/flamegraph.py
@@ -89,10 +89,21 @@ def _flush(self) -> None:
if self._stack:
frames = list(reversed(self._stack)) # perf prints leaf-first
if self._drop_unknown:
- # Frame-pointer unwinding emits a single unresolvable "[unknown]" frame at the
- # glibc allocator boundary (Fedora's libc is built without frame pointers). Fold it
- # into its caller: the sample is preserved and the real neighbours (the app frame and
- # the named libc symbol such as cfree/operator new) stay in the stack.
+ # Every "[unknown]" frame on this aarch64 host has been IDENTIFIED (it is not a
+ # mystery) and is a fp-unwinding artifact whose samples belong to its nearest resolved
+ # frame, so each is folded into that caller. Two kinds occur:
+ # 1. Allocator boundary: fp inserts one spurious frame (a corrupted address such as
+ # 0x62ffff027c1a63) between two RESOLVED frames at the glibc malloc edge, e.g.
+ # operator new(...) ; [unknown] ; _mid_memalign / _int_malloc / _int_free / cfree
+ # glibc 2.43's malloc fast paths do not preserve x29, so walking out of them
+ # reads a data register as a return address. Removing it reveals the true
+ # operator-new -> malloc chain (both real frames are already present).
+ # 2. vDSO leaf: a sample taken inside [vdso] clock_gettime (steady_clock::now())
+ # that perf cannot symbolize; it folds into the resolved clock_gettime caller.
+ # DWARF unwinding resolves (1) but mangles the _start asm entry into ~3 unknown frames
+ # per stack instead, so fp + this fold is the cleanest fully-symbolized result here.
+ # --keep-unknown disables the fold. Each unknown is attributed to its caller, never
+ # merging two distinct resolved regions.
kept = [f for f in frames if f != "[unknown]"]
self.dropped_unknown += len(frames) - len(kept)
frames = kept
diff --git a/scripts/flamegraph.sh b/scripts/flamegraph.sh
index c7bf338..10b4c21 100755
--- a/scripts/flamegraph.sh
+++ b/scripts/flamegraph.sh
@@ -125,7 +125,7 @@ if grep -Eiq 'zero-sized data|No samples|failed to open|Permission denied|Operat
fi
# perf record prints its sample summary as "(N samples)" or, on some versions,
-# "(~N samples)" — and that count is only its own estimate. Accept the optional
+# "(~N samples)", and that count is only its own estimate. Accept the optional
# `~` so the token is not dropped, but keep this value informational; the sample
# gate below uses the authoritative folded total, not this estimate.
SAMPLE_TOKEN="$(sed -nE 's/.*\(~?([0-9][0-9.,]*[KkMm]?) samples\).*/\1/p' "$RECORD_ERR" | head -1)"
@@ -134,7 +134,7 @@ PERF_EST_SAMPLES="$(parse_sample_count_token "$SAMPLE_TOKEN")"
# Fold to collapsed stacks for the text summary and as an SVG precondition. A
# nonzero COLLAPSE_STATUS means the renderer/parser itself failed (a generator
-# regression), which is handled as an unexpected failure below — never masked as
+# regression), which is handled as an unexpected failure below, never masked as
# a perf sampling limitation. FOLDED_SAMPLES is the real sample total carried by
# the folded stacks (sum of trailing counts), the authoritative gate input.
STACK_COUNT=0
@@ -201,7 +201,7 @@ if [[ "$STACK_COUNT" -gt 0 ]]; then
else
# No clean folded stacks. Remove any prior SVG so a constrained rerun cannot
# leave a previous host's flamegraph beside a .txt that says there is no
- # sample report — which could be committed as if the two still matched.
+ # sample report, which could be committed as if the two still matched.
rm -f "$OUT_SVG"
fi
@@ -268,7 +268,7 @@ echo "wrote $OUT_TXT"
[[ "$STACK_COUNT" -gt 0 ]] && echo "wrote $OUT_SVG"
# A renderer/parser failure (perf script succeeded but flamegraph.py errored) is
-# a generator bug, not a perf sampling limitation — fail hard so partial mode
+# a generator bug, not a perf sampling limitation, fail hard so partial mode
# cannot publish a Python/parser regression as a constrained-environment artifact.
if [[ "$SCRIPT_STATUS" -eq 0 && "$COLLAPSE_STATUS" -ne 0 ]]; then
echo "error: flamegraph.py --collapse-only failed (status $COLLAPSE_STATUS); this is a renderer/parser failure, not a perf limitation, and partial mode cannot mask it." >&2
diff --git a/src/engine/contiguous_store.hpp b/src/engine/contiguous_store.hpp
index 352a158..ba3cd4e 100644
--- a/src/engine/contiguous_store.hpp
+++ b/src/engine/contiguous_store.hpp
@@ -17,8 +17,8 @@ namespace qsl::engine {
// M47 contiguous storage: price levels live in a flat array indexed directly by price tick
// (band [kContiguousMinPrice, kContiguousMaxPrice]); per-side occupancy bitmaps answer
// best-price queries; each level's FIFO queue is a contiguous vector with a head cursor and
-// tombstoned cancels. The band constrains *resting* only — out-of-band limit prices still
-// match — and an order whose remainder would rest out of band is refused via can_store_limit.
+// tombstoned cancels. The band constrains *resting* only, out-of-band limit prices still
+// match, and an order whose remainder would rest out of band is refused via can_store_limit.
struct OrderBook::ContiguousStore {
static constexpr Price kMinPrice = OrderBook::kContiguousMinPrice;
static constexpr Price kMaxPrice = OrderBook::kContiguousMaxPrice;
diff --git a/src/engine/order_book.cpp b/src/engine/order_book.cpp
index 0bb866d..6337e06 100644
--- a/src/engine/order_book.cpp
+++ b/src/engine/order_book.cpp
@@ -556,7 +556,7 @@ OrderBook::OrderBook(Storage storage)
contiguous_(storage == Storage::Contiguous ? std::make_unique() : nullptr) {
// The order index is on the hot path: every new/cancel/modify/fill does 1-4 point lookups on
// it. Capping the load factor at 0.25 (vs the default 1.0) keeps probe chains short, which
- // measurably speeds the whole engine on a busy book — a measured ~+18% on the steady-state
+ // measurably speeds the whole engine on a busy book, a measured ~+18% on the steady-state
// profile workload, trading a modest amount of memory (more empty buckets) for fewer
// collisions. This only changes bucket count, never iteration-for-output: index_ is used solely
// for find/insert/erase/size, while snapshots and resting_orders() iterate the ordered
@@ -637,11 +637,11 @@ std::size_t OrderBook::count_matches(const OppMap &opposite, MatchQuery query) c
OrderBook::Level &OrderBook::level_for(Side side, Price price) {
// try_emplace avoids allocating+freeing a map node (std::map::emplace constructs the node
// before checking for a duplicate key) and constructing a throwaway pmr list when the price
- // level already exists — the steady-state common case with a bounded price band. The intrusive
+ // level already exists, the steady-state common case with a bounded price band. The intrusive
// store already uses this; this brings the baseline path in line. Semantics are identical: an
// absent level is inserted empty with the same pmr allocator, and erase_level_if_empty still
// prunes it. The map carries resource_, so pmr scoped-allocator propagation constructs the
- // inserted Level with that same resource — no explicit allocator argument needed (matching the
+ // inserted Level with that same resource, no explicit allocator argument needed (matching the
// intrusive store).
if (side == Side::Buy) {
return bids_.try_emplace(price).first->second;
diff --git a/src/gateway/epoll_server.cpp b/src/gateway/epoll_server.cpp
index 77c706e..a51a673 100644
--- a/src/gateway/epoll_server.cpp
+++ b/src/gateway/epoll_server.cpp
@@ -199,7 +199,7 @@ struct EventLoop {
// Arm/disarm the listener's EPOLLIN. On fd exhaustion we cannot accept (or even close) a new
// connection, and the listener is level-triggered, so leaving it armed would busy-spin epoll_wait.
-// Disarming (MOD to 0 events) stops it being reported until a client fd frees and we re-arm — the
+// Disarming (MOD to 0 events) stops it being reported until a client fd frees and we re-arm, the
// server keeps serving existing clients instead of tearing down (Fatal) or spinning (Retry).
void set_listener_armed(EventLoop &loop, bool armed) {
if (loop.listener_armed == armed) {
diff --git a/src/gateway/tcp_server.cpp b/src/gateway/tcp_server.cpp
index 359b12d..a3e28f8 100644
--- a/src/gateway/tcp_server.cpp
+++ b/src/gateway/tcp_server.cpp
@@ -98,16 +98,20 @@ bool at_capacity(const std::vector &workers, std::size_t cap)
// accept() can fail transiently without the listener being broken: a signal (EINTR) or a peer that
// RSTs between the kernel completing the handshake and accept() returning (ECONNABORTED, and
-// related per-connection errnos). These must not tear the server down — only a genuinely fatal
+// related per-connection errnos). These must not tear the server down, only a genuinely fatal
// listener error (e.g. EBADF/EINVAL) should. Mirrors the epoll path's is_transient_accept_error().
bool transient_accept_errno() noexcept {
- static constexpr int kTransient[] = {EINTR, ECONNABORTED, EPROTO, ENETDOWN,
- EHOSTDOWN, ENONET, EHOSTUNREACH, ENETUNREACH};
+ // The full set of already-pending per-connection network errors Linux accept(2) can surface,
+ // matching the epoll path's is_transient_accept_error() (it previously omitted ENOPROTOOPT and
+ // EOPNOTSUPP, so those two were wrongly fatal in the threaded acceptor).
+ static constexpr int kTransient[] = {EINTR, ECONNABORTED, EPROTO, ENETDOWN,
+ ENOPROTOOPT, EHOSTDOWN, ENONET, EHOSTUNREACH,
+ EOPNOTSUPP, ENETUNREACH};
return std::find(std::begin(kTransient), std::end(kTransient), errno) != std::end(kTransient);
}
// Descriptor/resource exhaustion: out of fds (process or system-wide) or kernel buffers. Like the
-// epoll path, this must not tear the server down — the condition clears as clients disconnect and
+// epoll path, this must not tear the server down, the condition clears as clients disconnect and
// their worker threads close their sockets. It cannot be added to transient_accept_errno() (an
// immediate retry would just re-fail, since the pending connection stays in the backlog), so the
// acceptor backs off briefly between retries instead of busy-spinning.
diff --git a/src/replay/fixture.cpp b/src/replay/fixture.cpp
index 9c34802..6359011 100644
--- a/src/replay/fixture.cpp
+++ b/src/replay/fixture.cpp
@@ -88,7 +88,7 @@ void emit_command_reject(std::ostream &os, const Command &command, core::RejectR
}
// Replay commands through the engine and return the final snapshot. When `drop_cancels` is set
-// this is a deliberately buggy oracle (it ignores cancels) — used to inject a divergence for the
+// this is a deliberately buggy oracle (it ignores cancels), used to inject a divergence for the
// shrinker demonstration (issue #37). Risk limits match the property flow.
engine::EngineSnapshot replay_for_divergence(const std::vector &cmds, bool drop_cancels) {
engine::MatchingEngine eng;
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 2008000..89b1b24 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -119,6 +119,12 @@ add_test(NAME qsl_perfeval_smoke COMMAND qsl-perfeval 5000 --latency)
set_tests_properties(qsl_perfeval_smoke PROPERTIES PASS_REGULAR_EXPRESSION
"perfeval: latency_ns")
+# Malformed order counts must be rejected, not truncated or wrapped.
+add_test(NAME qsl_perfeval_invalid_count_fails
+ COMMAND ${CMAKE_COMMAND} -D "PROGRAM=$" -D "ARGS=123abc" -D
+ "EXPECT_OUTPUT=usage:" -P
+ "${CMAKE_CURRENT_LIST_DIR}/cmake/expect_command_failure.cmake")
+
# Shell unit tests for the shared artifact-publish helper (MAC sanitization +
# trailing-whitespace/blank-line trimming). Portable (sed/awk/mktemp); the script
# locates the repo via BASH_SOURCE, so it runs correctly by absolute path.
diff --git a/tests/concurrency/test_pipeline.cpp b/tests/concurrency/test_pipeline.cpp
index 26d28ea..de0529a 100644
--- a/tests/concurrency/test_pipeline.cpp
+++ b/tests/concurrency/test_pipeline.cpp
@@ -141,7 +141,7 @@ void require_replayed_state(const MatchingEngine &replayed, const PipelineResult
}
// Collects the downstream event stream in order. Touched only by the publisher/log thread during
-// the run, then by the test after join() — no synchronization needed.
+// the run, then by the test after join(), no synchronization needed.
struct CollectingSink final : OutputSink {
std::vector events;
std::uint64_t processed = 0;
diff --git a/tests/shell/test_flamegraph.sh b/tests/shell/test_flamegraph.sh
index bd5a349..718687d 100644
--- a/tests/shell/test_flamegraph.sh
+++ b/tests/shell/test_flamegraph.sh
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
-# Unit tests for scripts/flamegraph.py — the dependency-free stackcollapse + SVG
+# Unit tests for scripts/flamegraph.py, the dependency-free stackcollapse + SVG
# renderer behind `make flamegraph` (issue #32).
#
# The shell driver (scripts/flamegraph.sh) needs Linux `perf`, which CI does not
diff --git a/tests/unit/test_fix_protocol.cpp b/tests/unit/test_fix_protocol.cpp
index 1a6f5d2..56ee3e1 100644
--- a/tests/unit/test_fix_protocol.cpp
+++ b/tests/unit/test_fix_protocol.cpp
@@ -133,7 +133,7 @@ TEST_CASE("FIX side/ord-type/tif codes map both directions", "[fix]") {
TEST_CASE("FIX deterministic fixture pins the wire format", "[fix]") {
const std::string msg = fix::encode(sample_new_order(), /*seq=*/7);
// Built with explicit SOH so the byte sequence (and the pinned BodyLength 50
- // and CheckSum 164) are unambiguous — a "\x01..." literal would greedily
+ // and CheckSum 164) are unambiguous, a "\x01..." literal would greedily
// swallow the following digits into one hex escape.
const std::string S(1, SOH);
const std::string expected = "8=FIX.4.2" + S + "9=50" + S + "35=D" + S + "34=7" + S + "11=1" +
@@ -157,7 +157,7 @@ TEST_CASE("FIX malformed framing rejects deterministically", "[fix]") {
}
TEST_CASE("FIX MsgType must be the first body field", "[fix]") {
- // 8/9/34/35/.../10 — every required NewOrder field is present, but MsgType
+ // 8/9/34/35/.../10, every required NewOrder field is present, but MsgType
// (35) does not immediately follow BodyLength. A first-match scan would still
// decode this; the standard envelope requires 35 first, so it is malformed.
std::string body = field(34, "1") + field(35, "D") + field(11, "1") + field(55, "2") +
diff --git a/tests/unit/test_shrink.cpp b/tests/unit/test_shrink.cpp
index d85e6e7..5dd7fd5 100644
--- a/tests/unit/test_shrink.cpp
+++ b/tests/unit/test_shrink.cpp
@@ -87,7 +87,7 @@ TEST_CASE("shrinker renumbers symbols, dropping unreferenced registrations", "[s
TEST_CASE("renumber leaves idempotent duplicate registrations unchanged", "[shrink]") {
// `reg X; reg X` allocates only symbol id 0 (registration is idempotent), so id 1 is never
// registered and `limit 1 ...` is an UnknownSymbol reject. renumber must not assume the second
- // registration owns id 1 and "fix up" that order into an accepted one — it must bail.
+ // registration owns id 1 and "fix up" that order into an accepted one, it must bail.
using namespace replay;
const std::vector flow = {
RegisterSymbol{"X"},