pjson_rs/memory/
arena.rs

1//! Arena-based memory allocation for high-performance JSON parsing
2//!
3//! Arena allocators provide excellent performance for scenarios where many objects
4//! are allocated together and freed all at once, which is perfect for JSON parsing.
5
6use std::{cell::RefCell, collections::HashMap};
7use typed_arena::Arena;
8
9/// Arena-backed string pool for zero-copy string operations
10pub struct StringArena {
11    arena: Arena<String>,
12    interned: RefCell<HashMap<&'static str, &'static str>>,
13}
14
15impl StringArena {
16    /// Create new string arena
17    pub fn new() -> Self {
18        Self {
19            arena: Arena::new(),
20            interned: RefCell::new(HashMap::new()),
21        }
22    }
23
24    /// Allocate string in arena and return reference with arena lifetime
25    pub fn alloc_str(&self, s: String) -> &str {
26        self.arena.alloc(s).as_str()
27    }
28
29    /// Intern string to avoid duplicates (useful for JSON keys)
30    pub fn intern(&self, s: &str) -> &str {
31        // For demonstration - in production you'd use a more sophisticated interning strategy
32        if let Some(&interned) = self.interned.borrow().get(s) {
33            return interned;
34        }
35
36        let allocated = self.alloc_str(s.to_string());
37        // This is unsafe in real usage - just for demonstration
38        let static_ref: &'static str = unsafe { std::mem::transmute(allocated) };
39        self.interned.borrow_mut().insert(static_ref, static_ref);
40        static_ref
41    }
42
43    /// Get current memory usage statistics
44    pub fn memory_usage(&self) -> ArenaStats {
45        // typed_arena doesn't expose internal stats, so we estimate
46        ArenaStats {
47            chunks_allocated: 1,  // Simplified
48            total_bytes: 0,       // Would need arena introspection
49            strings_allocated: 0, // Would need counting
50        }
51    }
52}
53
54impl Default for StringArena {
55    fn default() -> Self {
56        Self::new()
57    }
58}
59
60/// Arena for JSON value allocations
61pub struct ValueArena<T> {
62    arena: Arena<T>,
63    allocated_count: RefCell<usize>,
64}
65
66impl<T> ValueArena<T> {
67    /// Create new value arena
68    pub fn new() -> Self {
69        Self {
70            arena: Arena::new(),
71            allocated_count: RefCell::new(0),
72        }
73    }
74
75    /// Allocate value in arena
76    pub fn alloc(&self, value: T) -> &mut T {
77        *self.allocated_count.borrow_mut() += 1;
78        self.arena.alloc(value)
79    }
80
81    /// Allocate multiple values
82    pub fn alloc_extend<I: IntoIterator<Item = T>>(&self, iter: I) -> &mut [T] {
83        let values: Vec<T> = iter.into_iter().collect();
84        *self.allocated_count.borrow_mut() += values.len();
85        self.arena.alloc_extend(values)
86    }
87
88    /// Get count of allocated objects
89    pub fn allocated_count(&self) -> usize {
90        *self.allocated_count.borrow()
91    }
92}
93
94impl<T> Default for ValueArena<T> {
95    fn default() -> Self {
96        Self::new()
97    }
98}
99
100/// Memory usage statistics for arena
101#[derive(Debug, Clone)]
102pub struct ArenaStats {
103    pub chunks_allocated: usize,
104    pub total_bytes: usize,
105    pub strings_allocated: usize,
106}
107
108/// Combined arena allocator for JSON parsing
109pub struct JsonArena {
110    /// Arena for string allocations
111    pub strings: StringArena,
112    /// Arena for object allocations  
113    pub objects: ValueArena<serde_json::Map<String, serde_json::Value>>,
114    /// Arena for array allocations
115    pub arrays: ValueArena<Vec<serde_json::Value>>,
116    /// Arena for generic values
117    pub values: ValueArena<serde_json::Value>,
118}
119
120impl JsonArena {
121    /// Create new JSON arena with all allocators
122    pub fn new() -> Self {
123        Self {
124            strings: StringArena::new(),
125            objects: ValueArena::new(),
126            arrays: ValueArena::new(),
127            values: ValueArena::new(),
128        }
129    }
130
131    /// Get combined memory usage statistics
132    pub fn stats(&self) -> CombinedArenaStats {
133        CombinedArenaStats {
134            string_stats: self.strings.memory_usage(),
135            objects_allocated: self.objects.allocated_count(),
136            arrays_allocated: self.arrays.allocated_count(),
137            values_allocated: self.values.allocated_count(),
138        }
139    }
140
141    /// Reset all arenas (drops all allocated memory)
142    pub fn reset(&mut self) {
143        // Drop and recreate arenas to free memory
144        *self = Self::new();
145    }
146}
147
148impl Default for JsonArena {
149    fn default() -> Self {
150        Self::new()
151    }
152}
153
154/// Combined arena statistics
155#[derive(Debug, Clone)]
156pub struct CombinedArenaStats {
157    pub string_stats: ArenaStats,
158    pub objects_allocated: usize,
159    pub arrays_allocated: usize,
160    pub values_allocated: usize,
161}
162
163/// Arena-aware JSON parser for high-performance parsing
164pub struct ArenaJsonParser {
165    arena: JsonArena,
166    reuse_threshold: usize,
167}
168
169impl ArenaJsonParser {
170    /// Create new arena-backed parser
171    pub fn new() -> Self {
172        Self {
173            arena: JsonArena::new(),
174            reuse_threshold: 1000, // Reset arena after 1000 allocations
175        }
176    }
177
178    /// Parse JSON using arena allocation
179    pub fn parse(&mut self, json: &str) -> Result<serde_json::Value, serde_json::Error> {
180        // Check if we should reset arena to prevent unbounded growth
181        let stats = self.arena.stats();
182        if stats.values_allocated > self.reuse_threshold {
183            self.arena.reset();
184        }
185
186        // For now, fall back to standard parsing
187        // In a full implementation, you'd integrate arena allocation with the parser
188        serde_json::from_str(json)
189    }
190
191    /// Get current arena statistics
192    pub fn arena_stats(&self) -> CombinedArenaStats {
193        self.arena.stats()
194    }
195}
196
197impl Default for ArenaJsonParser {
198    fn default() -> Self {
199        Self::new()
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206
207    #[test]
208    fn test_string_arena_basic() {
209        let arena = StringArena::new();
210        let s1 = arena.alloc_str("hello".to_string());
211        let s2 = arena.alloc_str("world".to_string());
212
213        assert_eq!(s1, "hello");
214        assert_eq!(s2, "world");
215    }
216
217    #[test]
218    fn test_value_arena_counting() {
219        let arena = ValueArena::new();
220        assert_eq!(arena.allocated_count(), 0);
221
222        arena.alloc(42);
223        assert_eq!(arena.allocated_count(), 1);
224
225        arena.alloc_extend([1, 2, 3]);
226        assert_eq!(arena.allocated_count(), 4);
227    }
228
229    #[test]
230    fn test_json_arena_stats() {
231        let arena = JsonArena::new();
232        let stats = arena.stats();
233
234        // Initial stats should show zero allocations
235        assert_eq!(stats.objects_allocated, 0);
236        assert_eq!(stats.arrays_allocated, 0);
237        assert_eq!(stats.values_allocated, 0);
238    }
239
240    #[test]
241    fn test_arena_parser() {
242        let mut parser = ArenaJsonParser::new();
243        let result = parser.parse(r#"{"key": "value"}"#);
244
245        assert!(result.is_ok());
246        let value = result.unwrap();
247        assert!(value.is_object());
248    }
249
250    #[test]
251    fn test_arena_reset() {
252        let mut arena = JsonArena::new();
253
254        // Allocate some values
255        arena.values.alloc(serde_json::Value::Null);
256        arena.objects.alloc(serde_json::Map::new());
257
258        let initial_stats = arena.stats();
259        assert!(initial_stats.values_allocated > 0);
260
261        // Reset should clear everything
262        arena.reset();
263        let reset_stats = arena.stats();
264        assert_eq!(reset_stats.values_allocated, 0);
265        assert_eq!(reset_stats.objects_allocated, 0);
266    }
267}