Skip to content

Commit 3ae411e

Browse files
committed
Revert "fix(engine): flatten storage cache (#18880)"
This reverts commit 169a1fb.
1 parent bab9dee commit 3ae411e

File tree

1 file changed

+89
-54
lines changed

1 file changed

+89
-54
lines changed

crates/engine/tree/src/tree/cached_state.rs

Lines changed: 89 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
//! Execution cache implementation for block processing.
2-
use alloy_primitives::{
3-
map::{DefaultHashBuilder, HashSet},
4-
Address, StorageKey, StorageValue, B256,
5-
};
2+
use alloy_primitives::{Address, StorageKey, StorageValue, B256};
63
use metrics::Gauge;
74
use mini_moka::sync::CacheBuilder;
85
use reth_errors::ProviderResult;
@@ -17,6 +14,7 @@ use reth_trie::{
1714
updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof,
1815
MultiProofTargets, StorageMultiProof, StorageProof, TrieInput,
1916
};
17+
use revm_primitives::map::DefaultHashBuilder;
2018
use std::{sync::Arc, time::Duration};
2119
use tracing::{debug_span, instrument, trace};
2220

@@ -302,70 +300,65 @@ pub(crate) struct ExecutionCache {
302300
/// Cache for contract bytecode, keyed by code hash.
303301
code_cache: Cache<B256, Option<Bytecode>>,
304302

305-
/// Flattened storage cache: composite key of (`Address`, `StorageKey`) maps directly to
306-
/// values.
307-
storage_cache: Cache<(Address, StorageKey), Option<StorageValue>>,
303+
/// Per-account storage cache: outer cache keyed by Address, inner cache tracks that account’s
304+
/// storage slots.
305+
storage_cache: Cache<Address, AccountStorageCache>,
308306

309307
/// Cache for basic account information (nonce, balance, code hash).
310308
account_cache: Cache<Address, Option<Account>>,
311309
}
312310

313311
impl ExecutionCache {
314-
/// Get storage value from flattened cache.
312+
/// Get storage value from hierarchical cache.
315313
///
316314
/// Returns a `SlotStatus` indicating whether:
317-
/// - `NotCached`: The storage slot is not in the cache
318-
/// - `Empty`: The slot exists in the cache but is empty
315+
/// - `NotCached`: The account's storage cache doesn't exist
316+
/// - `Empty`: The slot exists in the account's cache but is empty
319317
/// - `Value`: The slot exists and has a specific value
320318
pub(crate) fn get_storage(&self, address: &Address, key: &StorageKey) -> SlotStatus {
321-
match self.storage_cache.get(&(*address, *key)) {
319+
match self.storage_cache.get(address) {
322320
None => SlotStatus::NotCached,
323-
Some(None) => SlotStatus::Empty,
324-
Some(Some(value)) => SlotStatus::Value(value),
321+
Some(account_cache) => account_cache.get_storage(key),
325322
}
326323
}
327324

328-
/// Insert storage value into flattened cache
325+
/// Insert storage value into hierarchical cache
329326
pub(crate) fn insert_storage(
330327
&self,
331328
address: Address,
332329
key: StorageKey,
333330
value: Option<StorageValue>,
334331
) {
335-
self.storage_cache.insert((address, key), value);
332+
self.insert_storage_bulk(address, [(key, value)]);
336333
}
337334

338-
/// Insert multiple storage values into flattened cache for a single account
335+
/// Insert multiple storage values into hierarchical cache for a single account
339336
///
340-
/// This method inserts multiple storage values for the same address directly
341-
/// into the flattened cache.
337+
/// This method is optimized for inserting multiple storage values for the same address
338+
/// by doing the account cache lookup only once instead of for each key-value pair.
342339
pub(crate) fn insert_storage_bulk<I>(&self, address: Address, storage_entries: I)
343340
where
344341
I: IntoIterator<Item = (StorageKey, Option<StorageValue>)>,
345342
{
343+
let account_cache = self.storage_cache.get(&address).unwrap_or_else(|| {
344+
let account_cache = AccountStorageCache::default();
345+
self.storage_cache.insert(address, account_cache.clone());
346+
account_cache
347+
});
348+
346349
for (key, value) in storage_entries {
347-
self.storage_cache.insert((address, key), value);
350+
account_cache.insert_storage(key, value);
348351
}
349352
}
350353

354+
/// Invalidate storage for specific account
355+
pub(crate) fn invalidate_account_storage(&self, address: &Address) {
356+
self.storage_cache.invalidate(address);
357+
}
358+
351359
/// Returns the total number of storage slots cached across all accounts
352360
pub(crate) fn total_storage_slots(&self) -> usize {
353-
self.storage_cache.entry_count() as usize
354-
}
355-
356-
/// Invalidates the storage for all addresses in the set
357-
#[instrument(level = "debug", target = "engine::caching", skip_all, fields(accounts = addresses.len()))]
358-
pub(crate) fn invalidate_storages(&self, addresses: HashSet<&Address>) {
359-
// NOTE: this must collect because the invalidate function should not be called while we
360-
// hold an iter for it
361-
let storage_entries = self
362-
.storage_cache
363-
.iter()
364-
.filter_map(|entry| addresses.contains(&entry.key().0).then_some(*entry.key()))
365-
.collect::<Vec<_>>();
366-
for key in storage_entries {
367-
self.storage_cache.invalidate(&key)
368-
}
361+
self.storage_cache.iter().map(|addr| addr.len()).sum()
369362
}
370363

371364
/// Inserts the post-execution state changes into the cache.
@@ -405,7 +398,6 @@ impl ExecutionCache {
405398
state_updates.state.values().map(|account| account.storage.len()).sum::<usize>()
406399
)
407400
.entered();
408-
let mut invalidated_accounts = HashSet::default();
409401
for (addr, account) in &state_updates.state {
410402
// If the account was not modified, as in not changed and not destroyed, then we have
411403
// nothing to do w.r.t. this particular account and can move on
@@ -418,7 +410,7 @@ impl ExecutionCache {
418410
// Invalidate the account cache entry if destroyed
419411
self.account_cache.invalidate(addr);
420412

421-
invalidated_accounts.insert(addr);
413+
self.invalidate_account_storage(addr);
422414
continue
423415
}
424416

@@ -445,9 +437,6 @@ impl ExecutionCache {
445437
self.account_cache.insert(*addr, Some(Account::from(account_info)));
446438
}
447439

448-
// invalidate storage for all destroyed accounts
449-
self.invalidate_storages(invalidated_accounts);
450-
451440
Ok(())
452441
}
453442
}
@@ -476,11 +465,11 @@ impl ExecutionCacheBuilder {
476465
const TIME_TO_IDLE: Duration = Duration::from_secs(3600); // 1 hour
477466

478467
let storage_cache = CacheBuilder::new(self.storage_cache_entries)
479-
.weigher(|_key: &(Address, StorageKey), _value: &Option<StorageValue>| -> u32 {
480-
// Size of composite key (Address + StorageKey) + Option<StorageValue>
481-
// Address: 20 bytes, StorageKey: 32 bytes, Option<StorageValue>: 33 bytes
482-
// Plus some overhead for the hash map entry
483-
120_u32
468+
.weigher(|_key: &Address, value: &AccountStorageCache| -> u32 {
469+
// values based on results from measure_storage_cache_overhead test
470+
let base_weight = 39_000;
471+
let slots_weight = value.len() * 218;
472+
(base_weight + slots_weight) as u32
484473
})
485474
.max_capacity(storage_cache_size)
486475
.time_to_live(EXPIRY_TIME)
@@ -603,6 +592,56 @@ impl SavedCache {
603592
}
604593
}
605594

595+
/// Cache for an individual account's storage slots.
596+
///
597+
/// This represents the second level of the hierarchical storage cache.
598+
/// Each account gets its own `AccountStorageCache` to store accessed storage slots.
599+
#[derive(Debug, Clone)]
600+
pub(crate) struct AccountStorageCache {
601+
/// Map of storage keys to their cached values.
602+
slots: Cache<StorageKey, Option<StorageValue>>,
603+
}
604+
605+
impl AccountStorageCache {
606+
/// Create a new [`AccountStorageCache`]
607+
pub(crate) fn new(max_slots: u64) -> Self {
608+
Self {
609+
slots: CacheBuilder::new(max_slots).build_with_hasher(DefaultHashBuilder::default()),
610+
}
611+
}
612+
613+
/// Get a storage value from this account's cache.
614+
/// - `NotCached`: The slot is not in the cache
615+
/// - `Empty`: The slot is empty
616+
/// - `Value`: The slot has a specific value
617+
pub(crate) fn get_storage(&self, key: &StorageKey) -> SlotStatus {
618+
match self.slots.get(key) {
619+
None => SlotStatus::NotCached,
620+
Some(None) => SlotStatus::Empty,
621+
Some(Some(value)) => SlotStatus::Value(value),
622+
}
623+
}
624+
625+
/// Insert a storage value
626+
pub(crate) fn insert_storage(&self, key: StorageKey, value: Option<StorageValue>) {
627+
self.slots.insert(key, value);
628+
}
629+
630+
/// Returns the number of slots in the cache
631+
pub(crate) fn len(&self) -> usize {
632+
self.slots.entry_count() as usize
633+
}
634+
}
635+
636+
impl Default for AccountStorageCache {
637+
fn default() -> Self {
638+
// With weigher and max_capacity in place, this number represents
639+
// the maximum number of entries that can be stored, not the actual
640+
// memory usage which is controlled by storage cache's max_capacity.
641+
Self::new(1_000_000)
642+
}
643+
}
644+
606645
#[cfg(test)]
607646
mod tests {
608647
use super::*;
@@ -677,36 +716,32 @@ mod tests {
677716

678717
#[test]
679718
fn measure_storage_cache_overhead() {
680-
let (base_overhead, cache) =
681-
measure_allocation(|| ExecutionCacheBuilder::default().build_caches(1000));
682-
println!("Base ExecutionCache overhead: {base_overhead} bytes");
719+
let (base_overhead, cache) = measure_allocation(|| AccountStorageCache::new(1000));
720+
println!("Base AccountStorageCache overhead: {base_overhead} bytes");
683721
let mut rng = rand::rng();
684722

685-
let address = Address::random();
686723
let key = StorageKey::random();
687724
let value = StorageValue::from(rng.random::<u128>());
688725
let (first_slot, _) = measure_allocation(|| {
689-
cache.insert_storage(address, key, Some(value));
726+
cache.insert_storage(key, Some(value));
690727
});
691728
println!("First slot insertion overhead: {first_slot} bytes");
692729

693730
const TOTAL_SLOTS: usize = 10_000;
694731
let (test_slots, _) = measure_allocation(|| {
695732
for _ in 0..TOTAL_SLOTS {
696-
let addr = Address::random();
697733
let key = StorageKey::random();
698734
let value = StorageValue::from(rng.random::<u128>());
699-
cache.insert_storage(addr, key, Some(value));
735+
cache.insert_storage(key, Some(value));
700736
}
701737
});
702738
println!("Average overhead over {} slots: {} bytes", TOTAL_SLOTS, test_slots / TOTAL_SLOTS);
703739

704740
println!("\nTheoretical sizes:");
705-
println!("Address size: {} bytes", size_of::<Address>());
706741
println!("StorageKey size: {} bytes", size_of::<StorageKey>());
707742
println!("StorageValue size: {} bytes", size_of::<StorageValue>());
708743
println!("Option<StorageValue> size: {} bytes", size_of::<Option<StorageValue>>());
709-
println!("(Address, StorageKey) size: {} bytes", size_of::<(Address, StorageKey)>());
744+
println!("Option<B256> size: {} bytes", size_of::<Option<B256>>());
710745
}
711746

712747
#[test]

0 commit comments

Comments
 (0)