From e38fe6332a7a49ba45910d7a79d62c781faea795 Mon Sep 17 00:00:00 2001 From: leiysky Date: Wed, 28 Oct 2026 17:19:42 +0800 Subject: [PATCH 1/4] perf: remove redundant index page dirty state --- CHANGELOG.md | 2 +- cache2/src/region/index/storage/mod.rs | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec22b6e..ef3e08b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ ### Improvements -- L2 index updates and cold-record reclamation skip atomic writes for heat bits that are already clear. +- L2 index updates and cold-record reclamation avoid redundant atomic writes for already-clear heat bits and validated index pages. - Hot-record reclamation reuses the checksum computed during payload validation for its rewrite, avoiding a second payload scan while preserving record validation and the on-disk format. - L1 replacement and eviction reuse detached, exclusively owned allocations of the same size under CLOCK and S3-FIFO, reducing repeated allocation during fixed-size turnover without an additional buffer pool. Caller-held values remain immutable and retain their separate memory charge. When activity statistics are enabled, mutations with no eviction also avoid a shared zero-value counter update. diff --git a/cache2/src/region/index/storage/mod.rs b/cache2/src/region/index/storage/mod.rs index 6a4e059..148ee6f 100644 --- a/cache2/src/region/index/storage/mod.rs +++ b/cache2/src/region/index/storage/mod.rs @@ -67,11 +67,12 @@ pub mod page_format; /// does not issue one positioned syscall per page. pub const WARM_IMAGE_WRITE_BATCH_BYTES: usize = 1024 * 1024; +// Page states track source-image validation. Partition locks publish runtime +// mutations, and warm close regenerates every page and its checksum. const PAGE_STATE_UNCHECKED: u8 = 0; const PAGE_STATE_VALIDATING: u8 = 1; const PAGE_STATE_VALID: u8 = 2; -const PAGE_STATE_DIRTY: u8 = 3; -const PAGE_STATE_REJECTED: u8 = 4; +const PAGE_STATE_REJECTED: u8 = 3; const IMAGE_STATE_USABLE: u8 = 0; const IMAGE_STATE_REJECTED: u8 = 1; @@ -443,7 +444,6 @@ pub enum PageValidationState { Unchecked, Validating, Valid, - Dirty, Rejected, } @@ -947,7 +947,6 @@ impl IndexStorageCore { PAGE_STATE_UNCHECKED => PageValidationState::Unchecked, PAGE_STATE_VALIDATING => PageValidationState::Validating, PAGE_STATE_VALID => PageValidationState::Valid, - PAGE_STATE_DIRTY => PageValidationState::Dirty, PAGE_STATE_REJECTED => PageValidationState::Rejected, _ => PageValidationState::Rejected, }) @@ -978,7 +977,6 @@ impl IndexStorageCore { INDEX_IMAGE_SLOT_SIZE, ); } - self.page_states[page].store(PAGE_STATE_DIRTY, Ordering::Release); self.ensure_image_usable(page) } @@ -1009,7 +1007,7 @@ impl IndexStorageCore { loop { self.ensure_image_usable(page)?; return match state.load(Ordering::Acquire) { - PAGE_STATE_VALID | PAGE_STATE_DIRTY => Ok(()), + PAGE_STATE_VALID => Ok(()), PAGE_STATE_REJECTED => Err(IndexStorageError::CorruptPage { page_index: page, reason: CorruptPageReason::PreviouslyRejected, From 4e0916ffb067b993abd132c67c8020f1180a8007 Mon Sep 17 00:00:00 2001 From: leiysky Date: Wed, 28 Oct 2026 17:20:07 +0800 Subject: [PATCH 2/4] perf: skip unchanged managed memory peak updates --- CHANGELOG.md | 1 + cache2/src/managed_memory.rs | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef3e08b..1a03fb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Improvements +- Transient L2 reads avoid a redundant atomic peak update when their memory reservation does not exceed the recorded high-water mark. - L2 index updates and cold-record reclamation avoid redundant atomic writes for already-clear heat bits and validated index pages. - Hot-record reclamation reuses the checksum computed during payload validation for its rewrite, avoiding a second payload scan while preserving record validation and the on-disk format. - L1 replacement and eviction reuse detached, exclusively owned allocations of the same size under CLOCK and S3-FIFO, reducing repeated allocation during fixed-size turnover without an additional buffer pool. Caller-held values remain immutable and retain their separate memory charge. When activity statistics are enabled, mutations with no eviction also avoid a shared zero-value counter update. diff --git a/cache2/src/managed_memory.rs b/cache2/src/managed_memory.rs index 3b3dab9..d3dfb12 100644 --- a/cache2/src/managed_memory.rs +++ b/cache2/src/managed_memory.rs @@ -399,7 +399,11 @@ fn align_up(value: usize, alignment: usize) -> Option { } fn update_peak(peak: &AtomicUsize, candidate: usize) { - peak.fetch_max(candidate, Ordering::Relaxed); + // The peak only grows; a stale lower observation can cause an extra RMW, + // but cannot suppress a new maximum. + if candidate > peak.load(Ordering::Relaxed) { + peak.fetch_max(candidate, Ordering::Relaxed); + } } const MAX_ATOMIC_UPDATE_ATTEMPTS: usize = 8; From 63cf85bbe90a291d2d80158919438b0dec21479b Mon Sep 17 00:00:00 2001 From: leiysky Date: Wed, 28 Oct 2026 17:22:03 +0800 Subject: [PATCH 3/4] perf: bypass L1 work when entry capacity is zero --- CHANGELOG.md | 1 + cache2/src/cache/runtime/mod.rs | 3 +++ cache2/src/memory/mod.rs | 14 ++++++++++++++ 3 files changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a03fb7..ae8a36b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Improvements +- A zero-capacity L1 bypasses shard locking and directory work for lookups, fills, promotions, and deletes. - Transient L2 reads avoid a redundant atomic peak update when their memory reservation does not exceed the recorded high-water mark. - L2 index updates and cold-record reclamation avoid redundant atomic writes for already-clear heat bits and validated index pages. - Hot-record reclamation reuses the checksum computed during payload validation for its rewrite, avoiding a second payload scan while preserving record validation and the on-disk format. diff --git a/cache2/src/cache/runtime/mod.rs b/cache2/src/cache/runtime/mod.rs index e0afefa..7b38b39 100644 --- a/cache2/src/cache/runtime/mod.rs +++ b/cache2/src/cache/runtime/mod.rs @@ -2506,6 +2506,9 @@ mod tests { store.close_warm().unwrap(); std::fs::remove_dir_all(path).unwrap(); assert_eq!(snapshot.l1_hits, 0); + if l1_capacity == 0 { + assert_eq!(snapshot.l1_bypasses, 2); + } assert_eq!(snapshot.l2_hits, u64::from(activity_counters)); assert_eq!( snapshot.served_bytes, diff --git a/cache2/src/memory/mod.rs b/cache2/src/memory/mod.rs index 8132f15..2309a3c 100644 --- a/cache2/src/memory/mod.rs +++ b/cache2/src/memory/mod.rs @@ -921,6 +921,10 @@ impl MemoryStore { } pub fn publish(&self, hash: u64, key: &[u8], value: &[u8], seqno: u64) -> bool { + if self.entry_capacity == 0 { + self.record_insert(MemoryInsertResult::bypassed()); + return false; + } let Some(charged_bytes) = memory_entry_bytes(key, value) else { self.record_insert(MemoryInsertResult::bypassed()); return false; @@ -955,6 +959,9 @@ impl MemoryStore { /// Best-effort exact-key cleanup for a sequenced delete. Contention /// bypasses L1, and a newer resident value is never removed. pub fn delete(&self, hash: u64, key: &[u8], seqno: u64) -> bool { + if self.entry_capacity == 0 { + return true; + } let shard_id = self.route(hash); let Some(mut shard) = self.try_lock_shard(shard_id) else { self.record_insert(MemoryInsertResult::bypassed()); @@ -975,6 +982,9 @@ impl MemoryStore { pub fn lookup(&self, hash: u64, key: &[u8]) -> MemoryLookup { let shard_id = self.route(hash); + if self.entry_capacity == 0 { + return MemoryLookup::Miss(MemoryReadToken { shard_id }); + } let mut attempts = 1; let mut shard = loop { match self.shards[shard_id].try_lock() { @@ -1010,6 +1020,10 @@ impl MemoryStore { if token.shard_id != self.route(hash) { return None; } + if self.entry_capacity == 0 { + self.record_insert(MemoryInsertResult::bypassed()); + return None; + } let Some(charged_bytes) = memory_entry_bytes(key, value) else { self.record_insert(MemoryInsertResult::bypassed()); return None; From be1591c5c02d853df0339d4c70ef41ce66845f9a Mon Sep 17 00:00:00 2001 From: leiysky Date: Wed, 28 Oct 2026 17:24:45 +0800 Subject: [PATCH 4/4] perf: skip net-zero L1 charge counter updates --- CHANGELOG.md | 2 +- cache2/src/memory/mod.rs | 31 ++++++++++++++++++------------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae8a36b..3a3aae8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ ### Improvements - A zero-capacity L1 bypasses shard locking and directory work for lookups, fills, promotions, and deletes. -- Transient L2 reads avoid a redundant atomic peak update when their memory reservation does not exceed the recorded high-water mark. +- Memory accounting skips redundant atomic updates when transient reads stay within the recorded peak or L1 transfers leave the charged byte total unchanged. - L2 index updates and cold-record reclamation avoid redundant atomic writes for already-clear heat bits and validated index pages. - Hot-record reclamation reuses the checksum computed during payload validation for its rewrite, avoiding a second payload scan while preserving record validation and the on-disk format. - L1 replacement and eviction reuse detached, exclusively owned allocations of the same size under CLOCK and S3-FIFO, reducing repeated allocation during fixed-size turnover without an additional buffer pool. Caller-held values remain immutable and retain their separate memory charge. When activity statistics are enabled, mutations with no eviction also avoid a shared zero-value counter update. diff --git a/cache2/src/memory/mod.rs b/cache2/src/memory/mod.rs index 2309a3c..fcaefb5 100644 --- a/cache2/src/memory/mod.rs +++ b/cache2/src/memory/mod.rs @@ -116,9 +116,9 @@ impl MemoryBudget { .saturating_sub(self.used_bytes.load(Ordering::Acquire)) } - /// Atomically transfers exclusive victim charges to one candidate before - /// any resident entry is removed. A failed CAS leaves both accounting and - /// the resident set unchanged. + /// Transfers exclusive victim charges to one candidate before any resident + /// entry is removed. Net byte changes use CAS; failure leaves both + /// accounting and the resident set unchanged. fn try_transfer_charge( self: &Arc, released: usize, @@ -135,18 +135,23 @@ impl MemoryBudget { if next > self.capacity_bytes { return MemoryChargeAttempt::Full; } - match self - .used_bytes - .compare_exchange(used, next, Ordering::AcqRel, Ordering::Acquire) + // Exclusive victims keep these bytes charged until the caller + // disarms them. A net-zero transfer needs no counter update. + if next != used + && let Err(observed) = self.used_bytes.compare_exchange( + used, + next, + Ordering::AcqRel, + Ordering::Acquire, + ) { - Ok(_) => { - return MemoryChargeAttempt::Charged(MemoryCharge { - budget: Some(Arc::clone(self)), - bytes: required, - }); - } - Err(observed) => used = observed, + used = observed; + continue; } + return MemoryChargeAttempt::Charged(MemoryCharge { + budget: Some(Arc::clone(self)), + bytes: required, + }); } MemoryChargeAttempt::Contended }