Skip to content

perf(canvas): use FxHash for NodeId-keyed caches#600

Merged
softmarshmallow merged 1 commit intocanaryfrom
feature/great-wright
Mar 24, 2026
Merged

perf(canvas): use FxHash for NodeId-keyed caches#600
softmarshmallow merged 1 commit intocanaryfrom
feature/great-wright

Conversation

@softmarshmallow
Copy link
Copy Markdown
Member

@softmarshmallow softmarshmallow commented Mar 24, 2026

Summary

  • Replace std::collections::HashMap (SipHash) with a FxHash-style multiplicative hasher for all internal rendering caches keyed by NodeId (u64)
  • Internal keys have no DoS risk, so the cheaper hash (~3ns vs ~25ns) is safe
  • Criterion-verified 5-15% improvement on pan/zoom camera operations

Affected caches

geometry, picture, vector_path, atlas, atlas_set, compositor, painter draw_order, scene node_map

Test plan

  • cargo test -p cg — all 281+ tests pass (no correctness regressions)
  • cargo check -p cg -p grida-canvas-wasm -p grida-dev — all 3 crates compile
  • cargo bench -p cg --bench bench_camera — confirm improvement on zoom/pan benchmarks

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor

    • Internal caching infrastructure updated with specialized data structures across multiple cache layers
    • Rendering pipeline components modified to support new caching implementation
    • Cache initialization and operations refactored throughout the system
  • Tests

    • Test utilities updated to accommodate refactored caching components

Replace SipHash (std HashMap) with a FxHash-style multiplicative hasher
for all internal rendering caches keyed by NodeId (u64). These caches
have trusted-input keys only, so DoS-resistant hashing is unnecessary.
The fast hasher reduces per-lookup cost from ~25ns to ~3ns, yielding
5-15% improvement on pan/zoom operations (Criterion-verified).

Affected caches: geometry, picture, vector_path, atlas, atlas_set,
compositor, painter draw_order, and scene node_map.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 24, 2026

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

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Mar 24, 2026 6:59am
6 Skipped Deployments
Project Deployment Actions Updated (UTC)
code Ignored Ignored Mar 24, 2026 6:59am
legacy Ignored Ignored Mar 24, 2026 6:59am
backgrounds Skipped Skipped Mar 24, 2026 6:59am
blog Skipped Skipped Mar 24, 2026 6:59am
grida Skipped Skipped Mar 24, 2026 6:59am
viewer Skipped Skipped Mar 24, 2026 6:59am

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 24, 2026

Walkthrough

This PR introduces a custom hashing implementation (NodeIdHashMap) optimized for cache structures keyed by trusted integer NodeIds, replacing std::collections::HashMap across 8 modules (atlas, atlas_set, compositor, geometry, picture, vector_path, painter, scene). The custom hasher uses multiply-based mixing for faster hashing on hot-path caches.

Changes

Cohort / File(s) Summary
Fast Hash Implementation
crates/grida-canvas/src/cache/fast_hash.rs
Introduces NodeIdHasher with multiply-based mixing strategy and NodeIdBuildHasher. Provides NodeIdHashMap<K, V> type alias and constructor functions new_node_id_map() and new_node_id_map_with_capacity().
Atlas Cache Updates
crates/grida-canvas/src/cache/atlas/atlas.rs, crates/grida-canvas/src/cache/atlas/atlas_set.rs
Replaces HashMap with NodeIdHashMap for AtlasPage slot/node mappings and AtlasSet node-to-page storage.
Rendering Cache Updates
crates/grida-canvas/src/cache/compositor/cache.rs, crates/grida-canvas/src/cache/geometry.rs, crates/grida-canvas/src/cache/picture.rs, crates/grida-canvas/src/cache/vector_path.rs
Replaces HashMap with NodeIdHashMap for LayerImageCache, GeometryCache, PictureCache, and VectorPathCache internal storage without altering public APIs.
Painter and Scene
crates/grida-canvas/src/painter/painter.rs, crates/grida-canvas/src/runtime/scene.rs, crates/grida-canvas/tests/compositor_effects.rs
Updates Painter::with_promoted_blits to accept NodeIdHashMap references; scene rendering uses NodeIdHashMap for promoted-blits storage.
Module Export
crates/grida-canvas/src/cache/mod.rs
Exports new fast_hash module.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

canvas, performance, cg

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: replacing standard HashMap with FxHash for NodeId-keyed caches, which is the central focus of this performance optimization pull request.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/great-wright

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/grida-canvas/src/runtime/scene.rs (1)

1395-1395: ⚠️ Potential issue | 🟡 Minor

Fix the typos failure before merge.

CI reports a spelling failure at Line 1395 (LODLOAD). Please update the text (or reword to avoid the token) so the pipeline passes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/grida-canvas/src/runtime/scene.rs` at line 1395, Update the comment at
the invalidated zoom image cache location to fix the typos CI failure by
replacing or rewording the token "LOD" to "LOAD" (or another wording that avoids
the flagged token) so the comment no longer triggers the spelling check; locate
the comment near the invalidate zoom image cache logic in scene.rs (around the
comment "// Invalidate zoom image cache on stable frames (always
full-quality).") and adjust the text accordingly.
🧹 Nitpick comments (1)
crates/grida-canvas/src/cache/fast_hash.rs (1)

26-31: Minor observation: write() fallback has weak initial mixing.

The first byte iteration computes 0 * K + b = b, which provides no mixing. This is acceptable since write() is a fallback path—all intended key types (NodeId, SlotId, tuple keys) invoke write_u64() directly via their Hash implementations. No change needed.

💡 Optional: Seed with non-zero value for stronger fallback
 #[derive(Default)]
 pub struct NodeIdHasher {
-    hash: u64,
+    hash: u64 = 0xcbf29ce484222325, // FNV offset basis
 }

Note: Rust stable doesn't support default field values yet (requires nightly), so this would need a manual Default impl if desired.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/grida-canvas/src/cache/fast_hash.rs` around lines 26 - 31, The
fallback write() in fast_hash.rs starts from zero so the first byte becomes
un-mixed (hash = b); modify the implementation of write() (and the hasher's
constructor/Default if present) to seed the internal state with a non-zero
constant or perform an initial mix before the loop so the first byte is combined
(refer to the write() method in this file and the hasher struct/Default impl),
ensuring the change is localized to the fallback path and does not affect
callers that already use write_u64().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@crates/grida-canvas/src/runtime/scene.rs`:
- Line 1395: Update the comment at the invalidated zoom image cache location to
fix the typos CI failure by replacing or rewording the token "LOD" to "LOAD" (or
another wording that avoids the flagged token) so the comment no longer triggers
the spelling check; locate the comment near the invalidate zoom image cache
logic in scene.rs (around the comment "// Invalidate zoom image cache on stable
frames (always full-quality).") and adjust the text accordingly.

---

Nitpick comments:
In `@crates/grida-canvas/src/cache/fast_hash.rs`:
- Around line 26-31: The fallback write() in fast_hash.rs starts from zero so
the first byte becomes un-mixed (hash = b); modify the implementation of write()
(and the hasher's constructor/Default if present) to seed the internal state
with a non-zero constant or perform an initial mix before the loop so the first
byte is combined (refer to the write() method in this file and the hasher
struct/Default impl), ensuring the change is localized to the fallback path and
does not affect callers that already use write_u64().

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a08578fc-7d48-49e0-bf5b-4ca84a25996e

📥 Commits

Reviewing files that changed from the base of the PR and between 792d60c and cc847e1.

📒 Files selected for processing (11)
  • crates/grida-canvas/src/cache/atlas/atlas.rs
  • crates/grida-canvas/src/cache/atlas/atlas_set.rs
  • crates/grida-canvas/src/cache/compositor/cache.rs
  • crates/grida-canvas/src/cache/fast_hash.rs
  • crates/grida-canvas/src/cache/geometry.rs
  • crates/grida-canvas/src/cache/mod.rs
  • crates/grida-canvas/src/cache/picture.rs
  • crates/grida-canvas/src/cache/vector_path.rs
  • crates/grida-canvas/src/painter/painter.rs
  • crates/grida-canvas/src/runtime/scene.rs
  • crates/grida-canvas/tests/compositor_effects.rs

@softmarshmallow softmarshmallow changed the base branch from main to canary March 24, 2026 07:20
@softmarshmallow softmarshmallow merged commit 2e20f3b into canary Mar 24, 2026
13 checks passed
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.

1 participant