From b5104fcb0ce3bf566b42cfe7a2412fa0104bc7ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=E7=89=B9=E6=AF=8F=E7=BE=8A=E9=98=9F?= Date: Tue, 30 Jun 2026 23:28:35 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20Shape=20=E5=8A=A0=20depth=20=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=20+=20positions=20=E5=8F=8D=E5=90=91=E7=B4=A2?= =?UTF-8?q?=E5=BC=95=EF=BC=8Clookup=5Fposition=20=E9=A6=96=E6=AC=A1?= =?UTF-8?q?=E5=86=99=E5=85=A5=E7=BC=93=E5=AD=98=E5=90=8E=20O(1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_kernel/src/shape_forge.rs | 97 ++++++++++++++++---------- 1 file changed, 61 insertions(+), 36 deletions(-) diff --git a/crates/oxide_kernel/src/shape_forge.rs b/crates/oxide_kernel/src/shape_forge.rs index 5756c5c..4e5b6d7 100644 --- a/crates/oxide_kernel/src/shape_forge.rs +++ b/crates/oxide_kernel/src/shape_forge.rs @@ -17,6 +17,8 @@ pub struct Shape { pub id: ShapeId, pub property_name: StringIndex, pub parent: Option, + /// Number of non-sentinel properties from the root to this shape. + pub depth: u32, } /// Shared hidden-class store. @@ -24,11 +26,13 @@ pub struct Shape { /// Concurrency model: /// - `shapes` is append-mostly behind an `RwLock`. /// - `transitions` is a sharded `DashMap` keyed by `(parent_shape, property_name)`. +/// - `positions` caches `(shape_id, prop_name) → slot` for O(1) repeat lookups. /// - Shard count is fixed for reproducible contention behavior across machines instead /// of relying on DashMap's CPU-dependent default. pub struct ShapeForge { shapes: RwLock>>>, transitions: DashMap, + positions: DashMap, next_id: AtomicU32, overflow_map: RwLock>, overflow_active: AtomicBool, @@ -39,6 +43,7 @@ impl ShapeForge { let forge = Self { shapes: RwLock::new(Vec::with_capacity(256)), transitions: DashMap::with_capacity_and_shard_amount(256, 16), + positions: DashMap::with_capacity_and_shard_amount(256, 16), next_id: AtomicU32::new(2), overflow_map: RwLock::new(HashMap::new()), overflow_active: AtomicBool::new(false), @@ -49,6 +54,7 @@ impl ShapeForge { id: EMPTY_SHAPE_ID, property_name: EMPTY_SENTINEL, parent: None, + depth: 0, }); debug_assert_eq!(shapes.len(), 0); shapes.push(Some(empty)); @@ -60,26 +66,6 @@ impl ShapeForge { ((parent_id as u64) << 32) | (prop_name as u64) } - fn compute_depth(shape_id: ShapeId, shapes: &[Option>]) -> u32 { - if shape_id == 0 { - return 0; - } - let mut count = 0u32; - let mut cursor = Some(shape_id); - while let Some(id) = cursor { - match shapes.get((id - 1) as usize).and_then(|s| s.clone()) { - Some(s) => { - if s.property_name != EMPTY_SENTINEL { - count += 1; - } - cursor = s.parent; - } - None => break, - } - } - count - } - pub fn make_shape(&self, parent_id: ShapeId, prop_name: StringIndex) -> ShapeId { let key = Self::pack_key(parent_id, prop_name); @@ -111,10 +97,21 @@ impl ShapeForge { return new_id; } + // Compute depth from parent (O(1) — just read the cached value) + let parent_depth = { + let shapes = self.shapes.read().unwrap(); + shapes + .get((parent_id - 1) as usize) + .and_then(|s| s.as_ref()) + .map(|s| s.depth) + .unwrap_or(0) + }; + let shape = Arc::new(Shape { id: new_id, property_name: prop_name, parent: Some(parent_id), + depth: parent_depth + 1, }); { @@ -154,12 +151,19 @@ impl ShapeForge { shapes.truncate(1); } self.transitions.clear(); + self.positions.clear(); self.next_id.store(2, Ordering::Relaxed); } pub fn lookup_position(&self, shape_id: ShapeId, prop_name: StringIndex) -> Option { + let cache_key = Self::pack_key(shape_id, prop_name); + if let Some(pos) = self.positions.get(&cache_key) { + return Some(*pos); + } + let shapes = self.shapes.read().unwrap(); - let total_depth = Self::compute_depth(shape_id, &shapes); + let root = shapes.get((shape_id - 1) as usize)?.as_ref()?; + let total_depth = root.depth; let mut prop_steps: u32 = 0; let mut cursor = Some(shape_id); while let Some(id) = cursor { @@ -167,7 +171,10 @@ impl ShapeForge { Some(s) => { if s.property_name != EMPTY_SENTINEL { if s.property_name == prop_name { - return total_depth.checked_sub(prop_steps + 1); + let pos = total_depth.checked_sub(prop_steps + 1)?; + drop(shapes); + self.positions.insert(cache_key, pos); + return Some(pos); } prop_steps += 1; } @@ -198,20 +205,11 @@ impl ShapeForge { pub fn shape_prop_count(&self, shape_id: ShapeId) -> u32 { let shapes = self.shapes.read().unwrap(); - let mut count = 0u32; - let mut cursor = Some(shape_id); - while let Some(id) = cursor { - match shapes.get((id - 1) as usize).and_then(|s| s.clone()) { - Some(s) => { - if s.property_name != EMPTY_SENTINEL { - count += 1; - } - cursor = s.parent; - } - None => break, - } - } - count + shapes + .get((shape_id - 1) as usize) + .and_then(|s| s.as_ref()) + .map(|s| s.depth) + .unwrap_or(0) } } @@ -234,6 +232,7 @@ mod tests { let s = s.unwrap(); assert_eq!(s.id, EMPTY_SHAPE_ID); assert!(s.parent.is_none()); + assert_eq!(s.depth, 0); } #[test] @@ -245,6 +244,7 @@ mod tests { let shape = forge.get_shape(s1).unwrap(); assert_eq!(shape.property_name, key); assert_eq!(shape.parent, Some(EMPTY_SHAPE_ID)); + assert_eq!(shape.depth, 1); } #[test] @@ -279,6 +279,31 @@ mod tests { assert_eq!(forge.lookup_position(s3, 99), None); } + #[test] + fn lookup_position_cached_second_call() { + let forge = ShapeForge::new(); + let s1 = forge.make_shape(EMPTY_SHAPE_ID, 1_000_010); + let s2 = forge.make_shape(s1, 1_000_020); + let s3 = forge.make_shape(s2, 1_000_030); + + // First call populates cache + assert_eq!(forge.lookup_position(s3, 1_000_020), Some(1)); + // Second call hits cache (verify no regression) + assert_eq!(forge.lookup_position(s3, 1_000_020), Some(1)); + assert_eq!(forge.lookup_position(s3, 1_000_030), Some(2)); + assert_eq!(forge.lookup_position(s3, 1_000_030), Some(2)); + } + + #[test] + fn clear_transient_clears_positions() { + let forge = ShapeForge::new(); + let s1 = forge.make_shape(EMPTY_SHAPE_ID, 1_000_010); + assert_eq!(forge.lookup_position(s1, 1_000_010), Some(0)); + forge.clear_transient(); + // After clear, EMPTY_SHAPE remains but others are gone + assert_eq!(forge.shapes.read().unwrap().len(), 1); + } + #[test] fn two_branches_share_ancestor() { let forge = ShapeForge::new();