Skip to content

perf(codegen): cascade FxHashMap to copy_prop/alg_simplify/unroll/vectorize (+48.8%) - #236

Closed
TheTom wants to merge 1 commit into
0xClandestine:devfrom
TheTom:tom/perf/codegen-remap-fxhash-cascade
Closed

perf(codegen): cascade FxHashMap to copy_prop/alg_simplify/unroll/vectorize (+48.8%)#236
TheTom wants to merge 1 commit into
0xClandestine:devfrom
TheTom:tom/perf/codegen-remap-fxhash-cascade

Conversation

@TheTom

@TheTom TheTom commented May 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

Cascade FxHashMap adoption to 4 codegen passes whose ValueId remap maps are pure get-only (no ordered iteration). Adds a sister walker remap_value_ids_fx so callers can swap the map type without touching the canonical remap_value_ids. Also drops one drifted local clone of the walker (algebraic_simplify::remap_values_in_op) that was missing 3 Op variants the canonical version had picked up.

Continuation of @0xClandestine's PR #38 dead_store_elim hot-path swap (BTree→FxHash, -29.8% measured there) into the rest of the pipeline where the same pattern applies.

Measured (release mode, copy_prop hot path)

perf_copy_prop_btreemap_vs_fxhash — 2000-op block, 256-entry replacement map, dead-vid collect, 500 iters:

BTreeMap (old)  : 21.93 ns/op
FxHashMap (new) : 11.22 ns/op
speedup         : 1.95x (+48.8%)

Pinned by regression assertion fx_ns_per <= bt_ns_per * 1.05 so future hardware/lib changes that would erode the win surface as a CI fail.

cargo test -p metaltile-codegen --release perf_copy_prop_btreemap_vs_fxhash -- --ignored --nocapture

Cascade

File Change
remap.rs Add remap_value_ids_fx(op, &FxHashMap<ValueId, ValueId>) sister to existing remap_value_ids.
copy_prop.rs vid_replacements + dead_vids FxHashMap/FxHashSet, pre-sized to block.ops.len(). Microbench added.
algebraic_simplify.rs vid_replacements + vid_to_op_pos + dead_vids + vid_to_pos swap. Deletes local remap_values_in_op (had drifted from canonical walker).
unroll.rs vid_map FxHashMap, pre-sized body_n + 1.
vectorize.rs result_remap FxHashMap @ phase 3 rebuild.

kernel_inline.rs intentionally NOT touched — overlaps with your in-flight PR #58 scope.

Why FxHashMap is correct here

  • Keys are dense u32-backed ValueIds, already hashed by SSA construction.
  • No iteration-order dependency anywhere these maps are consumed (map → for_each_value_id_mut per-Op, order irrelevant).
  • FxHasher collapses to a single wrapping_mul — beats BTreeMap's log2(n) probe + cache misses on n=2000.

Test plan

  • cargo test -p metaltile-codegen --lib — 161/161 pass
  • cargo clippy --workspace --all-targets --all-features -- -D warnings — clean
  • cargo fmt --all --check — clean
  • cargo test -p metaltile-codegen --release perf_copy_prop_btreemap_vs_fxhash -- --ignored --nocapture — +48.8%

@github-actions github-actions Bot added the performance Performance improvement label May 30, 2026
@TheTom
TheTom force-pushed the tom/perf/codegen-remap-fxhash-cascade branch from 9bb2d06 to c012916 Compare May 30, 2026 05:07
…ll/vectorize

Adds `remap_value_ids_fx` sister to `remap_value_ids` so passes whose
replacement maps are pure get-only (no iteration-order dependency) can
build `FxHashMap<ValueId, ValueId>` and skip BTree's log(n) per-probe
cost on hot loops. Cascades to 4 callers + drops 1 drifted clone.

Microbench (copy_prop hot path, 2000 ops + 256 replacements + dead-vid
collect, 500 iters, release):

  BTreeMap (old)  : 21.93 ns/op
  FxHashMap (new) : 11.22 ns/op
  speedup         : 1.95x (+48.8%)

Pinned by regression assertion `fx_ns_per <= bt_ns_per * 1.05`.

### Cascade

- `copy_prop.rs` — vid_replacements + dead_vids FxHashMap/FxHashSet,
  pre-sized to `block.ops.len()`.
- `algebraic_simplify.rs` — vid_replacements + vid_to_op_pos + dead_vids
  + vid_to_pos params. Deletes local `remap_values_in_op` drifted
  clone of the canonical walker (was missing 3 Op variants added to
  `for_each_value_id_mut` since the clone was made).
- `unroll.rs` — vid_map FxHashMap, pre-sized `body_n + 1`.
- `vectorize.rs` — result_remap FxHashMap at line 300.

`kernel_inline.rs` intentionally NOT touched: overlaps with @0xClandestine
in-flight PR 0xClandestine#58 scope.

### Why FxHashMap here

- Keys are dense `u32`-backed `ValueId`s already hashed by Rust SSA.
- No ordered iteration (map is consumed by `for_each_value_id_mut`
  per-Op, order irrelevant).
- `FxHasher` collapses to a single `wrapping_mul` — beats `BTreeMap`'s
  `log2(n)` probe + cache misses on n=2000.

Verified with 161/161 codegen tests passing, clippy clean, fmt clean.
@TheTom
TheTom force-pushed the tom/perf/codegen-remap-fxhash-cascade branch from c012916 to 3da27e4 Compare May 30, 2026 12:18
TheTom added a commit to TheTom/metaltile that referenced this pull request May 30, 2026
…d/fusion/register_estimate (+57.6%)

Continues 0xClandestine#235 (licm) and 0xClandestine#236 (copy_prop/alg_simplify/unroll/vectorize)
into the four remaining codegen passes that still used BTree containers
keyed by `ValueId` (or `usize` position) for pure get/insert/contains:

- **value_sink.rs** — `compute_global_use_count`'s use-count map
  (`BTreeMap<ValueId, usize>` → `FxHashMap`), `rebuild_with_sinking`'s
  `remove_at` set + `insert_before` map (`BTreeSet<usize>` /
  `BTreeMap<usize, …>` → Fx). Pre-sized from the outer plans/op counts.
- **const_fold.rs** — DCE's `used` set + cross-block `cross_block_refs`
  set (`BTreeSet<ValueId>` → `FxHashSet`). `collect_uses` helper
  signature updated.
- **fusion.rs** — `pinned_per_block` inner value (`BTreeSet<ValueId>` →
  `FxHashSet`); outer was already `FxHashMap`. `fuse_block` only does
  `.contains()` on the inner set, no iteration order needed.
- **register_estimate.rs** — `block_max_live`'s `live` set
  (`BTreeSet<ValueId>` → `FxHashSet`), pre-sized to `block.ops.len()`.

Microbench (M5 Max, release, 2000-op block, 10K iters side-by-side):

  BTreeSet  (old): 40110.2 ns/call
  FxHashSet (new): 17000.2 ns/call
  → speedup       : 2.36× (+57.6%)

Pinned by `assert!(fx_ns_per * 1.05 <= bt_ns_per)`.

`type_check.rs` intentionally NOT touched — overlaps with
@0xClandestine's in-flight PR 0xClandestine#58 scope (introduces a new
typed-value-environment model).

Verified with 161/161 codegen tests passing, clippy clean, fmt clean.
0xClandestine pushed a commit that referenced this pull request May 30, 2026
…egister_estimate (+57.6%) (#239)

## Summary

Continues #235 (licm) and #236 (copy_prop/alg_simplify/unroll/vectorize)
into the four remaining codegen passes that still used BTree containers
keyed by \`ValueId\` (or \`usize\` position) for pure
get/insert/contains:

| File | Change |
|------|--------|
| \`value_sink.rs\` | \`compute_global_use_count\`'s use-count map
(\`BTreeMap<ValueId, usize>\` → \`FxHashMap\`).
\`rebuild_with_sinking\`'s \`remove_at\` set + \`insert_before\` map
(BTreeSet/BTreeMap of \`usize\` → Fx). All pre-sized from outer plans/op
counts. |
| \`const_fold.rs\` | DCE's \`used\` set + cross-block
\`cross_block_refs\` (\`BTreeSet<ValueId>\` → \`FxHashSet\`).
\`collect_uses\` helper signature updated. |
| \`fusion.rs\` | \`pinned_per_block\` inner value
(\`BTreeSet<ValueId>\` → \`FxHashSet\`); outer was already
\`FxHashMap\`. \`fuse_block\` only does \`.contains()\` on the inner set
— no iteration order. |
| \`register_estimate.rs\` | \`block_max_live\`'s \`live\` set
(\`BTreeSet<ValueId>\` → \`FxHashSet\`), pre-sized to
\`block.ops.len()\`. |

\`type_check.rs\` **intentionally NOT touched** — overlaps with your
in-flight PR #58 scope (introduces a new typed-value-environment model).

## Measured (M5 Max, release, 2000-op block, 10K iters side-by-side)

\`perf_block_max_live_btree_vs_fxhash\` — BTreeSet impl kept inline in
the test module:

\`\`\`
=== block_max_live: 2000-op live-set walk × 10000 iters ===
  BTreeSet  (old): 40110.2 ns/call
  FxHashSet (new): 17000.2 ns/call
  → speedup       : 2.36× (+57.6%)
\`\`\`

Pinned by \`assert!(fx_ns_per * 1.05 <= bt_ns_per)\`.

\`cargo test -p metaltile-codegen --release
perf_block_max_live_btree_vs_fxhash -- --ignored --nocapture\`

## Test plan
- [x] \`cargo test -p metaltile-codegen --lib\` — 161/161 pass
- [x] \`cargo clippy --workspace --all-targets --all-features -- -D
warnings\` — clean
- [x] \`cargo fmt --all --check\` — clean
- [x] \`cargo test -p metaltile-codegen --release
perf_block_max_live_btree_vs_fxhash -- --ignored --nocapture\` — +57.6%
@TheTom

TheTom commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator Author

Closing as superseded — current dev already cascades FxHash through all the passes this PR targets: copy_prop, algebraic_simplify, unroll, vectorize, and remap all use rustc_hash on dev. A rebase conflicts precisely because dev applied the same swap (landed via the codegen work in #243). No net change remains.

Nothing lost; the optimization is live on dev.

@TheTom TheTom closed this Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance Performance improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant