Skip to content

perf(hydro_lang): avoid allocations in sim scheduler's ready-list partitioning - #3116

Merged
shadaj merged 1 commit into
mainfrom
sandbox-a11652b2-66d7-467b-9ae1-13e696bd93aa
Aug 11, 2026
Merged

perf(hydro_lang): avoid allocations in sim scheduler's ready-list partitioning#3116
shadaj merged 1 commit into
mainfrom
sandbox-a11652b2-66d7-467b-9ae1-13e696bd93aa

Conversation

@shadaj

@shadaj shadaj commented Aug 3, 2026

Copy link
Copy Markdown
Member

The simulator's LaunchedSim::step spent significant time in Iterator::partition, which allocated two fresh Vecs per call (four call sites, invoked on every scheduler step and for every async DFIR that made progress). Each partition predicate additionally performed a HashMap lookup keyed by (LocationId, Option<u32>), cloning a LocationId (a boxed allocation for Tick variants) for every element checked.

Changes

  • Introduce SimTick and SimObservation structs that store each tick's / observation's hooks (and inline hooks) inline, replacing the anonymous (LocationId, Option<u32>, DfirErased) tuples plus the shared hooks / inline_hooks maps on LaunchedSim. The maps are drained once in start() (where the generated code's serialized-location string keys can be used directly, before any LocationId deserialization), so the scheduler's hot paths no longer do any keyed lookups or LocationId clones. A debug_assert! checks that every hook is claimed by a tick or observation.
  • Replace drain(..).partition(...) with Vec::extract_if, which moves matching elements between the ready/not-ready lists in place without allocating intermediate vectors.
  • Store the tick's parent location (SimTick::parent_location) instead of the full tick location, since the full location was only ever used to extract the parent for readiness matching; this removes an unreachable!() destructure from the hot loop.
  • Factor the shared readiness predicate into hook_can_release and SimTick::can_run / SimObservation::can_run helper methods, so the scheduling conditions are named and documented instead of duplicated inline.
  • Misc cleanup: run_hooks now takes &mut [Box<dyn SimHook>], the empty-default_hooks scratch Vec for observations is gone, and field names are pluralized (possibly_ready_observations, etc.) for consistency.

Verified with cargo check/clippy --all-features (clean) and cargo test --features sim sim:: (36 passed).

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 3, 2026

Copy link
Copy Markdown

Deploying hydro with  Cloudflare Pages  Cloudflare Pages

Latest commit: 7048b97
Status: ✅  Deploy successful!
Preview URL: https://294b3358.hydroflow.pages.dev
Branch Preview URL: https://sandbox-a11652b2-66d7-467b-9.hydroflow.pages.dev

View logs

shadaj added a commit that referenced this pull request Aug 3, 2026
…titioning

The simulator's `LaunchedSim::step` spent significant time in `Iterator::partition`, which allocated two fresh `Vec`s per call (four call sites, invoked on every scheduler step and for every async DFIR that made progress). Each partition predicate additionally performed a `HashMap` lookup keyed by `(LocationId, Option<u32>)`, cloning a `LocationId` (a boxed allocation for `Tick` variants) for every element checked.

## Changes

- **Introduce `SimTick` and `SimObservation` structs** that store each tick's / observation's hooks (and inline hooks) *inline*, replacing the anonymous `(LocationId, Option<u32>, DfirErased)` tuples plus the shared `hooks` / `inline_hooks` maps on `LaunchedSim`. The maps are drained once in `start()` (where the generated code's serialized-location string keys can be used directly, before any `LocationId` deserialization), so the scheduler's hot paths no longer do any keyed lookups or `LocationId` clones. A `debug_assert!` checks that every hook is claimed by a tick or observation.
- **Replace `drain(..).partition(...)` with `Vec::extract_if`**, which moves matching elements between the ready/not-ready lists in place without allocating intermediate vectors.
- **Store the tick's parent location** (`SimTick::parent_location`) instead of the full tick location, since the full location was only ever used to extract the parent for readiness matching; this removes an `unreachable!()` destructure from the hot loop.
- **Factor the shared readiness predicate** into `hook_can_release` and `SimTick::can_run` / `SimObservation::can_run` helper methods, so the scheduling conditions are named and documented instead of duplicated inline.
- Misc cleanup: `run_hooks` now takes `&mut [Box<dyn SimHook>]`, the empty-`default_hooks` scratch `Vec` for observations is gone, and field names are pluralized (`possibly_ready_observations`, etc.) for consistency.

Verified with `cargo check`/`clippy --all-features` (clean) and `cargo test --features sim sim::` (36 passed).

Co-authored-by: Infinity 🤖 <infinity@hydro.run>
PR: #3116
@shadaj
shadaj force-pushed the sandbox-a11652b2-66d7-467b-9ae1-13e696bd93aa branch from d50f627 to 9caf4cb Compare August 3, 2026 22:24
@shadaj
shadaj marked this pull request as ready for review August 3, 2026 22:39
@shadaj
shadaj requested a review from a team August 3, 2026 22:39
shadaj added a commit that referenced this pull request Aug 3, 2026
…titioning

The simulator's `LaunchedSim::step` spent significant time in `Iterator::partition`, which allocated two fresh `Vec`s per call (four call sites, invoked on every scheduler step and for every async DFIR that made progress). Each partition predicate additionally performed a `HashMap` lookup keyed by `(LocationId, Option<u32>)`, cloning a `LocationId` (a boxed allocation for `Tick` variants) for every element checked.

## Changes

- **Introduce `SimTick` and `SimObservation` structs** that store each tick's / observation's hooks (and inline hooks) *inline*, replacing the anonymous `(LocationId, Option<u32>, DfirErased)` tuples plus the shared `hooks` / `inline_hooks` maps on `LaunchedSim`. The maps are drained once in `start()` (where the generated code's serialized-location string keys can be used directly, before any `LocationId` deserialization), so the scheduler's hot paths no longer do any keyed lookups or `LocationId` clones. A `debug_assert!` checks that every hook is claimed by a tick or observation.
- **Replace `drain(..).partition(...)` with `Vec::extract_if`**, which moves matching elements between the ready/not-ready lists in place without allocating intermediate vectors.
- **Store the tick's parent location** (`SimTick::parent_location`) instead of the full tick location, since the full location was only ever used to extract the parent for readiness matching; this removes an `unreachable!()` destructure from the hot loop.
- **Factor the shared readiness predicate** into `hook_can_release` and `SimTick::can_run` / `SimObservation::can_run` helper methods, so the scheduling conditions are named and documented instead of duplicated inline.
- Misc cleanup: `run_hooks` now takes `&mut [Box<dyn SimHook>]`, the empty-`default_hooks` scratch `Vec` for observations is gone, and field names are pluralized (`possibly_ready_observations`, etc.) for consistency.

Verified with `cargo check`/`clippy --all-features` (clean) and `cargo test --features sim sim::` (36 passed).

Co-authored-by: Infinity 🤖 <infinity@hydro.run>
PR: #3116
@shadaj
shadaj force-pushed the sandbox-a11652b2-66d7-467b-9ae1-13e696bd93aa branch from 9caf4cb to 436f0a4 Compare August 3, 2026 22:59
shadaj added a commit that referenced this pull request Aug 3, 2026
…titioning

The simulator's `LaunchedSim::step` spent significant time in `Iterator::partition`, which allocated two fresh `Vec`s per call (four call sites, invoked on every scheduler step and for every async DFIR that made progress). Each partition predicate additionally performed a `HashMap` lookup keyed by `(LocationId, Option<u32>)`, cloning a `LocationId` (a boxed allocation for `Tick` variants) for every element checked.

## Changes

- **Introduce `SimTick` and `SimObservation` structs** that store each tick's / observation's hooks (and inline hooks) *inline*, replacing the anonymous `(LocationId, Option<u32>, DfirErased)` tuples plus the shared `hooks` / `inline_hooks` maps on `LaunchedSim`. The maps are drained once in `start()` (where the generated code's serialized-location string keys can be used directly, before any `LocationId` deserialization), so the scheduler's hot paths no longer do any keyed lookups or `LocationId` clones. A `debug_assert!` checks that every hook is claimed by a tick or observation.
- **Replace `drain(..).partition(...)` with `Vec::extract_if`**, which moves matching elements between the ready/not-ready lists in place without allocating intermediate vectors.
- **Store the tick's parent location** (`SimTick::parent_location`) instead of the full tick location, since the full location was only ever used to extract the parent for readiness matching; this removes an `unreachable!()` destructure from the hot loop.
- **Factor the shared readiness predicate** into `hook_can_release` and `SimTick::can_run` / `SimObservation::can_run` helper methods, so the scheduling conditions are named and documented instead of duplicated inline.
- Misc cleanup: `run_hooks` now takes `&mut [Box<dyn SimHook>]`, the empty-`default_hooks` scratch `Vec` for observations is gone, and field names are pluralized (`possibly_ready_observations`, etc.) for consistency.

Verified with `cargo check`/`clippy --all-features` (clean) and `cargo test --features sim sim::` (36 passed).

Co-authored-by: Infinity 🤖 <infinity@hydro.run>
PR: #3116
@shadaj
shadaj force-pushed the sandbox-a11652b2-66d7-467b-9ae1-13e696bd93aa branch from 436f0a4 to 15acdfb Compare August 3, 2026 23:34
@shadaj
shadaj force-pushed the sandbox-a11652b2-66d7-467b-9ae1-13e696bd93aa branch from 15acdfb to eaa13df Compare August 4, 2026 21:19
@shadaj
shadaj force-pushed the sandbox-a11652b2-66d7-467b-9ae1-13e696bd93aa branch from eaa13df to 4ee8ac7 Compare August 4, 2026 21:19
Comment thread hydro_lang/src/sim/compiled.rs
Comment thread hydro_lang/src/sim/compiled.rs
@shadaj
shadaj force-pushed the sandbox-a11652b2-66d7-467b-9ae1-13e696bd93aa branch 2 times, most recently from 39f7fe3 to d752b7e Compare August 4, 2026 22:50

@MingweiSamuel MingweiSamuel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I guess the hook order matters?

@shadaj
shadaj force-pushed the sandbox-a11652b2-66d7-467b-9ae1-13e696bd93aa branch from d752b7e to 4503a17 Compare August 5, 2026 21:48
Base automatically changed from sandbox-db444e4e-78bd-4261-b714-f97435c63d66 to main August 11, 2026 01:10
…titioning

The simulator's `LaunchedSim::step` spent significant time in `Iterator::partition`, which allocated two fresh `Vec`s per call (four call sites, invoked on every scheduler step and for every async DFIR that made progress). Each partition predicate additionally performed a `HashMap` lookup keyed by `(LocationId, Option<u32>)`, cloning a `LocationId` (a boxed allocation for `Tick` variants) for every element checked.

## Changes

- **Introduce `SimTick` and `SimObservation` structs** that store each tick's / observation's hooks (and inline hooks) *inline*, replacing the anonymous `(LocationId, Option<u32>, DfirErased)` tuples plus the shared `hooks` / `inline_hooks` maps on `LaunchedSim`. The maps are drained once in `start()` (where the generated code's serialized-location string keys can be used directly, before any `LocationId` deserialization), so the scheduler's hot paths no longer do any keyed lookups or `LocationId` clones. A `debug_assert!` checks that every hook is claimed by a tick or observation.
- **Replace `drain(..).partition(...)` with `Vec::extract_if`**, which moves matching elements between the ready/not-ready lists in place without allocating intermediate vectors.
- **Store the tick's parent location** (`SimTick::parent_location`) instead of the full tick location, since the full location was only ever used to extract the parent for readiness matching; this removes an `unreachable!()` destructure from the hot loop.
- **Factor the shared readiness predicate** into `hook_can_release` and `SimTick::can_run` / `SimObservation::can_run` helper methods, so the scheduling conditions are named and documented instead of duplicated inline.
- Misc cleanup: `run_hooks` now takes `&mut [Box<dyn SimHook>]`, the empty-`default_hooks` scratch `Vec` for observations is gone, and field names are pluralized (`possibly_ready_observations`, etc.) for consistency.

Verified with `cargo check`/`clippy --all-features` (clean) and `cargo test --features sim sim::` (36 passed).

Co-authored-by: Infinity 🤖 <infinity@hydro.run>
PR: #3116
@shadaj
shadaj force-pushed the sandbox-a11652b2-66d7-467b-9ae1-13e696bd93aa branch from 4503a17 to 7048b97 Compare August 11, 2026 01:10
@shadaj
shadaj merged commit 8422a96 into main Aug 11, 2026
21 checks passed
@shadaj
shadaj deleted the sandbox-a11652b2-66d7-467b-9ae1-13e696bd93aa branch August 11, 2026 15:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants