Skip to content

Conversation

@indietyp
Copy link
Member

🌟 What is the purpose of this PR?

Replace imbl with rpds for persistent data structures in the MIR interpreter, and optimize interpreter performance.

The interpreter is now only 20-40% slower instead of the previous 100%+.

🔍 What does this change?

  • Replaces imbl dependency with rpds for persistent data structures (List and Dict)
  • Adds inlining annotations to hot code paths in the interpreter
  • Optimizes SwitchTargets::target() with a fast path for common cases
  • Adds cold path annotations to error handling code
  • Optimizes value comparison in the interpreter
  • Adds a new benchmark for the interpreter (fibonacci recursive)
  • Improves parameter passing in the interpreter to avoid unnecessary copies
  • Optimizes local variable storage in the interpreter

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • does not modify any publishable blocks or libraries, or modifications do not need publishing

📜 Does this require a change to the docs?

The changes in this PR:

  • are internal and do not require a docs change

🕸️ Does this require a change to the Turbo Graph?

The changes in this PR:

  • do not affect the execution graph

🛡 What tests cover this?

  • New benchmark for the interpreter (fibonacci recursive)
  • Existing tests continue to pass

❓ How to test this?

  1. Run the new benchmark to verify performance improvements
  2. Run existing tests to ensure functionality is preserved

@cursor
Copy link

cursor bot commented Jan 10, 2026

PR Summary

Focuses on MIR interpreter performance and benchmarking.

  • Replace imbl with rpds for persistent collections in interpreter values (List, Dict); update Cargo.toml/Cargo.lock and deps in hashql-mir
  • Optimize hot paths: add #[inline], cold_path, and a small-size fast path in SwitchTargets::target(); simplify comparisons and arithmetic in value ops
  • Reduce allocations/copies and tighten APIs: Locals now stores Value (not Option<Value>), requires ExactSizeIterator for args, and many interpreter APIs take references instead of by-value (e.g., operands, places, subscript_mut)
  • Minor core API additions: IdVec::reserve; mark IdSlice::lookup/lookup_mut as inline
  • Add new benches/interpret.rs (recursive Fibonacci) and factor shared benchmarking harness into benches/common/run.rs; update transform benches to reuse it
  • Enable additional nightly features used in optimizations (e.g., cold_path, likely_unlikely, exhaustive_patterns)

Written by Cursor Bugbot for commit 585d675. This will update automatically on new commits. Configure here.

@github-actions github-actions bot added area/deps Relates to third-party dependencies (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > backend Owned by the @backend team labels Jan 10, 2026
Copy link
Member Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@vercel
Copy link

vercel bot commented Jan 15, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Review Updated (UTC)
ds-theme Ignored Ignored Preview Jan 17, 2026 5:56pm
hashdotdesign Ignored Ignored Preview Jan 17, 2026 5:56pm

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

TimDiekmann
TimDiekmann previously approved these changes Jan 17, 2026
@indietyp indietyp force-pushed the bm/be-273-hashql-interpreter-benchmarks branch from a6ba41d to 585d675 Compare January 17, 2026 17:55
@indietyp indietyp force-pushed the bm/be-272-hashql-implement-and-inside-the-mir branch from 2e2c7ef to a6d5b81 Compare January 17, 2026 17:55
@github-actions github-actions bot dismissed TimDiekmann’s stale review January 17, 2026 17:56

Your organization requires reapproval when changes are made, so Graphite has dismissed approvals. See the output of git range-diff at https://github.com/hashintel/hash/actions/runs/21098529794

@github-actions
Copy link
Contributor

Benchmark results

@rust/hash-graph-benches – Integrations

policy_resolution_large

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 2002 $$27.7 \mathrm{ms} \pm 260 \mathrm{μs}\left({\color{red}6.30 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$3.18 \mathrm{ms} \pm 15.0 \mathrm{μs}\left({\color{gray}-0.631 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 1001 $$12.0 \mathrm{ms} \pm 75.2 \mathrm{μs}\left({\color{gray}-0.827 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 3314 $$42.2 \mathrm{ms} \pm 343 \mathrm{μs}\left({\color{gray}0.709 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$14.0 \mathrm{ms} \pm 74.7 \mathrm{μs}\left({\color{gray}-1.415 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 1526 $$23.2 \mathrm{ms} \pm 140 \mathrm{μs}\left({\color{gray}-0.025 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 2078 $$30.3 \mathrm{ms} \pm 158 \mathrm{μs}\left({\color{lightgreen}-29.937 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.56 \mathrm{ms} \pm 18.9 \mathrm{μs}\left({\color{lightgreen}-82.574 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 1033 $$13.7 \mathrm{ms} \pm 84.9 \mathrm{μs}\left({\color{lightgreen}-52.269 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_medium

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 102 $$3.59 \mathrm{ms} \pm 17.2 \mathrm{μs}\left({\color{gray}0.439 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.79 \mathrm{ms} \pm 9.78 \mathrm{μs}\left({\color{gray}-0.304 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 51 $$3.17 \mathrm{ms} \pm 15.5 \mathrm{μs}\left({\color{gray}0.937 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 269 $$4.92 \mathrm{ms} \pm 22.1 \mathrm{μs}\left({\color{gray}-0.296 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$3.35 \mathrm{ms} \pm 15.6 \mathrm{μs}\left({\color{gray}0.149 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 107 $$3.92 \mathrm{ms} \pm 21.9 \mathrm{μs}\left({\color{gray}0.960 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 133 $$4.17 \mathrm{ms} \pm 21.8 \mathrm{μs}\left({\color{gray}-0.621 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.24 \mathrm{ms} \pm 13.9 \mathrm{μs}\left({\color{gray}0.501 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 63 $$3.84 \mathrm{ms} \pm 36.4 \mathrm{μs}\left({\color{gray}-0.063 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_none

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 2 $$2.54 \mathrm{ms} \pm 12.2 \mathrm{μs}\left({\color{red}7.00 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.46 \mathrm{ms} \pm 8.27 \mathrm{μs}\left({\color{gray}4.76 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 1 $$2.60 \mathrm{ms} \pm 18.5 \mathrm{μs}\left({\color{red}6.69 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 8 $$2.74 \mathrm{ms} \pm 11.5 \mathrm{μs}\left({\color{red}5.15 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$2.63 \mathrm{ms} \pm 9.50 \mathrm{μs}\left({\color{gray}4.30 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 3 $$2.83 \mathrm{ms} \pm 10.4 \mathrm{μs}\left({\color{gray}4.99 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_small

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 52 $$2.81 \mathrm{ms} \pm 11.4 \mathrm{μs}\left({\color{gray}3.29 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.53 \mathrm{ms} \pm 9.14 \mathrm{μs}\left({\color{red}5.70 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 25 $$2.69 \mathrm{ms} \pm 10.2 \mathrm{μs}\left({\color{red}5.39 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 94 $$3.17 \mathrm{ms} \pm 14.9 \mathrm{μs}\left({\color{gray}3.97 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$2.77 \mathrm{ms} \pm 10.6 \mathrm{μs}\left({\color{red}5.79 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 26 $$2.98 \mathrm{ms} \pm 13.2 \mathrm{μs}\left({\color{gray}4.44 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 66 $$3.05 \mathrm{ms} \pm 11.6 \mathrm{μs}\left({\color{gray}2.31 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$2.76 \mathrm{ms} \pm 15.1 \mathrm{μs}\left({\color{red}6.55 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 29 $$2.95 \mathrm{ms} \pm 15.0 \mathrm{μs}\left({\color{gray}4.29 \mathrm{\%}}\right) $$ Flame Graph

read_scaling_complete

Function Value Mean Flame graphs
entity_by_id;one_depth 1 entities $$38.7 \mathrm{ms} \pm 136 \mathrm{μs}\left({\color{gray}0.618 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 10 entities $$76.0 \mathrm{ms} \pm 348 \mathrm{μs}\left({\color{gray}0.603 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 25 entities $$43.2 \mathrm{ms} \pm 141 \mathrm{μs}\left({\color{gray}2.49 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 5 entities $$45.1 \mathrm{ms} \pm 151 \mathrm{μs}\left({\color{gray}0.328 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 50 entities $$53.6 \mathrm{ms} \pm 297 \mathrm{μs}\left({\color{gray}2.05 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 1 entities $$40.2 \mathrm{ms} \pm 161 \mathrm{μs}\left({\color{gray}0.968 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 10 entities $$414 \mathrm{ms} \pm 735 \mathrm{μs}\left({\color{gray}0.121 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 25 entities $$95.1 \mathrm{ms} \pm 356 \mathrm{μs}\left({\color{gray}0.154 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 5 entities $$84.5 \mathrm{ms} \pm 315 \mathrm{μs}\left({\color{gray}0.314 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 50 entities $$259 \mathrm{ms} \pm 635 \mathrm{μs}\left({\color{lightgreen}-6.520 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 1 entities $$14.6 \mathrm{ms} \pm 57.9 \mathrm{μs}\left({\color{gray}-1.056 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 10 entities $$14.8 \mathrm{ms} \pm 68.4 \mathrm{μs}\left({\color{gray}1.82 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 25 entities $$15.1 \mathrm{ms} \pm 78.6 \mathrm{μs}\left({\color{gray}0.544 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 5 entities $$14.7 \mathrm{ms} \pm 75.8 \mathrm{μs}\left({\color{gray}1.82 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 50 entities $$17.5 \mathrm{ms} \pm 103 \mathrm{μs}\left({\color{gray}0.856 \mathrm{\%}}\right) $$ Flame Graph

read_scaling_linkless

Function Value Mean Flame graphs
entity_by_id 1 entities $$14.5 \mathrm{ms} \pm 72.0 \mathrm{μs}\left({\color{gray}1.65 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10 entities $$14.6 \mathrm{ms} \pm 67.3 \mathrm{μs}\left({\color{gray}-0.059 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 100 entities $$14.6 \mathrm{ms} \pm 84.2 \mathrm{μs}\left({\color{gray}1.33 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 1000 entities $$15.2 \mathrm{ms} \pm 103 \mathrm{μs}\left({\color{gray}1.17 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10000 entities $$22.5 \mathrm{ms} \pm 150 \mathrm{μs}\left({\color{gray}-0.372 \mathrm{\%}}\right) $$ Flame Graph

representative_read_entity

Function Value Mean Flame graphs
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/block/v/1 $$29.8 \mathrm{ms} \pm 296 \mathrm{μs}\left({\color{gray}-0.474 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/book/v/1 $$30.4 \mathrm{ms} \pm 242 \mathrm{μs}\left({\color{gray}-0.035 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/building/v/1 $$30.0 \mathrm{ms} \pm 320 \mathrm{μs}\left({\color{gray}1.80 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/organization/v/1 $$30.5 \mathrm{ms} \pm 291 \mathrm{μs}\left({\color{gray}-2.564 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/page/v/2 $$29.8 \mathrm{ms} \pm 309 \mathrm{μs}\left({\color{gray}-1.034 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/person/v/1 $$29.8 \mathrm{ms} \pm 252 \mathrm{μs}\left({\color{gray}0.176 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/playlist/v/1 $$29.8 \mathrm{ms} \pm 251 \mathrm{μs}\left({\color{gray}0.320 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/song/v/1 $$30.2 \mathrm{ms} \pm 312 \mathrm{μs}\left({\color{gray}-2.406 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/uk-address/v/1 $$30.6 \mathrm{ms} \pm 336 \mathrm{μs}\left({\color{gray}-0.579 \mathrm{\%}}\right) $$ Flame Graph

representative_read_entity_type

Function Value Mean Flame graphs
get_entity_type_by_id Account ID: bf5a9ef5-dc3b-43cf-a291-6210c0321eba $$7.96 \mathrm{ms} \pm 33.8 \mathrm{μs}\left({\color{gray}0.157 \mathrm{\%}}\right) $$ Flame Graph

representative_read_multiple_entities

Function Value Mean Flame graphs
entity_by_property traversal_paths=0 0 $$47.1 \mathrm{ms} \pm 213 \mathrm{μs}\left({\color{gray}-0.421 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=255 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true $$93.5 \mathrm{ms} \pm 366 \mathrm{μs}\left({\color{gray}0.161 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false $$52.3 \mathrm{ms} \pm 277 \mathrm{μs}\left({\color{gray}-0.915 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true $$60.2 \mathrm{ms} \pm 283 \mathrm{μs}\left({\color{gray}0.203 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true $$68.0 \mathrm{ms} \pm 253 \mathrm{μs}\left({\color{gray}-0.634 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true $$74.5 \mathrm{ms} \pm 426 \mathrm{μs}\left({\color{gray}-0.145 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=0 0 $$49.8 \mathrm{ms} \pm 191 \mathrm{μs}\left({\color{gray}0.663 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=255 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true $$76.6 \mathrm{ms} \pm 357 \mathrm{μs}\left({\color{gray}0.723 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false $$56.5 \mathrm{ms} \pm 220 \mathrm{μs}\left({\color{gray}0.247 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true $$64.5 \mathrm{ms} \pm 373 \mathrm{μs}\left({\color{gray}0.192 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true $$66.2 \mathrm{ms} \pm 265 \mathrm{μs}\left({\color{gray}0.549 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true $$65.9 \mathrm{ms} \pm 280 \mathrm{μs}\left({\color{gray}-0.767 \mathrm{\%}}\right) $$

scenarios

Function Value Mean Flame graphs
full_test query-limited $$135 \mathrm{ms} \pm 456 \mathrm{μs}\left({\color{gray}-4.867 \mathrm{\%}}\right) $$ Flame Graph
full_test query-unlimited $$131 \mathrm{ms} \pm 404 \mathrm{μs}\left({\color{lightgreen}-5.260 \mathrm{\%}}\right) $$ Flame Graph
linked_queries query-limited $$39.3 \mathrm{ms} \pm 207 \mathrm{μs}\left({\color{lightgreen}-61.691 \mathrm{\%}}\right) $$ Flame Graph
linked_queries query-unlimited $$572 \mathrm{ms} \pm 805 \mathrm{μs}\left({\color{gray}-3.063 \mathrm{\%}}\right) $$ Flame Graph

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/deps Relates to third-party dependencies (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > backend Owned by the @backend team

Development

Successfully merging this pull request may close these issues.

3 participants