diff --git a/runtime/src/accounts_db.rs b/runtime/src/accounts_db.rs index 11512958b0210e..2ca1370d4fa12b 100644 --- a/runtime/src/accounts_db.rs +++ b/runtime/src/accounts_db.rs @@ -2364,6 +2364,8 @@ impl AccountsDb { ) -> Self { let num_threads = get_thread_count(); const MAX_READ_ONLY_CACHE_DATA_SIZE: usize = 400_000_000; // 400M bytes + // read only cache does not update lru on read of an entry unless it has been at least this many ms since the last lru update + const READ_ONLY_CACHE_MS_TO_SKIP_LRU_UPDATE: u32 = 100; let mut temp_accounts_hash_cache_path = None; let accounts_hash_cache_path = accounts_hash_cache_path.unwrap_or_else(|| { @@ -2395,7 +2397,10 @@ impl AccountsDb { storage: AccountStorage::default(), accounts_cache: AccountsCache::default(), sender_bg_hasher: None, - read_only_accounts_cache: ReadOnlyAccountsCache::new(MAX_READ_ONLY_CACHE_DATA_SIZE), + read_only_accounts_cache: ReadOnlyAccountsCache::new( + MAX_READ_ONLY_CACHE_DATA_SIZE, + READ_ONLY_CACHE_MS_TO_SKIP_LRU_UPDATE, + ), recycle_stores: RwLock::new(RecycleStores::default()), uncleaned_pubkeys: DashMap::new(), next_id: AtomicAppendVecId::new(0), diff --git a/runtime/src/read_only_accounts_cache.rs b/runtime/src/read_only_accounts_cache.rs index 23a0af60f4731e..10e77ddb714a8e 100644 --- a/runtime/src/read_only_accounts_cache.rs +++ b/runtime/src/read_only_accounts_cache.rs @@ -8,6 +8,7 @@ use { account::{AccountSharedData, ReadableAccount}, clock::Slot, pubkey::Pubkey, + timing::timestamp, }, std::sync::{ atomic::{AtomicU64, AtomicUsize, Ordering}, @@ -23,20 +24,45 @@ type ReadOnlyCacheKey = (Pubkey, Slot); #[derive(Debug)] struct ReadOnlyAccountCacheEntry { account: AccountSharedData, - index: Index, // Index of the entry in the eviction queue. + /// Index of the entry in the eviction queue. + index: Index, + /// lower bits of last timestamp when eviction queue was updated + last_time: u32, +} + +impl ReadOnlyAccountCacheEntry { + fn new(account: AccountSharedData, index: Index) -> Self { + Self { + account, + index, + last_time: Self::timestamp(), + } + } + /// lower bits of current timestamp. We don't need higher bits and u32 fits with Index u32 + fn timestamp() -> u32 { + (timestamp() % (u32::MAX as u64 + 1)) as u32 + } + /// ms since `last_time` timestamp + fn ms_since_last_time(&self) -> u32 { + Self::timestamp().wrapping_sub(self.last_time) + } } #[derive(Debug)] pub(crate) struct ReadOnlyAccountsCache { cache: DashMap, - // When an item is first entered into the cache, it is added to the end of - // the queue. Also each time an entry is looked up from the cache it is - // moved to the end of the queue. As a result, items in the queue are - // always sorted in the order that they have last been accessed. When doing - // LRU eviction, cache entries are evicted from the front of the queue. + /// When an item is first entered into the cache, it is added to the end of + /// the queue. Also each time an entry is looked up from the cache it is + /// moved to the end of the queue. As a result, items in the queue are + /// always sorted in the order that they have last been accessed. When doing + /// LRU eviction, cache entries are evicted from the front of the queue. queue: Mutex>, max_data_size: usize, data_size: AtomicUsize, + // read only cache does not update lru on read of an entry unless it has been at least this many ms since the last lru update + ms_to_skip_lru_update: u32, + + /// stats hits: AtomicU64, misses: AtomicU64, evicts: AtomicU64, @@ -44,12 +70,13 @@ pub(crate) struct ReadOnlyAccountsCache { } impl ReadOnlyAccountsCache { - pub(crate) fn new(max_data_size: usize) -> Self { + pub(crate) fn new(max_data_size: usize, ms_to_skip_lru_update: u32) -> Self { Self { max_data_size, cache: DashMap::default(), queue: Mutex::>::default(), data_size: AtomicUsize::default(), + ms_to_skip_lru_update, hits: AtomicU64::default(), misses: AtomicU64::default(), evicts: AtomicU64::default(), @@ -84,10 +111,13 @@ impl ReadOnlyAccountsCache { // Move the entry to the end of the queue. // self.queue is modified while holding a reference to the cache entry; // so that another thread cannot write to the same key. - { + // If we updated the eviction queue within this much time, then leave it where it is. We're likely to hit it again. + let update_lru = entry.ms_since_last_time() >= self.ms_to_skip_lru_update; + if update_lru { let mut queue = self.queue.lock().unwrap(); queue.remove(entry.index); entry.index = queue.insert_last(key); + entry.last_time = ReadOnlyAccountCacheEntry::timestamp(); } let account = entry.account.clone(); drop(entry); @@ -113,7 +143,7 @@ impl ReadOnlyAccountsCache { // Insert the entry at the end of the queue. let mut queue = self.queue.lock().unwrap(); let index = queue.insert_last(key); - entry.insert(ReadOnlyAccountCacheEntry { account, index }); + entry.insert(ReadOnlyAccountCacheEntry::new(account, index)); } Entry::Occupied(mut entry) => { let entry = entry.get_mut(); @@ -192,7 +222,8 @@ mod tests { let per_account_size = CACHE_ENTRY_SIZE; let data_size = 100; let max = data_size + per_account_size; - let cache = ReadOnlyAccountsCache::new(max); + let cache = + ReadOnlyAccountsCache::new(max, READ_ONLY_CACHE_MS_TO_SKIP_LRU_UPDATE_FOR_TESTS); let slot = 0; assert!(cache.load(Pubkey::default(), slot).is_none()); assert_eq!(0, cache.cache_len()); @@ -227,7 +258,8 @@ mod tests { // can store 2 items, 3rd item kicks oldest item out let max = (data_size + per_account_size) * 2; - let cache = ReadOnlyAccountsCache::new(max); + let cache = + ReadOnlyAccountsCache::new(max, READ_ONLY_CACHE_MS_TO_SKIP_LRU_UPDATE_FOR_TESTS); cache.store(key1, slot, account1.clone()); assert_eq!(100 + per_account_size, cache.data_size()); assert!(accounts_equal(&cache.load(key1, slot).unwrap(), &account1)); @@ -250,13 +282,19 @@ mod tests { assert_eq!(2, cache.cache_len()); } + /// tests like to deterministically update lru always + const READ_ONLY_CACHE_MS_TO_SKIP_LRU_UPDATE_FOR_TESTS: u32 = 0; + #[test] fn test_read_only_accounts_cache_random() { const SEED: [u8; 32] = [0xdb; 32]; const DATA_SIZE: usize = 19; const MAX_CACHE_SIZE: usize = 17 * (CACHE_ENTRY_SIZE + DATA_SIZE); let mut rng = ChaChaRng::from_seed(SEED); - let cache = ReadOnlyAccountsCache::new(MAX_CACHE_SIZE); + let cache = ReadOnlyAccountsCache::new( + MAX_CACHE_SIZE, + READ_ONLY_CACHE_MS_TO_SKIP_LRU_UPDATE_FOR_TESTS, + ); let slots: Vec = repeat_with(|| rng.gen_range(0, 1000)).take(5).collect(); let pubkeys: Vec = repeat_with(|| { let mut arr = [0u8; 32];