From 46a9b9ad3d41c0402e6ce5ac499d2ca28f89a20d Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Tue, 3 Feb 2026 13:31:05 +0100 Subject: [PATCH] perf: replace RwLock with DashMap/DashSet (#21692) Co-authored-by: Georgios Konstantopoulos Co-authored-by: joshieDo <93316087+joshieDo@users.noreply.github.com> --- Cargo.lock | 5 +++-- crates/net/dns/Cargo.toml | 4 ++-- crates/net/dns/src/resolver.rs | 12 +++++------ .../provider/src/providers/state/overlay.rs | 20 +++++++++---------- crates/storage/rpc-provider/Cargo.toml | 2 +- crates/storage/rpc-provider/src/lib.rs | 20 ++++++++----------- 6 files changed, 29 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0897bb0058..4d95903b2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2902,6 +2902,7 @@ dependencies = [ "lock_api", "once_cell", "parking_lot_core", + "serde", ] [[package]] @@ -8233,11 +8234,11 @@ dependencies = [ "alloy-chains", "alloy-primitives", "alloy-rlp", + "dashmap", "data-encoding", "enr", "hickory-resolver", "linked_hash_set", - "parking_lot", "rand 0.9.2", "reth-chainspec", "reth-ethereum-forks", @@ -10999,7 +11000,7 @@ dependencies = [ "alloy-provider", "alloy-rpc-types", "alloy-rpc-types-engine", - "parking_lot", + "dashmap", "reth-chainspec", "reth-db-api", "reth-errors", diff --git a/crates/net/dns/Cargo.toml b/crates/net/dns/Cargo.toml index 8a04d1517d..f4b789105c 100644 --- a/crates/net/dns/Cargo.toml +++ b/crates/net/dns/Cargo.toml @@ -30,12 +30,12 @@ tokio-stream.workspace = true hickory-resolver = { workspace = true, features = ["tokio"] } # misc +dashmap = { workspace = true, features = ["inline"] } data-encoding.workspace = true linked_hash_set.workspace = true schnellru.workspace = true thiserror.workspace = true tracing.workspace = true -parking_lot.workspace = true serde = { workspace = true, optional = true } serde_with = { workspace = true, optional = true } @@ -56,9 +56,9 @@ serde = [ "alloy-primitives/serde", "enr/serde", "linked_hash_set/serde", - "parking_lot/serde", "rand/serde", "secp256k1/serde", "hickory-resolver/serde", "reth-ethereum-forks/serde", + "dashmap/serde", ] diff --git a/crates/net/dns/src/resolver.rs b/crates/net/dns/src/resolver.rs index 6e02530d40..434f34c75a 100644 --- a/crates/net/dns/src/resolver.rs +++ b/crates/net/dns/src/resolver.rs @@ -1,9 +1,9 @@ //! Perform DNS lookups +use dashmap::DashMap; use hickory_resolver::name_server::ConnectionProvider; pub use hickory_resolver::{ResolveError, TokioResolver}; -use parking_lot::RwLock; -use std::{collections::HashMap, future::Future}; +use std::future::Future; use tracing::trace; /// A type that can lookup DNS entries @@ -72,25 +72,25 @@ impl Resolver for DnsResolver { /// A [Resolver] that uses an in memory map to lookup entries #[derive(Debug, Default)] -pub struct MapResolver(RwLock>); +pub struct MapResolver(DashMap); // === impl MapResolver === impl MapResolver { /// Inserts a key-value pair into the map. pub fn insert(&self, k: String, v: String) -> Option { - self.0.write().insert(k, v) + self.0.insert(k, v) } /// Returns the value corresponding to the key pub fn get(&self, k: &str) -> Option { - self.0.read().get(k).cloned() + self.0.get(k).map(|entry| entry.value().clone()) } /// Removes a key from the map, returning the value at the key if the key was previously in the /// map. pub fn remove(&self, k: &str) -> Option { - self.0.write().remove(k) + self.0.remove(k).map(|(_, v)| v) } } diff --git a/crates/storage/provider/src/providers/state/overlay.rs b/crates/storage/provider/src/providers/state/overlay.rs index 8f7919f7f3..30232c221e 100644 --- a/crates/storage/provider/src/providers/state/overlay.rs +++ b/crates/storage/provider/src/providers/state/overlay.rs @@ -1,6 +1,6 @@ use alloy_primitives::{BlockNumber, B256}; +use dashmap::DashMap; use metrics::{Counter, Histogram}; -use parking_lot::RwLock; use reth_chain_state::LazyOverlay; use reth_db_api::DatabaseError; use reth_errors::{ProviderError, ProviderResult}; @@ -22,7 +22,6 @@ use reth_trie_db::{ ChangesetCache, DatabaseHashedCursorFactory, DatabaseHashedPostState, DatabaseTrieCursorFactory, }; use std::{ - collections::{hash_map::Entry, HashMap}, sync::Arc, time::{Duration, Instant}, }; @@ -102,7 +101,7 @@ pub struct OverlayStateProviderFactory { metrics: OverlayStateProviderMetrics, /// A cache which maps `db_tip -> Overlay`. If the db tip changes during usage of the factory /// then a new entry will get added to this, but in most cases only one entry is present. - overlay_cache: Arc>>, + overlay_cache: Arc>, } impl OverlayStateProviderFactory { @@ -419,17 +418,16 @@ where let db_tip_block = self.get_db_tip_block_number(provider)?; // If the overlay is present in the cache then return it directly. - if let Some(overlay) = self.overlay_cache.as_ref().read().get(&db_tip_block) { - return Ok(overlay.clone()); + if let Some(entry) = self.overlay_cache.get(&db_tip_block) { + return Ok(entry.value().clone()); } - // If the overlay is not present then we need to calculate a new one. We grab a write lock, - // and then check the cache again in case some other thread populated the cache since we - // checked with the read-lock. If still not present we calculate and populate. + // If the overlay is not present then we need to calculate a new one. + // DashMap's entry API handles the race condition internally. let mut cache_miss = false; - let overlay = match self.overlay_cache.as_ref().write().entry(db_tip_block) { - Entry::Occupied(entry) => entry.get().clone(), - Entry::Vacant(entry) => { + let overlay = match self.overlay_cache.entry(db_tip_block) { + dashmap::Entry::Occupied(entry) => entry.get().clone(), + dashmap::Entry::Vacant(entry) => { cache_miss = true; let overlay = self.calculate_overlay(provider, db_tip_block)?; entry.insert(overlay.clone()); diff --git a/crates/storage/rpc-provider/Cargo.toml b/crates/storage/rpc-provider/Cargo.toml index a47bf7ea21..54eb20ac12 100644 --- a/crates/storage/rpc-provider/Cargo.toml +++ b/crates/storage/rpc-provider/Cargo.toml @@ -40,7 +40,7 @@ tokio = { workspace = true, features = ["sync", "macros", "rt-multi-thread"] } # other tracing.workspace = true -parking_lot.workspace = true +dashmap = { workspace = true, features = ["inline"] } # revm revm.workspace = true diff --git a/crates/storage/rpc-provider/src/lib.rs b/crates/storage/rpc-provider/src/lib.rs index fa340ea2ae..1a50a106f1 100644 --- a/crates/storage/rpc-provider/src/lib.rs +++ b/crates/storage/rpc-provider/src/lib.rs @@ -27,13 +27,11 @@ use alloy_consensus::{constants::KECCAK_EMPTY, BlockHeader}; use alloy_eips::{BlockHashOrNumber, BlockNumberOrTag}; use alloy_network::{primitives::HeaderResponse, BlockResponse}; -use alloy_primitives::{ - map::HashMap, Address, BlockHash, BlockNumber, StorageKey, TxHash, TxNumber, B256, U256, -}; +use alloy_primitives::{Address, BlockHash, BlockNumber, StorageKey, TxHash, TxNumber, B256, U256}; use alloy_provider::{ext::DebugApi, network::Network, Provider}; use alloy_rpc_types::{AccountInfo, BlockId}; use alloy_rpc_types_engine::ForkchoiceState; -use parking_lot::RwLock; +use dashmap::DashMap; use reth_chainspec::{ChainInfo, ChainSpecProvider}; use reth_db_api::{ mock::{DatabaseMock, TxMock}, @@ -912,7 +910,7 @@ where /// Cached bytecode for accounts /// /// Since the state provider is short-lived, we don't worry about memory leaks. - code_store: RwLock>, + code_store: DashMap, /// Whether to use Reth-specific RPC methods for better performance reth_rpc_support: bool, } @@ -942,7 +940,7 @@ impl RpcBlockchainStateProvider { network: std::marker::PhantomData, chain_spec: None, compute_state_root: false, - code_store: RwLock::new(HashMap::default()), + code_store: Default::default(), reth_rpc_support: true, } } @@ -960,7 +958,7 @@ impl RpcBlockchainStateProvider { network: std::marker::PhantomData, chain_spec: Some(chain_spec), compute_state_root: false, - code_store: RwLock::new(HashMap::default()), + code_store: Default::default(), reth_rpc_support: true, } } @@ -982,7 +980,7 @@ impl RpcBlockchainStateProvider { network: self.network, chain_spec: self.chain_spec.clone(), compute_state_root: self.compute_state_root, - code_store: RwLock::new(HashMap::default()), + code_store: Default::default(), reth_rpc_support: self.reth_rpc_support, } } @@ -1038,9 +1036,7 @@ impl RpcBlockchainStateProvider { let code_hash = account_info.code_hash(); if code_hash != KECCAK_EMPTY { // Insert code into the cache - self.code_store - .write() - .insert(code_hash, Bytecode::new_raw(account_info.code.clone())); + self.code_store.insert(code_hash, Bytecode::new_raw(account_info.code.clone())); } Ok(account_info) @@ -1119,7 +1115,7 @@ where { fn bytecode_by_hash(&self, code_hash: &B256) -> Result, ProviderError> { if !self.reth_rpc_support { - return Ok(self.code_store.read().get(code_hash).cloned()); + return Ok(self.code_store.get(code_hash).map(|entry| entry.value().clone())); } self.block_on_async(async {