From 58c49672ca8678295edada997c37da96c28bdacb Mon Sep 17 00:00:00 2001 From: houseme Date: Sat, 8 Aug 2026 02:52:31 +0800 Subject: [PATCH] perf(ecstore): cache modern erasure codec construction (#5824) Co-authored-by: heihutu Co-authored-by: zhi22915 --- crates/ecstore/src/erasure/coding/erasure.rs | 49 ++++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/crates/ecstore/src/erasure/coding/erasure.rs b/crates/ecstore/src/erasure/coding/erasure.rs index 50910f47b..f80bd0570 100644 --- a/crates/ecstore/src/erasure/coding/erasure.rs +++ b/crates/ecstore/src/erasure/coding/erasure.rs @@ -20,12 +20,21 @@ use bytes::{Bytes, BytesMut}; use reed_solomon_erasure::galois_8::ReedSolomon; use reed_solomon_simd; use smallvec::SmallVec; -use std::io; +use std::{ + collections::HashMap, + io, + sync::{Arc, OnceLock, RwLock}, +}; use tokio::io::AsyncRead; use tracing::warn; use uuid::Uuid; const MODERN_MAX_TOTAL_SHARDS: usize = ::ORDER; +const MODERN_REED_SOLOMON_CACHE_MAX_ENTRIES: usize = 64; + +type ModernReedSolomonCache = RwLock>>; + +static MODERN_REED_SOLOMON_CACHE: OnceLock = OnceLock::new(); /// Errors returned when constructing an [`Erasure`] codec. #[derive(Debug, thiserror::Error)] @@ -275,7 +284,7 @@ impl LegacyReedSolomonEncoder { pub struct ReedSolomonEncoder { data_shards: usize, parity_shards: usize, - encoder: Option, + encoder: Option>, } impl Clone for ReedSolomonEncoder { @@ -291,7 +300,7 @@ impl Clone for ReedSolomonEncoder { impl ReedSolomonEncoder { fn try_new_typed(data_shards: usize, parity_shards: usize) -> Result { let encoder = if parity_shards > 0 { - Some(ReedSolomon::new(data_shards, parity_shards)?) + Some(cached_modern_reed_solomon(data_shards, parity_shards)?) } else { None }; @@ -362,6 +371,30 @@ impl ReedSolomonEncoder { } } +fn cached_modern_reed_solomon(data_shards: usize, parity_shards: usize) -> Result, reed_solomon_erasure::Error> { + let key = (data_shards, parity_shards); + let cache = MODERN_REED_SOLOMON_CACHE.get_or_init(|| RwLock::new(HashMap::new())); + + if let Some(encoder) = cache + .read() + .unwrap_or_else(|poisoned| poisoned.into_inner()) + .get(&key) + .cloned() + { + return Ok(encoder); + } + + let encoder = Arc::new(ReedSolomon::new(data_shards, parity_shards)?); + let mut cache = cache.write().unwrap_or_else(|poisoned| poisoned.into_inner()); + if let Some(existing) = cache.get(&key) { + return Ok(Arc::clone(existing)); + } + if cache.len() < MODERN_REED_SOLOMON_CACHE_MAX_ENTRIES { + cache.insert(key, Arc::clone(&encoder)); + } + Ok(encoder) +} + fn encode_parity_shards(shards: &mut [Option>], data_shards: usize, parity_shards: usize, encode: F) -> io::Result<()> where F: FnOnce(SmallVec<[&mut [u8]; 16]>) -> io::Result<()>, @@ -1272,6 +1305,16 @@ mod tests { assert!(legacy.legacy_encoder.is_some()); } + #[test] + fn modern_encoder_construction_reuses_cached_codec() { + let first = ReedSolomonEncoder::try_new_typed(31, 7).expect("modern codec should construct"); + let second = ReedSolomonEncoder::try_new_typed(31, 7).expect("modern codec should construct"); + + let first = first.encoder.as_ref().expect("modern codec should initialize an encoder"); + let second = second.encoder.as_ref().expect("modern codec should initialize an encoder"); + assert!(Arc::ptr_eq(first, second)); + } + #[test] fn construction_errors_preserve_encoder_sources() { let modern = ErasureConstructionError::ModernEncoder {