uses atomics for read-only accounts cache entry index (#32518)

Using atomics for entry indices allows load function to use self.cache.get
instead of get_mut which reduces lock contention on the respective
dash-map shard.
This commit is contained in:
behzad nouri 2023-08-07 17:47:09 +00:00 committed by GitHub
parent c2dec254c8
commit b9a2030537
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 29 additions and 9 deletions

View File

@ -10,7 +10,7 @@ use {
pubkey::Pubkey,
},
std::sync::{
atomic::{AtomicU64, AtomicUsize, Ordering},
atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering},
Mutex,
},
};
@ -23,7 +23,7 @@ type ReadOnlyCacheKey = (Pubkey, Slot);
#[derive(Debug)]
struct ReadOnlyAccountCacheEntry {
account: AccountSharedData,
index: Index, // Index of the entry in the eviction queue.
index: AtomicU32, // Index of the entry in the eviction queue.
}
#[derive(Debug)]
@ -77,7 +77,7 @@ impl ReadOnlyAccountsCache {
pub(crate) fn load(&self, pubkey: Pubkey, slot: Slot) -> Option<AccountSharedData> {
let (account, load_us) = measure_us!({
let key = (pubkey, slot);
let Some(mut entry) = self.cache.get_mut(&key) else {
let Some(entry) = self.cache.get(&key) else {
self.misses.fetch_add(1, Ordering::Relaxed);
return None;
};
@ -86,8 +86,8 @@ impl ReadOnlyAccountsCache {
// so that another thread cannot write to the same key.
{
let mut queue = self.queue.lock().unwrap();
queue.remove(entry.index);
entry.index = queue.insert_last(key);
queue.remove(entry.index());
entry.set_index(queue.insert_last(key));
}
let account = entry.account.clone();
drop(entry);
@ -113,7 +113,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();
@ -122,8 +122,8 @@ impl ReadOnlyAccountsCache {
entry.account = account;
// Move the entry to the end of the queue.
let mut queue = self.queue.lock().unwrap();
queue.remove(entry.index);
entry.index = queue.insert_last(key);
queue.remove(entry.index());
entry.set_index(queue.insert_last(key));
}
};
// Evict entries from the front of the queue.
@ -143,7 +143,7 @@ impl ReadOnlyAccountsCache {
// self.queue should be modified only after removing the entry from the
// cache, so that this is still safe if another thread writes to the
// same key.
self.queue.lock().unwrap().remove(entry.index);
self.queue.lock().unwrap().remove(entry.index());
let account_size = self.account_size(&entry.account);
self.data_size.fetch_sub(account_size, Ordering::Relaxed);
Some(entry.account)
@ -167,6 +167,26 @@ impl ReadOnlyAccountsCache {
}
}
impl ReadOnlyAccountCacheEntry {
fn new(account: AccountSharedData, index: Index) -> Self {
let index = unsafe { std::mem::transmute::<Index, u32>(index) };
let index = AtomicU32::new(index);
Self { account, index }
}
#[inline]
fn index(&self) -> Index {
let index = self.index.load(Ordering::Relaxed);
unsafe { std::mem::transmute::<u32, Index>(index) }
}
#[inline]
fn set_index(&self, index: Index) {
let index = unsafe { std::mem::transmute::<Index, u32>(index) };
self.index.store(index, Ordering::Relaxed);
}
}
#[cfg(test)]
mod tests {
use {