Skip to content

Commit 3c2150c

Browse files
committed
accounts-db: Benchmark cache evictions
The already existing `concurrent_{read,scan}_write` benchmarks are not sufficient for benchmarking the eviction and evaluating what kind of eviction policy performs the best, because they don't fill up the cache, so eviction never happens. Add a new benchmark, which starts measuring the concurrent reads and writes on a full cache.
1 parent 6c6c26e commit 3c2150c

File tree

5 files changed

+157
-8
lines changed

5 files changed

+157
-8
lines changed

accounts-db/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ harness = false
103103
name = "bench_hashing"
104104
harness = false
105105

106+
[[bench]]
107+
name = "bench_read_only_accounts_cache"
108+
harness = false
109+
106110
[[bench]]
107111
name = "bench_serde"
108112
harness = false
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
#![feature(test)]
2+
3+
extern crate test;
4+
5+
use {
6+
criterion::{criterion_group, criterion_main, BenchmarkId, Criterion},
7+
rand::{rngs::SmallRng, seq::SliceRandom, SeedableRng},
8+
solana_accounts_db::{
9+
accounts_db::AccountsDb, read_only_accounts_cache::ReadOnlyAccountsCache,
10+
},
11+
solana_sdk::account::{Account, ReadableAccount},
12+
std::{
13+
sync::{
14+
atomic::{AtomicBool, Ordering},
15+
Arc,
16+
},
17+
thread::Builder,
18+
},
19+
};
20+
21+
const NUM_READERS_WRITERS: &[usize] = &[
22+
8,
23+
16,
24+
// These parameters are likely to freeze your computer, if it has less than
25+
// 32 cores.
26+
// 32, 64, 128
27+
];
28+
29+
/// Benchmarks the read-only cache eviction mechanism. It does so by performing
30+
/// multithreaded reads and writes on a full cache. Each write triggers
31+
/// eviction. Background reads add more contention.
32+
fn bench_cache_eviction(c: &mut Criterion) {
33+
/// Number of 1 MiB accounts needed to initially fill the cache.
34+
const NUM_ACCOUNTS_INIT: usize = 410;
35+
/// Number of accounts used in the benchmarked writes (per thread).
36+
const NUM_NEW_ACCOUNTS_PER_THREAD: usize = 512;
37+
38+
let mut group = c.benchmark_group("cache_eviction");
39+
40+
for num_readers_writers in NUM_READERS_WRITERS {
41+
let cache = Arc::new(ReadOnlyAccountsCache::new(
42+
AccountsDb::DEFAULT_MAX_READ_ONLY_CACHE_DATA_SIZE_LO,
43+
AccountsDb::DEFAULT_MAX_READ_ONLY_CACHE_DATA_SIZE_HI,
44+
AccountsDb::READ_ONLY_CACHE_MS_TO_SKIP_LRU_UPDATE,
45+
));
46+
47+
// Prepare accounts for the cache fillup.
48+
let pubkeys: Vec<_> = std::iter::repeat_with(solana_sdk::pubkey::new_rand)
49+
.take(NUM_ACCOUNTS_INIT)
50+
.collect();
51+
let accounts_data = std::iter::repeat(
52+
Account {
53+
lamports: 1,
54+
// 1 MiB
55+
data: vec![1; 1024 * 1024],
56+
..Default::default()
57+
}
58+
.to_account_shared_data(),
59+
)
60+
.take(NUM_ACCOUNTS_INIT);
61+
let storable_accounts = pubkeys.iter().zip(accounts_data);
62+
63+
// Fill up the cache.
64+
let slot = 0;
65+
for (pubkey, account) in storable_accounts {
66+
cache.store(*pubkey, slot, account);
67+
}
68+
69+
// Prepare accounts for the new writes.
70+
let new_storable_accounts = std::iter::repeat_with(solana_sdk::pubkey::new_rand)
71+
.map(|pubkey| {
72+
(
73+
pubkey,
74+
Account {
75+
lamports: 1,
76+
// 1 MiB
77+
data: vec![1; 1024 * 1024],
78+
..Default::default()
79+
}
80+
.to_account_shared_data(),
81+
)
82+
})
83+
.take(NUM_NEW_ACCOUNTS_PER_THREAD);
84+
85+
// Spawn the reader threads in the background.
86+
let stop_reader = Arc::new(AtomicBool::new(false));
87+
let reader_handles = (0..*num_readers_writers).map(|i| {
88+
let cache = cache.clone();
89+
let pubkeys = pubkeys.clone();
90+
let stop_reader = stop_reader.clone();
91+
Builder::new()
92+
.name(format!("reader{i:02}"))
93+
.spawn({
94+
move || {
95+
// Continuously read random accounts.
96+
let mut rng = SmallRng::seed_from_u64(i as u64);
97+
while !stop_reader.load(Ordering::Relaxed) {
98+
let pubkey = pubkeys.choose(&mut rng).unwrap();
99+
test::black_box(cache.load(*pubkey, slot));
100+
}
101+
}
102+
})
103+
.unwrap()
104+
});
105+
106+
// Benchmark reads and writes on a full cache, trigerring eviction on each
107+
// write.
108+
let slot = 1;
109+
group.sample_size(10);
110+
group.bench_function(
111+
BenchmarkId::new("cache_eviction", num_readers_writers),
112+
|b| {
113+
b.iter(|| {
114+
// Perform the writes.
115+
let writer_handles = (0..*num_readers_writers).map(|i| {
116+
let cache = cache.clone();
117+
let new_storable_accounts = new_storable_accounts.clone();
118+
Builder::new()
119+
.name(format!("writer{i:02}"))
120+
.spawn({
121+
move || {
122+
for (pubkey, account) in new_storable_accounts {
123+
cache.store(pubkey, slot, account);
124+
}
125+
}
126+
})
127+
.unwrap()
128+
});
129+
130+
for writer_handle in writer_handles {
131+
writer_handle.join().unwrap();
132+
}
133+
})
134+
},
135+
);
136+
137+
stop_reader.store(true, Ordering::Relaxed);
138+
for reader_handle in reader_handles {
139+
reader_handle.join().unwrap();
140+
}
141+
}
142+
}
143+
144+
criterion_group!(benches, bench_cache_eviction);
145+
criterion_main!(benches);

accounts-db/src/accounts_db.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1889,12 +1889,12 @@ impl AccountsDb {
18891889
pub const DEFAULT_ACCOUNTS_HASH_CACHE_DIR: &'static str = "accounts_hash_cache";
18901890

18911891
// 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
1892-
const READ_ONLY_CACHE_MS_TO_SKIP_LRU_UPDATE: u32 = 100;
1892+
pub const READ_ONLY_CACHE_MS_TO_SKIP_LRU_UPDATE: u32 = 100;
18931893

18941894
// The default high and low watermark sizes for the accounts read cache.
18951895
// If the cache size exceeds MAX_SIZE_HI, it'll evict entries until the size is <= MAX_SIZE_LO.
1896-
const DEFAULT_MAX_READ_ONLY_CACHE_DATA_SIZE_LO: usize = 400 * 1024 * 1024;
1897-
const DEFAULT_MAX_READ_ONLY_CACHE_DATA_SIZE_HI: usize = 410 * 1024 * 1024;
1896+
pub const DEFAULT_MAX_READ_ONLY_CACHE_DATA_SIZE_LO: usize = 400 * 1024 * 1024;
1897+
pub const DEFAULT_MAX_READ_ONLY_CACHE_DATA_SIZE_HI: usize = 410 * 1024 * 1024;
18981898

18991899
pub fn default_for_tests() -> Self {
19001900
Self::new_single_for_tests()

accounts-db/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ mod file_io;
3232
pub mod hardened_unpack;
3333
pub mod partitioned_rewards;
3434
pub mod pubkey_bins;
35-
mod read_only_accounts_cache;
35+
pub mod read_only_accounts_cache;
3636
mod rolling_bit_field;
3737
pub mod secondary_index;
3838
pub mod shared_buffer_reader;

accounts-db/src/read_only_accounts_cache.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ struct AtomicReadOnlyCacheStats {
6666
}
6767

6868
#[derive(Debug)]
69-
pub(crate) struct ReadOnlyAccountsCache {
69+
pub struct ReadOnlyAccountsCache {
7070
cache: Arc<DashMap<ReadOnlyCacheKey, ReadOnlyAccountCacheEntry>>,
7171
/// When an item is first entered into the cache, it is added to the end of
7272
/// the queue. Also each time an entry is looked up from the cache it is
@@ -93,7 +93,7 @@ pub(crate) struct ReadOnlyAccountsCache {
9393
}
9494

9595
impl ReadOnlyAccountsCache {
96-
pub(crate) fn new(
96+
pub fn new(
9797
max_data_size_lo: usize,
9898
max_data_size_hi: usize,
9999
ms_to_skip_lru_update: u32,
@@ -137,7 +137,7 @@ impl ReadOnlyAccountsCache {
137137
}
138138
}
139139

140-
pub(crate) fn load(&self, pubkey: Pubkey, slot: Slot) -> Option<AccountSharedData> {
140+
pub fn load(&self, pubkey: Pubkey, slot: Slot) -> Option<AccountSharedData> {
141141
let (account, load_us) = measure_us!({
142142
let mut found = None;
143143
if let Some(entry) = self.cache.get(&pubkey) {
@@ -175,7 +175,7 @@ impl ReadOnlyAccountsCache {
175175
CACHE_ENTRY_SIZE + account.data().len()
176176
}
177177

178-
pub(crate) fn store(&self, pubkey: Pubkey, slot: Slot, account: AccountSharedData) {
178+
pub fn store(&self, pubkey: Pubkey, slot: Slot, account: AccountSharedData) {
179179
let measure_store = Measure::start("");
180180
self.highest_slot_stored.fetch_max(slot, Ordering::Release);
181181
let account_size = Self::account_size(&account);

0 commit comments

Comments
 (0)