-
Notifications
You must be signed in to change notification settings - Fork 906
Memory Aware Caching in Lighthouse #7803
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: unstable
Are you sure you want to change the base?
Changes from all commits
07a19bf
b2aeb85
5074f3a
e65d834
13629c0
d681be3
b6874b8
be7a772
cf9ef9b
d758cdb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -816,6 +816,15 @@ pub fn cli_app() -> Command { | |
.action(ArgAction::Set) | ||
.display_order(0) | ||
) | ||
.arg( | ||
Arg::new("state-cache-max-bytes") | ||
.long("state-cache-max-bytes") | ||
.value_name("BYTES") | ||
.help("Specifies the maximum size of the state cache in bytes") | ||
.default_value("536870912") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This CLI param should probably be in MiB, as I doubt it's useful to set a finer granularity than 1 MiB. |
||
.action(ArgAction::Set) | ||
.display_order(0) | ||
) | ||
/* | ||
* Execution Layer Integration | ||
*/ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -377,6 +377,12 @@ pub fn get_config<E: EthSpec>( | |
.map_err(|_| "state-cache-size is not a valid integer".to_string())?; | ||
} | ||
|
||
if let Some(max_bytes) = cli_args.get_one::<String>("state-cache-max-bytes") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
client_config.store.max_state_cache_bytes = max_bytes | ||
.parse() | ||
.map_err(|_| "state-cache-max-bytes is not a valid integer".to_string())?; | ||
} | ||
|
||
if let Some(historic_state_cache_size) = | ||
clap_utils::parse_optional(cli_args, "historic-state-cache-size")? | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
use bls::{FixedBytesExtended, Hash256, PublicKeyBytes}; | ||
use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; | ||
use rand::Rng; | ||
use ssz::Decode; | ||
use std::num::NonZeroUsize; | ||
use store::state_cache::StateCache; | ||
|
||
use types::{ | ||
BeaconState, ChainSpec, Epoch, Eth1Data, EthSpec, MainnetEthSpec as E, Slot, Validator, | ||
}; | ||
|
||
fn build_state( | ||
spec: &ChainSpec, | ||
slot: Slot, | ||
validator_count: usize, | ||
rng: &mut impl Rng, | ||
) -> BeaconState<E> { | ||
let genesis_time = 0; | ||
let eth1_data = Eth1Data::default(); | ||
let mut state = BeaconState::<E>::new(genesis_time, eth1_data, spec); | ||
|
||
for _ in 0..validator_count { | ||
append_validator(&mut state, rng); | ||
} | ||
|
||
*state.slot_mut() = slot; | ||
state.latest_block_header_mut().slot = slot; | ||
state.apply_pending_mutations().unwrap(); | ||
state | ||
} | ||
|
||
fn append_validator(state: &mut BeaconState<E>, mut rng: &mut impl Rng) { | ||
state | ||
.balances_mut() | ||
.push(32_000_000_000 + rng.random_range(1..=1_000_000_000)) | ||
.unwrap(); | ||
if let Ok(inactivity_scores) = state.inactivity_scores_mut() { | ||
inactivity_scores.push(0).unwrap(); | ||
} | ||
state | ||
.validators_mut() | ||
.push(rand_validator(&mut rng)) | ||
.unwrap(); | ||
} | ||
|
||
fn rand_validator(rng: &mut impl Rng) -> Validator { | ||
let mut pubkey = [0u8; 48]; | ||
rng.fill_bytes(&mut pubkey); | ||
let withdrawal_credentials: [u8; 32] = rng.random(); | ||
|
||
Validator { | ||
pubkey: PublicKeyBytes::from_ssz_bytes(&pubkey).unwrap(), | ||
withdrawal_credentials: withdrawal_credentials.into(), | ||
slashed: false, | ||
effective_balance: 32_000_000_000, | ||
activation_eligibility_epoch: Epoch::max_value(), | ||
activation_epoch: Epoch::max_value(), | ||
exit_epoch: Epoch::max_value(), | ||
withdrawable_epoch: Epoch::max_value(), | ||
} | ||
} | ||
|
||
pub fn all_benches(c: &mut Criterion) { | ||
let spec = E::default_spec(); | ||
let mut rng = rand::rng(); | ||
let num_states = 20; | ||
let validator_count = 1024; | ||
|
||
let states: Vec<(Hash256, Hash256, BeaconState<E>)> = (0..num_states) | ||
.map(|i| { | ||
let slot = Slot::new(i as u64); | ||
let state = build_state(&spec, slot, validator_count, &mut rng); | ||
let root = Hash256::from_low_u64_le(i as u64 + 1); | ||
(root, root, state) | ||
}).collect(); | ||
|
||
let capacity = NonZeroUsize::new(num_states).unwrap(); | ||
let headroom = NonZeroUsize::new(1).unwrap(); | ||
let hdiff_capacity = NonZeroUsize::new(1).unwrap(); | ||
|
||
c.bench_function("state_cache_insert_without_memory_limit", |b| { | ||
b.iter_batched( | ||
|| StateCache::new(capacity, headroom, hdiff_capacity, usize::MAX), | ||
|mut cache| { | ||
for (state_root, block_root, state) in &states { | ||
cache.put_state(*state_root, *block_root, state).unwrap(); | ||
} | ||
}, | ||
BatchSize::SmallInput, | ||
); | ||
}); | ||
|
||
let low_max_bytes = 1_000_000; | ||
c.bench_function("state_cache_insert_with_memory_limit", |b| { | ||
b.iter_batched( | ||
|| StateCache::new(capacity, headroom, hdiff_capacity, low_max_bytes), | ||
|mut cache| { | ||
for (state_root, block_root, state) in &states { | ||
cache.put_state(*state_root, *block_root, state).unwrap(); | ||
} | ||
assert!(cache.cached_bytes() <= cache.max_cached_bytes()); | ||
}, | ||
BatchSize::SmallInput, | ||
); | ||
}); | ||
|
||
} | ||
|
||
criterion_group!{ | ||
name = benches; | ||
config = Criterion::default().sample_size(10); | ||
targets = all_benches | ||
} | ||
criterion_main!(benches); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
use crate::metrics::BEACON_STATE_MEMORY_SIZE_CALCULATION_TIME; | ||
use milhouse::mem::{MemorySize, MemoryTracker}; | ||
use std::time::Instant; | ||
use types::{BeaconState, EthSpec}; | ||
|
||
/// BeaconState Wrapper for memory tracking. | ||
pub struct BeaconStateWrapper<'a, E: EthSpec>(pub &'a BeaconState<E>); | ||
|
||
impl<'a, E: EthSpec> MemorySize for BeaconStateWrapper<'a, E> { | ||
fn self_pointer(&self) -> usize { | ||
self.0 as *const _ as usize | ||
} | ||
|
||
fn subtrees(&self) -> Vec<&dyn MemorySize> { | ||
vec![] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're missing all of the fields of the |
||
} | ||
|
||
fn intrinsic_size(&self) -> usize { | ||
std::mem::size_of::<Self>() | ||
} | ||
} | ||
|
||
/// Extension trait for approximate memory consumption of a `BeaconState`. | ||
pub trait BeaconStateMemorySize { | ||
fn memory_size(&self) -> usize; | ||
} | ||
|
||
impl<E: EthSpec> BeaconStateMemorySize for BeaconState<E> { | ||
fn memory_size(&self) -> usize { | ||
let wrapper = BeaconStateWrapper(self); | ||
// Timer for MemorySize | ||
let timer = Instant::now(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can do this using |
||
// Use MemoryTracker on the wrapper | ||
let mut tracker = MemoryTracker::default(); | ||
let stats = tracker.track_item(&wrapper); | ||
|
||
let elapsed_time = timer.elapsed(); | ||
metrics::observe( | ||
&BEACON_STATE_MEMORY_SIZE_CALCULATION_TIME, | ||
elapsed_time.as_secs_f64(), | ||
); | ||
stats.differential_size | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a stylistic thing but we could write this constant as
512 * (1 << 20)
, perhaps with an intermediate constant likeconst MEGABYTE: usize = 1 << 20
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or
512 * 1024 * 1024
is even better!There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This config can probably be deleted actually, as you've got the default in the store config