Skip to content

perf(core): drop the per-state-read heap allocations in the witness path - #198

Draft
flyq wants to merge 1 commit into
liquan/perf/blocking-work-off-the-runtimefrom
liquan/perf/witness-state-read-allocations
Draft

perf(core): drop the per-state-read heap allocations in the witness path#198
flyq wants to merge 1 commit into
liquan/perf/blocking-work-off-the-runtimefrom
liquan/perf/witness-state-read-allocations

Conversation

@flyq

@flyq flyq commented Sep 3, 2026

Copy link
Copy Markdown
Member

Summary

PR 5/6 of the #170 split, stacked on #197. Removes the heap allocations on the per-state-read path of block replay. Listed in vincent's review of #170 as "A hot-path optimization" worth its own line.

This lookup runs once per unique account and storage slot touched by a block, so it is the hottest thing in WitnessDatabase.

  • WitnessDatabase reads go through salt's find(), which hands back the SaltValue (an inline array) instead of plain_value()'s freshly heap-allocated copy of the value bytes. Callers decode from .value() in place.
  • Key encodings are stack-allocated: PlainKey::account_key_bytes (a borrow of the address) and PlainKey::storage_key_bytes (a FixedBytes<52>) replace PlainKey::{Account,Storage}(..).encode()'s Vec. encode() itself delegates to them, so there is one definition of each encoding. bucket_id_for_account / bucket_id_for_slot use them too — that is one allocation per call removed on another per-read path.
  • LightWitness::metadata stops cloning the SaltValue it only reads.

Net: two heap allocations per witnessed state read, plus one per bucket_id_for_* call.

Correctness

The interesting claim is that find() returns the same bytes plain_value() did. That is pinned by the mainnet fixture replays (validate_block_mainnet_fixtures, validate_block_deriving_updates_mainnet_fixtures, replayed_gas_used_matches_the_mainnet_header): a read that returned different bytes changes execution and fails the post-state-root check on every fixture.

stack_key_encodings_match_encode pins the narrower claim — that the allocation-free encoders agree with encode() — against a future edit that stops delegating and lets the two drift into looking up different keys.

Testing

cargo fmt --all --check, cargo clippy --workspace --all-targets --all-features (0 warnings), cargo sort --check, full workspace suite 482 passed / 0 failed, cargo test -p stateless-core --no-default-features --lib --no-run clean.

`WitnessDatabase` reads go through salt's `find()` with in-place decoding and
stack-allocated key encodings; `LightWitness::metadata` stops cloning the
`SaltValue` it only reads.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CHVpMMX9N69sUNbuKgpBVY
@mega-maxwell

mega-maxwell Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Claude review status

Living comment — rewritten in place. The review workflow keeps this single comment up to date instead of posting a new one each round, so it always describes the latest reviewed commit and the earlier text is intentionally gone. No reply is needed here; reply to a finding in its own review thread, and answer an open question in a reply on this PR. The next review round reconciles your answer.

✅ Review clean

Last reviewed: head 50741dc3 · updated 2026-09-03T11:48:24+00:00

New this round: 0 finding(s), 0 question(s) · Resolved this round: 0 · Open questions: 0

@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.77419% with 1 line in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (liquan/perf/blocking-work-off-the-runtime@432e550). Learn more about missing BASE report.

Files with missing lines Patch % Lines
crates/stateless-core/src/light_witness.rs 0.0% 1 Missing ⚠️

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 50741dc320

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines 88 to +90
EphemeralSaltState::new(self.witness)
.plain_value(plain_key)
.find(plain_key)
.map(|found| found.map(|(_, salt_value)| salt_value))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the indexed witness lookup path

When W is LightWitnessExecutor in the debug-trace server, the old plain_value path can use StateReader::plain_value_fast, which is backed by the prebuilt direct_lookup_tbl in light_witness.rs; invoking EphemeralSaltState::find directly bypasses that index and probes the SALT table for every account and slot read. On trace replays with many state reads, this discards the executor's purpose-built fast path and may outweigh the saved value allocation. Resolve the SaltKey through plain_value_fast and read the SaltValue directly, retaining the find fallback needed for non-existent keys.

Useful? React with 👍 / 👎.

@flyq
flyq marked this pull request as draft September 4, 2026 00:28
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