Use hashlink over lru for LruCache (#8911)

Use the `LruCache` implementation provided by `hashlink` instead of the current `lru` one.
This is mostly a 1-to-1 swap with only slight API incompatibilities.
I have decided to leave some config files which previously used `NonZeroUsize` but they may not be required anymore and could potentially switch to `usize`.


Co-Authored-By: Mac L <mjladson@pm.me>
This commit is contained in:
Mac L
2026-06-16 10:54:11 +04:00
committed by GitHub
parent 41dff2d965
commit e0ff3b5709
28 changed files with 166 additions and 192 deletions

View File

@@ -16,10 +16,10 @@ directory = { workspace = true }
ethereum_ssz = { workspace = true }
ethereum_ssz_derive = { workspace = true }
fixed_bytes = { workspace = true }
hashlink = { workspace = true }
itertools = { workspace = true }
leveldb = { version = "0.8.6", optional = true, default-features = false }
logging = { workspace = true }
lru = { workspace = true }
metrics = { workspace = true }
milhouse = { workspace = true }
parking_lot = { workspace = true }

View File

@@ -1,7 +1,6 @@
use crate::hdiff::{Error, HDiffBuffer};
use crate::metrics;
use lru::LruCache;
use std::num::NonZeroUsize;
use hashlink::lru_cache::LruCache;
use types::{BeaconState, ChainSpec, EthSpec, Slot};
/// Holds a combination of finalized states in two formats:
@@ -25,7 +24,7 @@ pub struct Metrics {
}
impl<E: EthSpec> HistoricStateCache<E> {
pub fn new(hdiff_buffer_cache_size: NonZeroUsize, state_cache_size: NonZeroUsize) -> Self {
pub fn new(hdiff_buffer_cache_size: usize, state_cache_size: usize) -> Self {
Self {
hdiff_buffers: LruCache::new(hdiff_buffer_cache_size),
states: LruCache::new(state_cache_size),
@@ -47,7 +46,7 @@ impl<E: EthSpec> HistoricStateCache<E> {
);
let cloned = buffer.clone();
drop(_timer);
self.hdiff_buffers.put(slot, cloned);
self.hdiff_buffers.insert(slot, cloned);
Some(buffer)
} else {
None
@@ -63,7 +62,7 @@ impl<E: EthSpec> HistoricStateCache<E> {
Ok(Some(state.clone()))
} else if let Some(buffer) = self.hdiff_buffers.get(&slot) {
let state = buffer.as_state(spec)?;
self.states.put(slot, state.clone());
self.states.insert(slot, state.clone());
Ok(Some(state))
} else {
Ok(None)
@@ -71,11 +70,11 @@ impl<E: EthSpec> HistoricStateCache<E> {
}
pub fn put_state(&mut self, slot: Slot, state: BeaconState<E>) {
self.states.put(slot, state);
self.states.insert(slot, state);
}
pub fn put_hdiff_buffer(&mut self, slot: Slot, buffer: HDiffBuffer) {
self.hdiff_buffers.put(slot, buffer);
self.hdiff_buffers.insert(slot, buffer);
}
pub fn put_both(&mut self, slot: Slot, state: BeaconState<E>, buffer: HDiffBuffer) {

View File

@@ -19,8 +19,8 @@ use crate::{
parse_data_column_key,
};
use fixed_bytes::FixedBytesExtended;
use hashlink::lru_cache::LruCache;
use itertools::{Itertools, process_results};
use lru::LruCache;
use parking_lot::{Mutex, RwLock};
use safe_arith::SafeArith;
use serde::{Deserialize, Serialize};
@@ -34,7 +34,6 @@ use std::cmp::{Ordering, min};
use std::collections::{HashMap, HashSet};
use std::io::{Read, Write};
use std::marker::PhantomData;
use std::num::NonZeroUsize;
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
@@ -97,7 +96,7 @@ struct BlockCache<E: EthSpec> {
}
impl<E: EthSpec> BlockCache<E> {
pub fn new(size: NonZeroUsize) -> Self {
pub fn new(size: usize) -> Self {
Self {
block_cache: LruCache::new(size),
blob_cache: LruCache::new(size),
@@ -106,14 +105,15 @@ impl<E: EthSpec> BlockCache<E> {
}
}
pub fn put_block(&mut self, block_root: Hash256, block: SignedBeaconBlock<E>) {
self.block_cache.put(block_root, block);
self.block_cache.insert(block_root, block);
}
pub fn put_blobs(&mut self, block_root: Hash256, blobs: BlobSidecarList<E>) {
self.blob_cache.put(block_root, blobs);
self.blob_cache.insert(block_root, blobs);
}
pub fn put_data_column(&mut self, block_root: Hash256, data_column: Arc<DataColumnSidecar<E>>) {
self.data_column_cache
.get_or_insert_mut(block_root, Default::default)
.entry(block_root)
.or_insert_with(Default::default)
.insert(*data_column.index(), data_column);
}
pub fn put_data_column_custody_info(
@@ -143,13 +143,13 @@ impl<E: EthSpec> BlockCache<E> {
self.data_column_custody_info_cache.clone()
}
pub fn delete_block(&mut self, block_root: &Hash256) {
let _ = self.block_cache.pop(block_root);
let _ = self.block_cache.remove(block_root);
}
pub fn delete_blobs(&mut self, block_root: &Hash256) {
let _ = self.blob_cache.pop(block_root);
let _ = self.blob_cache.remove(block_root);
}
pub fn delete_data_columns(&mut self, block_root: &Hash256) {
let _ = self.data_column_cache.pop(block_root);
let _ = self.data_column_cache.remove(block_root);
}
pub fn delete(&mut self, block_root: &Hash256) {
self.delete_block(block_root);
@@ -236,17 +236,16 @@ impl<E: EthSpec> HotColdDB<E, MemoryStore, MemoryStore> {
cold_db: MemoryStore::open(),
blobs_db: MemoryStore::open(),
hot_db: MemoryStore::open(),
block_cache: NonZeroUsize::new(config.block_cache_size)
.map(BlockCache::new)
.map(Mutex::new),
block_cache: (config.block_cache_size > 0)
.then(|| Mutex::new(BlockCache::new(config.block_cache_size))),
state_cache: Mutex::new(StateCache::new(
config.state_cache_size,
config.state_cache_headroom,
config.hot_hdiff_buffer_cache_size,
)),
historic_state_cache: Mutex::new(HistoricStateCache::new(
config.cold_hdiff_buffer_cache_size,
config.historic_state_cache_size,
config.cold_hdiff_buffer_cache_size.get(),
config.historic_state_cache_size.get(),
)),
config,
hierarchy,
@@ -290,17 +289,16 @@ impl<E: EthSpec> HotColdDB<E, BeaconNodeBackend, BeaconNodeBackend> {
blobs_db: BeaconNodeBackend::open(&config, blobs_db_path)?,
cold_db: BeaconNodeBackend::open(&config, cold_path)?,
hot_db,
block_cache: NonZeroUsize::new(config.block_cache_size)
.map(BlockCache::new)
.map(Mutex::new),
block_cache: (config.block_cache_size > 0)
.then(|| Mutex::new(BlockCache::new(config.block_cache_size))),
state_cache: Mutex::new(StateCache::new(
config.state_cache_size,
config.state_cache_headroom,
config.hot_hdiff_buffer_cache_size,
)),
historic_state_cache: Mutex::new(HistoricStateCache::new(
config.cold_hdiff_buffer_cache_size,
config.historic_state_cache_size,
config.cold_hdiff_buffer_cache_size.get(),
config.historic_state_cache_size.get(),
)),
config,
hierarchy,

View File

@@ -3,7 +3,7 @@ use crate::{
Error,
metrics::{self, HOT_METRIC},
};
use lru::LruCache;
use hashlink::lru_cache::LruCache;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::num::NonZeroUsize;
use tracing::instrument;
@@ -86,9 +86,9 @@ impl<E: EthSpec> StateCache<E> {
) -> Self {
StateCache {
finalized_state: None,
states: LruCache::new(state_capacity),
states: LruCache::new(state_capacity.get()),
block_map: BlockMap::default(),
hdiff_buffers: HotHDiffBufferCache::new(hdiff_capacity),
hdiff_buffers: HotHDiffBufferCache::new(hdiff_capacity.get()),
max_epoch: Epoch::new(0),
head_block_root: Hash256::ZERO,
headroom,
@@ -100,7 +100,7 @@ impl<E: EthSpec> StateCache<E> {
}
pub fn capacity(&self) -> usize {
self.states.cap().get()
self.states.capacity()
}
pub fn num_hdiff_buffers(&self) -> usize {
@@ -154,7 +154,7 @@ impl<E: EthSpec> StateCache<E> {
// preferences older slots.
// NOTE: This isn't perfect as it prunes by slot: there could be multiple buffers
// at some slots in the case of long forks without finality.
let new_hdiff_cache = HotHDiffBufferCache::new(self.hdiff_buffers.cap());
let new_hdiff_cache = HotHDiffBufferCache::new(self.hdiff_buffers.capacity());
let old_hdiff_cache = std::mem::replace(&mut self.hdiff_buffers, new_hdiff_cache);
for (state_root, (slot, buffer)) in old_hdiff_cache.hdiff_buffers {
if pre_finalized_slots_to_retain.contains(&slot) {
@@ -164,7 +164,7 @@ impl<E: EthSpec> StateCache<E> {
// Delete states.
for state_root in state_roots_to_prune {
if let Some((_, state)) = self.states.pop(&state_root) {
if let Some((_, state)) = self.states.remove(&state_root) {
// Add the hdiff buffer for this state to the hdiff cache if it is now part of
// the pre-finalized grid. The `put` method will take care of keeping the most
// useful buffers.
@@ -260,7 +260,7 @@ impl<E: EthSpec> StateCache<E> {
// Insert the full state into the cache.
if let Some((deleted_state_root, _)) =
self.states.put(state_root, (state_root, state.clone()))
self.states.insert(state_root, (state_root, state.clone()))
{
deleted_states.push(deleted_state_root);
}
@@ -334,14 +334,14 @@ impl<E: EthSpec> StateCache<E> {
}
pub fn delete_state(&mut self, state_root: &Hash256) {
self.states.pop(state_root);
self.states.remove(state_root);
self.block_map.delete(state_root);
}
pub fn delete_block_states(&mut self, block_root: &Hash256) {
if let Some(slot_map) = self.block_map.delete_block_states(block_root) {
for state_root in slot_map.slots.values() {
self.states.pop(state_root);
self.states.remove(state_root);
}
}
}
@@ -366,9 +366,10 @@ impl<E: EthSpec> StateCache<E> {
let mut old_boundary_state_roots = vec![];
let mut good_boundary_state_roots = vec![];
// Skip the `cull_exempt` most-recently used, then reverse the iterator to start at
// least-recently used states.
for (&state_root, (_, state)) in self.states.iter().skip(cull_exempt).rev() {
// Start at the least-recently used states, excluding the `cull_exempt` most-recently
// used (which are the final entries of the iterator).
let num_cull_candidates = self.states.len().saturating_sub(cull_exempt);
for (&state_root, (_, state)) in self.states.iter().take(num_cull_candidates) {
let is_advanced = state.slot() > state.latest_block_header().slot;
let is_boundary = state.slot() % E::slots_per_epoch() == 0;
let could_finalize =
@@ -450,7 +451,7 @@ impl BlockMap {
}
impl HotHDiffBufferCache {
pub fn new(capacity: NonZeroUsize) -> Self {
pub fn new(capacity: usize) -> Self {
Self {
hdiff_buffers: LruCache::new(capacity),
}
@@ -467,8 +468,8 @@ impl HotHDiffBufferCache {
/// If the value was inserted then `true` is returned.
pub fn put(&mut self, state_root: Hash256, slot: Slot, buffer: HDiffBuffer) -> bool {
// If the cache is not full, simply insert the value.
if self.hdiff_buffers.len() != self.hdiff_buffers.cap().get() {
self.hdiff_buffers.put(state_root, (slot, buffer));
if self.hdiff_buffers.len() != self.hdiff_buffers.capacity() {
self.hdiff_buffers.insert(state_root, (slot, buffer));
return true;
}
@@ -484,23 +485,23 @@ impl HotHDiffBufferCache {
return false;
};
if self.hdiff_buffers.cap().get() > 1 || slot < min_slot {
if self.hdiff_buffers.capacity() > 1 || slot < min_slot {
// Remove LRU value. Cache is now at size `cap - 1`.
let Some((removed_state_root, (removed_slot, removed_buffer))) =
self.hdiff_buffers.pop_lru()
self.hdiff_buffers.remove_lru()
else {
// Unreachable: cache is full so should have at least one entry to pop.
return false;
};
// Insert new value. Cache size is now at size `cap`.
self.hdiff_buffers.put(state_root, (slot, buffer));
self.hdiff_buffers.insert(state_root, (slot, buffer));
// If the removed value had the min slot and we didn't intend to replace it (cap=1)
// then we reinsert it.
if removed_slot == min_slot && slot >= min_slot {
self.hdiff_buffers
.put(removed_state_root, (removed_slot, removed_buffer));
.insert(removed_state_root, (removed_slot, removed_buffer));
}
true
} else {
@@ -509,8 +510,8 @@ impl HotHDiffBufferCache {
}
}
pub fn cap(&self) -> NonZeroUsize {
self.hdiff_buffers.cap()
pub fn capacity(&self) -> usize {
self.hdiff_buffers.capacity()
}
#[allow(clippy::len_without_is_empty)]