memscope_rs/core/
clone_utils.rs

1//! Utilities for optimizing clone operations
2//!
3//! This module provides simple utility functions to help optimize clone
4//! operations by using Arc sharing where appropriate.
5
6use crate::core::clone_monitor;
7use crate::core::optimized_types::OptimizedAllocationInfo;
8use crate::core::types::AllocationInfo;
9use std::sync::Arc;
10
11/// Create an Arc-shared version of AllocationInfo
12pub fn share_allocation_info(info: AllocationInfo) -> Arc<OptimizedAllocationInfo> {
13    let optimized = OptimizedAllocationInfo::from(info);
14    let size = std::mem::size_of::<OptimizedAllocationInfo>();
15
16    clone_monitor::record_optimized_clone("AllocationInfo", size);
17    Arc::new(optimized)
18}
19
20/// Clone an Arc-shared AllocationInfo (cheap operation)
21pub fn clone_shared_allocation(
22    arc_info: &Arc<OptimizedAllocationInfo>,
23) -> Arc<OptimizedAllocationInfo> {
24    let size = std::mem::size_of::<OptimizedAllocationInfo>();
25    clone_monitor::record_avoided_clone("AllocationInfo", size);
26    Arc::clone(arc_info)
27}
28
29/// Convert Arc-shared back to regular AllocationInfo when needed
30pub fn unshare_allocation_info(arc_info: Arc<OptimizedAllocationInfo>) -> AllocationInfo {
31    // Try to avoid cloning if we're the only reference
32    match Arc::try_unwrap(arc_info) {
33        Ok(optimized) => optimized.into(),
34        Err(arc_info) => (*arc_info).clone().into(),
35    }
36}
37
38/// Create a shared vector of allocations
39pub fn share_allocation_vector(
40    infos: Vec<AllocationInfo>,
41) -> Arc<Vec<Arc<OptimizedAllocationInfo>>> {
42    let shared_infos: Vec<Arc<OptimizedAllocationInfo>> =
43        infos.into_iter().map(share_allocation_info).collect();
44
45    Arc::new(shared_infos)
46}
47
48/// Check if a type should use Arc sharing based on size and usage patterns
49pub fn should_use_arc_sharing(type_name: &str, size: usize) -> bool {
50    // Use Arc for large objects or frequently cloned types
51    size > 1024
52        || type_name.contains("AllocationInfo")
53        || type_name.contains("Config")
54        || type_name.contains("Result")
55        || type_name.contains("Collection")
56}
57
58/// Optimize a clone operation by choosing between regular clone and Arc sharing
59pub fn optimized_clone<T>(value: &T) -> T
60where
61    T: Clone + 'static,
62{
63    let type_name = std::any::type_name::<T>();
64    let size = std::mem::size_of::<T>();
65
66    if should_use_arc_sharing(type_name, size) {
67        // For types that benefit from Arc sharing, we would need to restructure
68        // the calling code. For now, just record the clone and suggest optimization.
69        clone_monitor::record_clone(type_name, size, 0);
70        value.clone()
71    } else {
72        // Regular clone for small/simple types
73        let start = std::time::Instant::now();
74        let result = value.clone();
75        let duration = start.elapsed().as_nanos() as u64;
76        clone_monitor::record_clone(type_name, size, duration);
77        result
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use crate::core::types::AllocationInfo;
85
86    /// Helper function to create a test AllocationInfo
87    fn create_test_allocation_info(ptr: usize, size: usize) -> AllocationInfo {
88        AllocationInfo {
89            ptr,
90            size,
91            var_name: Some(format!("test_var_{ptr}")),
92            type_name: Some("TestType".to_string()),
93            scope_name: None,
94            timestamp_alloc: 1000000,
95            timestamp_dealloc: None,
96            thread_id: "test_thread".to_string(),
97            borrow_count: 0,
98            stack_trace: None,
99            is_leaked: false,
100            lifetime_ms: None,
101            borrow_info: None,
102            clone_info: None,
103            ownership_history_available: false,
104            smart_pointer_info: None,
105            memory_layout: None,
106            generic_info: None,
107            dynamic_type_info: None,
108            runtime_state: None,
109            stack_allocation: None,
110            temporary_object: None,
111            fragmentation_analysis: None,
112            generic_instantiation: None,
113            type_relationships: None,
114            type_usage: None,
115            function_call_tracking: None,
116            lifecycle_tracking: None,
117            access_tracking: None,
118            drop_chain_analysis: None,
119        }
120    }
121
122    #[test]
123    fn test_share_allocation_info() {
124        // Test converting AllocationInfo to Arc-shared OptimizedAllocationInfo
125        let info = create_test_allocation_info(0x1000, 1024);
126        let shared = share_allocation_info(info);
127
128        assert_eq!(shared.ptr, 0x1000);
129        assert_eq!(shared.size, 1024);
130        assert_eq!(
131            shared.var_name.as_ref().map(|s| s.as_ref()),
132            Some("test_var_4096")
133        );
134    }
135
136    #[test]
137    fn test_clone_shared_allocation() {
138        // Test that cloning an Arc-shared allocation is cheap
139        let info = create_test_allocation_info(0x2000, 2048);
140        let shared = share_allocation_info(info);
141
142        // Clone the shared allocation
143        let cloned = clone_shared_allocation(&shared);
144
145        // Both should point to the same data
146        assert_eq!(Arc::strong_count(&shared), 2);
147        assert_eq!(shared.ptr, cloned.ptr);
148        assert_eq!(shared.size, cloned.size);
149    }
150
151    #[test]
152    fn test_unshare_allocation_info_single_reference() {
153        // Test converting Arc-shared back to regular AllocationInfo when single reference
154        let info = create_test_allocation_info(0x3000, 512);
155        let original_ptr = info.ptr;
156        let original_size = info.size;
157
158        let shared = share_allocation_info(info);
159        assert_eq!(Arc::strong_count(&shared), 1);
160
161        let unshared = unshare_allocation_info(shared);
162        assert_eq!(unshared.ptr, original_ptr);
163        assert_eq!(unshared.size, original_size);
164    }
165
166    #[test]
167    fn test_unshare_allocation_info_multiple_references() {
168        // Test converting Arc-shared back when multiple references exist
169        let info = create_test_allocation_info(0x4000, 256);
170        let shared = share_allocation_info(info);
171        let _cloned = Arc::clone(&shared);
172
173        assert_eq!(Arc::strong_count(&shared), 2);
174
175        // This should clone the data since there are multiple references
176        let unshared = unshare_allocation_info(shared);
177        assert_eq!(unshared.ptr, 0x4000);
178        assert_eq!(unshared.size, 256);
179    }
180
181    #[test]
182    fn test_share_allocation_vector() {
183        // Test creating a shared vector of allocations
184        let infos = vec![
185            create_test_allocation_info(0x5000, 128),
186            create_test_allocation_info(0x6000, 256),
187            create_test_allocation_info(0x7000, 512),
188        ];
189
190        let shared_vec = share_allocation_vector(infos);
191
192        assert_eq!(shared_vec.len(), 3);
193        assert_eq!(shared_vec[0].ptr, 0x5000);
194        assert_eq!(shared_vec[1].ptr, 0x6000);
195        assert_eq!(shared_vec[2].ptr, 0x7000);
196    }
197
198    #[test]
199    fn test_should_use_arc_sharing_by_size() {
200        // Test Arc sharing decision based on size
201        assert!(should_use_arc_sharing("SomeType", 2048)); // Large size
202        assert!(!should_use_arc_sharing("SomeType", 512)); // Small size
203        assert!(!should_use_arc_sharing("TinyType", 64)); // Very small size
204    }
205
206    #[test]
207    fn test_should_use_arc_sharing_by_type_name() {
208        // Test Arc sharing decision based on type name
209        assert!(should_use_arc_sharing("AllocationInfo", 100));
210        assert!(should_use_arc_sharing("SomeConfig", 100));
211        assert!(should_use_arc_sharing("QueryResult", 100));
212        assert!(should_use_arc_sharing("DataCollection", 100));
213
214        // Should not use Arc for small simple types even if size is moderate
215        assert!(!should_use_arc_sharing("SimpleStruct", 100));
216    }
217
218    #[test]
219    fn test_optimized_clone_small_type() {
220        // Test optimized clone for small types
221        let small_value = 42u32;
222        let cloned = optimized_clone(&small_value);
223        assert_eq!(cloned, 42);
224    }
225
226    #[test]
227    fn test_optimized_clone_string() {
228        // Test optimized clone for String type
229        let test_string = "Test string for cloning".to_string();
230        let cloned = optimized_clone(&test_string);
231        assert_eq!(cloned, test_string);
232    }
233
234    #[test]
235    fn test_optimized_clone_vector() {
236        // Test optimized clone for Vector type
237        let test_vec = vec![1, 2, 3, 4, 5];
238        let cloned = optimized_clone(&test_vec);
239        assert_eq!(cloned, test_vec);
240    }
241
242    #[test]
243    fn test_arc_reference_counting() {
244        // Test that Arc reference counting works correctly
245        let info = create_test_allocation_info(0x8000, 1024);
246        let shared1 = share_allocation_info(info);
247
248        assert_eq!(Arc::strong_count(&shared1), 1);
249
250        let shared2 = clone_shared_allocation(&shared1);
251        assert_eq!(Arc::strong_count(&shared1), 2);
252        assert_eq!(Arc::strong_count(&shared2), 2);
253
254        let shared3 = clone_shared_allocation(&shared2);
255        assert_eq!(Arc::strong_count(&shared1), 3);
256        assert_eq!(Arc::strong_count(&shared2), 3);
257        assert_eq!(Arc::strong_count(&shared3), 3);
258
259        drop(shared3);
260        assert_eq!(Arc::strong_count(&shared1), 2);
261
262        drop(shared2);
263        assert_eq!(Arc::strong_count(&shared1), 1);
264    }
265}