mathhook_core/parser/cache.rs
1//! Thread-local caching for parser performance optimization
2//!
3//! This module provides thread-local storage for expensive parser operations
4//! to avoid repeated allocations and computations. Based on Rust Performance
5//! Book recommendations for high-performance parsing.
6
7use crate::core::Expression;
8use std::cell::RefCell;
9use std::collections::HashMap;
10use std::thread_local;
11
12thread_local! {
13 /// Cache for parsed function names to avoid repeated string allocations
14 ///
15 /// This cache stores constructed function names like "bessel_j_indexed"
16 /// to avoid repeated format! calls during parsing.
17 static FUNCTION_NAME_CACHE: RefCell<HashMap<String, String>> = RefCell::new(HashMap::new());
18
19 /// Pre-allocated Vec for expression lists to avoid repeated allocations
20 ///
21 /// This buffer is reused for building Vec<Expression> during parsing
22 /// to minimize heap allocations.
23 static EXPR_LIST_BUFFER: RefCell<Vec<Expression>> = RefCell::new(Vec::with_capacity(16));
24
25 /// Cache for commonly used Expression instances
26 ///
27 /// Stores frequently used expressions like constants and simple operations
28 /// to avoid repeated construction.
29 static COMMON_EXPRESSIONS: RefCell<HashMap<&'static str, Expression>> = RefCell::new({
30 let mut map = HashMap::new();
31 map.insert("0", Expression::integer(0));
32 map.insert("1", Expression::integer(1));
33 map.insert("-1", Expression::integer(-1));
34 map.insert("2", Expression::integer(2));
35 map.insert("pi", Expression::pi());
36 map.insert("e", Expression::e());
37 map.insert("i", Expression::i());
38 map.insert("infinity", Expression::infinity());
39 map
40 });
41}
42
43/// Efficient function name construction with caching
44///
45/// Constructs function names like "bessel_j_indexed" and caches them
46/// to avoid repeated string allocations.
47///
48/// # Examples
49///
50/// ```rust
51/// use mathhook_core::parser::cache::get_cached_function_name;
52///
53/// let name1 = get_cached_function_name("bessel", "j_indexed");
54/// let name2 = get_cached_function_name("bessel", "j_indexed");
55/// // Second call reuses cached result
56/// ```
57///
58/// # Performance
59///
60/// - First call: O(n) string construction + HashMap insertion
61/// - Subsequent calls: O(1) HashMap lookup + clone
62pub fn get_cached_function_name(base: &str, suffix: &str) -> String {
63 FUNCTION_NAME_CACHE.with(|cache| {
64 let key = format!("{}_{}", base, suffix);
65 let mut cache = cache.borrow_mut();
66
67 cache.entry(key.clone()).or_insert_with(|| key).clone()
68 })
69}
70
71/// Reuse Vec allocations for expression lists
72///
73/// Builds a `Vec<Expression>` using a thread-local buffer to minimize
74/// heap allocations during parsing.
75///
76/// # Examples
77///
78/// ```rust
79/// use mathhook_core::parser::cache::build_expr_list;
80/// use mathhook_core::Expression;
81///
82/// let exprs = vec![Expression::integer(1), Expression::integer(2)];
83/// let result = build_expr_list(exprs);
84/// ```
85///
86/// # Performance
87///
88/// - Reuses pre-allocated Vec capacity
89/// - Single clone operation instead of multiple allocations
90/// - Thread-local storage avoids synchronization overhead
91pub fn build_expr_list(exprs: impl IntoIterator<Item = Expression>) -> Vec<Expression> {
92 EXPR_LIST_BUFFER.with(|buffer| {
93 let mut buffer = buffer.borrow_mut();
94 buffer.clear();
95 buffer.extend(exprs);
96 buffer.clone() // Clone the populated buffer
97 })
98}
99
100/// Get a commonly used expression from cache
101///
102/// Returns cached instances of frequently used expressions to avoid
103/// repeated construction.
104///
105/// # Examples
106///
107/// ```rust
108/// use mathhook_core::parser::cache::get_cached_expression;
109///
110/// let zero = get_cached_expression("0");
111/// let pi = get_cached_expression("pi");
112/// ```
113///
114/// # Performance
115///
116/// - O(1) HashMap lookup for cached expressions
117/// - Avoids repeated Expression construction for constants
118pub fn get_cached_expression(key: &'static str) -> Option<Expression> {
119 COMMON_EXPRESSIONS.with(|cache| cache.borrow().get(key).cloned())
120}
121
122/// Build a function expression with cached name construction
123///
124/// Combines function name caching with expression construction for
125/// optimal performance in indexed function parsing.
126///
127/// # Examples
128///
129/// ```rust
130/// use mathhook_core::parser::cache::build_cached_function;
131/// use mathhook_core::Expression;
132///
133/// let args = vec![Expression::integer(1), Expression::symbol("x")];
134/// let func = build_cached_function("bessel", "j_indexed", args);
135/// ```
136pub fn build_cached_function(base: &str, suffix: &str, args: Vec<Expression>) -> Expression {
137 let name = get_cached_function_name(base, suffix);
138 Expression::function(name, args)
139}
140
141/// Clear all thread-local caches
142///
143/// Useful for testing or when memory usage needs to be minimized.
144/// Should rarely be needed in normal operation.
145pub fn clear_caches() {
146 FUNCTION_NAME_CACHE.with(|cache| cache.borrow_mut().clear());
147 EXPR_LIST_BUFFER.with(|buffer| buffer.borrow_mut().clear());
148 COMMON_EXPRESSIONS.with(|cache| cache.borrow_mut().clear());
149}
150
151/// Get cache statistics for monitoring
152///
153/// Returns information about cache usage for performance monitoring
154/// and optimization.
155#[derive(Debug, Clone)]
156pub struct CacheStats {
157 pub function_name_cache_size: usize,
158 pub expr_list_buffer_capacity: usize,
159 pub common_expressions_size: usize,
160}
161
162/// Get current cache statistics
163///
164/// # Examples
165///
166/// ```rust
167/// use mathhook_core::parser::cache::get_cache_stats;
168///
169/// let stats = get_cache_stats();
170/// println!("Function name cache size: {}", stats.function_name_cache_size);
171/// ```
172pub fn get_cache_stats() -> CacheStats {
173 let function_name_cache_size = FUNCTION_NAME_CACHE.with(|cache| cache.borrow().len());
174 let expr_list_buffer_capacity = EXPR_LIST_BUFFER.with(|buffer| buffer.borrow().capacity());
175 let common_expressions_size = COMMON_EXPRESSIONS.with(|cache| cache.borrow().len());
176
177 CacheStats {
178 function_name_cache_size,
179 expr_list_buffer_capacity,
180 common_expressions_size,
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn test_function_name_caching() {
190 clear_caches(); // Start with clean cache
191
192 let name1 = get_cached_function_name("test", "function");
193 let name2 = get_cached_function_name("test", "function");
194
195 assert_eq!(name1, name2);
196 assert_eq!(name1, "test_function");
197
198 let stats = get_cache_stats();
199 assert_eq!(stats.function_name_cache_size, 1);
200 }
201
202 #[test]
203 fn test_expr_list_building() {
204 let exprs = vec![
205 Expression::integer(1),
206 Expression::integer(2),
207 Expression::integer(3),
208 ];
209
210 let result = build_expr_list(exprs.clone());
211 assert_eq!(result.len(), 3);
212 assert_eq!(result, exprs);
213 }
214
215 #[test]
216 fn test_cached_expressions() {
217 let zero = get_cached_expression("0");
218 let pi = get_cached_expression("pi");
219 let unknown = get_cached_expression("unknown");
220
221 assert!(zero.is_some());
222 assert!(pi.is_some());
223 assert!(unknown.is_none());
224 }
225
226 #[test]
227 fn test_cached_function_building() {
228 let args = vec![Expression::integer(1), Expression::symbol("x")];
229 let func = build_cached_function("bessel", "j", args.clone());
230
231 match func {
232 Expression::Function {
233 name,
234 args: func_args,
235 } => {
236 assert_eq!(name, "bessel_j");
237 assert_eq!(*func_args, args);
238 }
239 _ => panic!("Expected function expression"),
240 }
241 }
242
243 #[test]
244 fn test_cache_clearing() {
245 // Populate caches
246 let _name = get_cached_function_name("test", "clear");
247 let _expr = build_expr_list(vec![Expression::integer(1)]);
248
249 let stats_before = get_cache_stats();
250 assert!(stats_before.function_name_cache_size > 0);
251
252 clear_caches();
253
254 let stats_after = get_cache_stats();
255 assert_eq!(stats_after.function_name_cache_size, 0);
256 }
257}