Skip to content

Commit ca412ab

Browse files
Use rebasing to minimise BeaconState mem usage (#4416)
* Use "rebasing" to minimise BeaconState mem usage * Update metastruct * Use upstream milhouse, update cargo lock * Rebase caches for extra memory savings
1 parent 6eb1513 commit ca412ab

File tree

4 files changed

+152
-7
lines changed

4 files changed

+152
-7
lines changed

Cargo.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

beacon_node/store/src/hot_cold_store.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1253,12 +1253,35 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
12531253
);
12541254
assert_eq!(summary.diff_base_slot, state.slot());
12551255

1256-
let mut base_buffer = HDiffBuffer::from_state(state);
1256+
let t = std::time::Instant::now();
1257+
let pre_state = state.clone();
1258+
let mut base_buffer = HDiffBuffer::from_state(pre_state.clone());
12571259
diff.apply(&mut base_buffer)?;
12581260
state = base_buffer.into_state(&self.spec)?;
1261+
let application_ms = t.elapsed().as_millis();
12591262

1263+
// Rebase state before adding it to the cache, to ensure it uses minimal memory.
1264+
let t = std::time::Instant::now();
1265+
state.rebase_on(&pre_state, &self.spec)?;
1266+
let rebase_ms = t.elapsed().as_millis();
1267+
1268+
let t = std::time::Instant::now();
12601269
state.update_tree_hash_cache()?;
1270+
let tree_hash_ms = t.elapsed().as_millis();
1271+
1272+
let t = std::time::Instant::now();
12611273
state.build_all_caches(&self.spec)?;
1274+
let cache_ms = t.elapsed().as_millis();
1275+
1276+
debug!(
1277+
self.log,
1278+
"State diff applied";
1279+
"application_ms" => application_ms,
1280+
"rebase_ms" => rebase_ms,
1281+
"tree_hash_ms" => tree_hash_ms,
1282+
"cache_ms" => cache_ms,
1283+
"slot" => state.slot()
1284+
);
12621285

12631286
// Add state to the cache, it is by definition an epoch boundary state and likely
12641287
// to be useful.

consensus/types/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ lazy_static = "1.4.0"
4747
parking_lot = "0.12.0"
4848
itertools = "0.10.0"
4949
superstruct = "0.7.0"
50-
metastruct = "0.1.0"
50+
metastruct = "0.1.1"
5151
serde_json = "1.0.74"
5252
smallvec = "1.8.0"
5353
milhouse = { git = "https://github.com/sigp/milhouse", branch = "main" }

consensus/types/src/beacon_state.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,27 +218,51 @@ impl From<BeaconStateHash> for Hash256 {
218218
map_beacon_state_base_fields(),
219219
map_beacon_state_base_tree_list_fields(mutable, fallible, groups(tree_lists)),
220220
),
221+
bimappings(bimap_beacon_state_base_tree_list_fields(
222+
other_type = "BeaconStateBase",
223+
self_mutable,
224+
fallible,
225+
groups(tree_lists)
226+
)),
221227
num_fields(all()),
222228
)),
223229
Altair(metastruct(
224230
mappings(
225231
map_beacon_state_altair_fields(),
226232
map_beacon_state_altair_tree_list_fields(mutable, fallible, groups(tree_lists)),
227233
),
234+
bimappings(bimap_beacon_state_altair_tree_list_fields(
235+
other_type = "BeaconStateAltair",
236+
self_mutable,
237+
fallible,
238+
groups(tree_lists)
239+
)),
228240
num_fields(all()),
229241
)),
230242
Merge(metastruct(
231243
mappings(
232244
map_beacon_state_bellatrix_fields(),
233245
map_beacon_state_bellatrix_tree_list_fields(mutable, fallible, groups(tree_lists)),
234246
),
247+
bimappings(bimap_beacon_state_merge_tree_list_fields(
248+
other_type = "BeaconStateMerge",
249+
self_mutable,
250+
fallible,
251+
groups(tree_lists)
252+
)),
235253
num_fields(all()),
236254
)),
237255
Capella(metastruct(
238256
mappings(
239257
map_beacon_state_capella_fields(),
240258
map_beacon_state_capella_tree_list_fields(mutable, fallible, groups(tree_lists)),
241259
),
260+
bimappings(bimap_beacon_state_capella_tree_list_fields(
261+
other_type = "BeaconStateCapella",
262+
self_mutable,
263+
fallible,
264+
groups(tree_lists)
265+
)),
242266
num_fields(all()),
243267
)),
244268
),
@@ -287,6 +311,8 @@ where
287311
#[metastruct(exclude_from(tree_lists))]
288312
pub eth1_data: Eth1Data,
289313
#[test_random(default)]
314+
// FIXME(sproul): excluded due to `rebase_on` issue
315+
#[metastruct(exclude_from(tree_lists))]
290316
pub eth1_data_votes: VList<Eth1Data, T::SlotsPerEth1VotingPeriod>,
291317
#[superstruct(getter(copy))]
292318
#[metastruct(exclude_from(tree_lists))]
@@ -1739,6 +1765,101 @@ impl<T: EthSpec> BeaconState<T> {
17391765
};
17401766
Ok(sync_committee)
17411767
}
1768+
1769+
// FIXME(sproul): missing eth1 data votes, they would need a ResetListDiff
1770+
#[allow(clippy::integer_arithmetic)]
1771+
pub fn rebase_on(&mut self, base: &Self, spec: &ChainSpec) -> Result<(), Error> {
1772+
// Required for macros (which use type-hints internally).
1773+
type GenericValidator = Validator;
1774+
1775+
match (&mut *self, base) {
1776+
(Self::Base(self_inner), Self::Base(base_inner)) => {
1777+
bimap_beacon_state_base_tree_list_fields!(
1778+
self_inner,
1779+
base_inner,
1780+
|_, self_field, base_field| { self_field.rebase_on(base_field) }
1781+
);
1782+
}
1783+
(Self::Altair(self_inner), Self::Altair(base_inner)) => {
1784+
bimap_beacon_state_altair_tree_list_fields!(
1785+
self_inner,
1786+
base_inner,
1787+
|_, self_field, base_field| { self_field.rebase_on(base_field) }
1788+
);
1789+
}
1790+
(Self::Merge(self_inner), Self::Merge(base_inner)) => {
1791+
bimap_beacon_state_merge_tree_list_fields!(
1792+
self_inner,
1793+
base_inner,
1794+
|_, self_field, base_field| { self_field.rebase_on(base_field) }
1795+
);
1796+
}
1797+
(Self::Capella(self_inner), Self::Capella(base_inner)) => {
1798+
bimap_beacon_state_capella_tree_list_fields!(
1799+
self_inner,
1800+
base_inner,
1801+
|_, self_field, base_field| { self_field.rebase_on(base_field) }
1802+
);
1803+
}
1804+
// Do not rebase across forks, this should be OK for most situations.
1805+
_ => {}
1806+
}
1807+
1808+
// Use sync committees from `base` if they are equal.
1809+
if let Ok(current_sync_committee) = self.current_sync_committee_mut() {
1810+
if let Ok(base_sync_committee) = base.current_sync_committee() {
1811+
if current_sync_committee == base_sync_committee {
1812+
*current_sync_committee = base_sync_committee.clone();
1813+
}
1814+
}
1815+
}
1816+
if let Ok(next_sync_committee) = self.next_sync_committee_mut() {
1817+
if let Ok(base_sync_committee) = base.next_sync_committee() {
1818+
if next_sync_committee == base_sync_committee {
1819+
*next_sync_committee = base_sync_committee.clone();
1820+
}
1821+
}
1822+
}
1823+
1824+
// Rebase caches like the committee caches and the pubkey cache, which are expensive to
1825+
// rebuild and likely to be re-usable from the base state.
1826+
self.rebase_caches_on(base, spec)?;
1827+
1828+
Ok(())
1829+
}
1830+
1831+
pub fn rebase_caches_on(&mut self, base: &Self, spec: &ChainSpec) -> Result<(), Error> {
1832+
// Use pubkey cache from `base` if it contains superior information (likely if our cache is
1833+
// uninitialized).
1834+
let num_validators = self.validators().len();
1835+
let pubkey_cache = self.pubkey_cache_mut();
1836+
let base_pubkey_cache = base.pubkey_cache();
1837+
if pubkey_cache.len() < base_pubkey_cache.len() && pubkey_cache.len() < num_validators {
1838+
*pubkey_cache = base_pubkey_cache.clone();
1839+
}
1840+
1841+
// Use committee caches from `base` if they are relevant.
1842+
let epochs = [
1843+
self.previous_epoch(),
1844+
self.current_epoch(),
1845+
self.next_epoch()?,
1846+
];
1847+
for (index, epoch) in epochs.into_iter().enumerate() {
1848+
if let Ok(base_relative_epoch) = RelativeEpoch::from_epoch(base.current_epoch(), epoch)
1849+
{
1850+
*self.committee_cache_at_index_mut(index)? =
1851+
base.committee_cache(base_relative_epoch)?.clone();
1852+
1853+
// Ensure total active balance cache remains built whenever current committee
1854+
// cache is built.
1855+
if epoch == self.current_epoch() {
1856+
self.build_total_active_balance_cache(spec)?;
1857+
}
1858+
}
1859+
}
1860+
1861+
Ok(())
1862+
}
17421863
}
17431864

17441865
impl<T: EthSpec, GenericValidator: ValidatorTrait> BeaconState<T, GenericValidator> {
@@ -1790,6 +1911,7 @@ impl<T: EthSpec, GenericValidator: ValidatorTrait> BeaconState<T, GenericValidat
17901911
map_beacon_state_capella_tree_list_fields!(inner, |_, x| { x.apply_updates() })
17911912
}
17921913
}
1914+
self.eth1_data_votes_mut().apply_updates()?;
17931915
Ok(())
17941916
}
17951917

0 commit comments

Comments
 (0)