-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2a50a99
commit f8cd0c6
Showing
8 changed files
with
343 additions
and
100 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
use std::collections::hash_map::Entry; | ||
use std::collections::BTreeMap; | ||
use std::hash::Hash; | ||
use std::mem; | ||
use std::ops::DerefMut; | ||
use std::sync::atomic::AtomicU64; | ||
use std::sync::atomic::Ordering; | ||
|
||
use parking_lot::RwLock; | ||
use parking_lot::RwLockWriteGuard; | ||
use rustc_hash::FxBuildHasher; | ||
use rustc_hash::FxHashMap; | ||
use smallvec::SmallVec; | ||
|
||
use super::AccountWithSlots; | ||
use crate::eth::primitives::Account; | ||
use crate::eth::primitives::Address; | ||
use crate::eth::primitives::ExecutionChanges; | ||
use crate::eth::primitives::Slot; | ||
use crate::eth::primitives::SlotIndex; | ||
use crate::eth::primitives::SlotValue; | ||
|
||
type EntryId = u64; | ||
|
||
pub struct StorageCache { | ||
slot_cache: Cache<(Address, SlotIndex), SlotValue>, | ||
account_cache: Cache<Address, Account>, | ||
} | ||
|
||
impl Default for StorageCache { | ||
fn default() -> Self { | ||
Self { | ||
slot_cache: Cache::with_target_capacity(100_000), | ||
account_cache: Cache::with_target_capacity(20_000), | ||
} | ||
} | ||
} | ||
|
||
impl StorageCache { | ||
pub fn cache_slot(&self, address: Address, slot: Slot) { | ||
self.slot_cache.put((address, slot.index), slot.value); | ||
} | ||
|
||
pub fn cache_account(&self, account: Account) { | ||
self.account_cache.put(account.address, account); | ||
} | ||
|
||
pub fn cache_account_and_slots_from_execution(&self, changes: &ExecutionChanges) { | ||
let mut slot_batch = SmallVec::<[_; 16]>::new(); | ||
let mut account_batch = SmallVec::<[_; 8]>::new(); | ||
|
||
for change in changes.values() { | ||
// cache slots | ||
for &slot in change.slots.values().flat_map(|slot| slot.take_ref()) { | ||
slot_batch.push(((change.address, slot.index), slot.value)); | ||
} | ||
|
||
// cache account | ||
let mut account = AccountWithSlots::new(change.address); | ||
if let Some(nonce) = change.nonce.take_ref() { | ||
account.info.nonce = *nonce; | ||
} | ||
if let Some(balance) = change.balance.take_ref() { | ||
account.info.balance = *balance; | ||
} | ||
if let Some(Some(bytecode)) = change.bytecode.take_ref() { | ||
account.info.bytecode = Some(bytecode.clone()); | ||
} | ||
account_batch.push((change.address, account.info)); | ||
} | ||
|
||
self.slot_cache.put_batch(slot_batch); | ||
self.account_cache.put_batch(account_batch); | ||
} | ||
|
||
pub fn get_slot(&self, address: Address, index: SlotIndex) -> Option<Slot> { | ||
self.slot_cache.get(&(address, index)).map(|value| Slot { value, index }) | ||
} | ||
|
||
pub fn get_account(&self, address: Address) -> Option<Account> { | ||
self.account_cache.get(&address) | ||
} | ||
} | ||
|
||
type Id = u64; | ||
|
||
struct Cache<K, V> { | ||
target_capacity: usize, | ||
id_counter: AtomicU64, | ||
kv: RwLock<FxHashMap<K, (Id, V)>>, | ||
eviction_queue: RwLock<BTreeMap<Id, K>>, | ||
} | ||
|
||
impl<K, V> Cache<K, V> | ||
where | ||
K: Hash + Eq + Clone, | ||
V: Clone, | ||
{ | ||
pub fn with_target_capacity(target_capacity: usize) -> Self { | ||
assert!(target_capacity > 8192); | ||
Self { | ||
target_capacity, | ||
id_counter: AtomicU64::new(0), | ||
kv: RwLock::new(FxHashMap::with_capacity_and_hasher(target_capacity + 8192 * 2, FxBuildHasher)), | ||
eviction_queue: RwLock::new(BTreeMap::new()), | ||
} | ||
} | ||
|
||
pub fn get(&self, key: &K) -> Option<V> { | ||
self.kv.read().get(key).map(|pair| pair.1.clone()) | ||
} | ||
|
||
pub fn put(&self, key: K, value: V) { | ||
self.put_batch([(key, value)]); | ||
} | ||
|
||
pub fn put_batch<I>(&self, iter: I) | ||
where | ||
I: IntoIterator<Item = (K, V)>, | ||
{ | ||
let mut should_evict = false; | ||
let mut ids_with_keys_to_add = SmallVec::<[(Id, K); 16]>::new(); | ||
let mut ids_with_keys_to_remove = SmallVec::<[(Id, K); 16]>::new(); | ||
|
||
{ | ||
let mut kv_lock = self.kv.write(); | ||
|
||
for (key, value) in iter { | ||
let (id, eviction_trigger) = self.next_id(); | ||
should_evict |= eviction_trigger; | ||
ids_with_keys_to_add.push((id, key.clone())); | ||
|
||
let previous = kv_lock.insert(key.clone(), (id, value)); | ||
if let Some((previous_id, _)) = previous { | ||
ids_with_keys_to_remove.push((previous_id, key)); | ||
} | ||
} | ||
} | ||
|
||
let mut queue_lock = self.eviction_queue.write(); | ||
for (id, key) in ids_with_keys_to_remove { | ||
queue_lock.remove(&id); | ||
} | ||
for (id, key) in ids_with_keys_to_add { | ||
queue_lock.insert(id, key); | ||
} | ||
if should_evict { | ||
self.run_eviction(queue_lock); | ||
} | ||
} | ||
|
||
#[inline] | ||
/// Calculates the next entry id and tells if should run eviction. | ||
fn next_id(&self) -> (EntryId, bool) { | ||
let id = self.id_counter.fetch_add(1, Ordering::Relaxed); | ||
// plan eviction every 8192 entries added to this cache | ||
// 8192 == 2^13, which makes this an stupidly fast bitmask check | ||
let should_evict = id % 8192 == 0; | ||
(id, should_evict) | ||
} | ||
|
||
#[inline] | ||
fn run_eviction(&self, mut queue_lock: RwLockWriteGuard<BTreeMap<Id, K>>) { | ||
if queue_lock.len() <= self.target_capacity { | ||
return; // nothing to do | ||
} | ||
|
||
// Underflow Safety: | ||
// - queue_lock.len() > self.target_capacity | ||
let amount_to_evict = queue_lock.len() - self.target_capacity; | ||
|
||
// Note: linear search might be a bottleneck, but `.lower_bound()` is unstable | ||
// Unwrap Safety: | ||
// - amount_to_evict < queue_lock.len() | ||
// - because self.target_capacity > 0 | ||
let (&id_to_split_at, _) = queue_lock.iter().nth(amount_to_evict).unwrap(); | ||
|
||
let right_side = queue_lock.split_off(&id_to_split_at); | ||
let elements_to_evict = mem::replace(queue_lock.deref_mut(), right_side); | ||
|
||
drop(queue_lock); | ||
let mut kv_lock = self.kv.write(); | ||
|
||
for (id, key) in elements_to_evict { | ||
if let Entry::Occupied(occupied) = kv_lock.entry(key) { | ||
if occupied.get().0 == id { | ||
occupied.remove(); | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.