diff --git a/RELEASES.md b/RELEASES.md index 6d20a5ba..2d23c944 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -9,6 +9,11 @@ Breaking changes * `Dynamic::into_shared` is no longer available under `no_closure`. It used to panic. +Enhancements +------------ + +* Functions resolution cache is used in more cases, making repeated function calls faster. + Version 0.19.11 =============== diff --git a/src/engine.rs b/src/engine.rs index 21f65fa3..d7e6bfd5 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -520,10 +520,12 @@ pub struct State { #[cfg(not(feature = "no_module"))] pub resolver: Option>, /// Cached lookup values for function hashes. - pub functions_cache: HashMap< - NonZeroU64, - Option<(CallableFunction, Option)>, - StraightHasherBuilder, + pub functions_caches: StaticVec< + HashMap< + NonZeroU64, + Option<(CallableFunction, Option)>, + StraightHasherBuilder, + >, >, } @@ -1856,6 +1858,7 @@ impl Engine { statements: impl IntoIterator, level: usize, ) -> Result> { + let mut has_imports = false; let prev_always_search = state.always_search; let prev_scope_len = scope.len(); let prev_mods_len = mods.len(); @@ -1864,13 +1867,26 @@ impl Engine { let result = statements .into_iter() .try_fold(Default::default(), |_, stmt| { + match stmt { + Stmt::Import(_, _, _) => { + // When imports list is modified, clear the functions lookup cache + if has_imports { + state.functions_caches.last_mut().map(|c| c.clear()); + } else { + state.functions_caches.push(Default::default()); + } + has_imports = true; + } + _ => (), + } + self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) }); scope.rewind(prev_scope_len); - if mods.len() != prev_mods_len { - // If imports list is modified, clear the functions lookup cache - state.functions_cache.clear(); + if has_imports { + // If imports list is modified, pop the functions lookup cache + state.functions_caches.pop(); } mods.truncate(prev_mods_len); state.scope_level -= 1; @@ -2365,8 +2381,6 @@ impl Engine { } else { mods.push(name_def.name.clone(), module); } - // When imports list is modified, clear the functions lookup cache - state.functions_cache.clear(); } state.modules += 1; diff --git a/src/fn_call.rs b/src/fn_call.rs index e038e845..15307458 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -176,28 +176,37 @@ impl Engine { self.inc_operations(state, pos)?; // Check if function access already in the cache - let func = &*state.functions_cache.entry(hash_fn).or_insert_with(|| { - // Search for the native function - // First search registered functions (can override packages) - // Then search packages - // Finally search modules + if state.functions_caches.is_empty() { + state.functions_caches.push(Default::default()); + } - //lib.get_fn(hash_fn, pub_only) - self.global_namespace - .get_fn(hash_fn, pub_only) - .cloned() - .map(|f| (f, None)) - .or_else(|| { - self.global_modules.iter().find_map(|m| { - m.get_fn(hash_fn, false) - .map(|f| (f.clone(), m.id_raw().cloned())) + let func = &*state + .functions_caches + .last_mut() + .unwrap() + .entry(hash_fn) + .or_insert_with(|| { + // Search for the native function + // First search registered functions (can override packages) + // Then search packages + // Finally search modules + + //lib.get_fn(hash_fn, pub_only) + self.global_namespace + .get_fn(hash_fn, pub_only) + .cloned() + .map(|f| (f, None)) + .or_else(|| { + self.global_modules.iter().find_map(|m| { + m.get_fn(hash_fn, false) + .map(|f| (f.clone(), m.id_raw().cloned())) + }) }) - }) - .or_else(|| { - mods.get_fn(hash_fn) - .map(|(f, source)| (f.clone(), source.cloned())) - }) - }); + .or_else(|| { + mods.get_fn(hash_fn) + .map(|(f, source)| (f.clone(), source.cloned())) + }) + }); if let Some((func, source)) = func { assert!(func.is_native()); @@ -392,11 +401,11 @@ impl Engine { // Merge in encapsulated environment, if any let mut lib_merged: StaticVec<_>; - let mut old_cache = None; + let mut unified = false; let unified_lib = if let Some(ref env_lib) = fn_def.lib { - old_cache = Some(mem::take(&mut state.functions_cache)); - + unified = true; + state.functions_caches.push(Default::default()); lib_merged = Default::default(); lib_merged.push(env_lib.as_ref()); lib_merged.extend(lib.iter().cloned()); @@ -467,8 +476,8 @@ impl Engine { mods.truncate(prev_mods_len); state.scope_level = orig_scope_level; - if let Some(cache) = old_cache { - state.functions_cache = cache; + if unified { + state.functions_caches.pop(); } result @@ -506,13 +515,13 @@ impl Engine { // Check if it is already in the cache if let Some(state) = state.as_mut() { if let Some(hash) = hash_script { - match state.functions_cache.get(&hash) { + match state.functions_caches.last().map_or(None, |c| c.get(&hash)) { Some(v) => return v.is_some(), None => (), } } if let Some(hash) = hash_fn { - match state.functions_cache.get(&hash) { + match state.functions_caches.last().map_or(None, |c| c.get(&hash)) { Some(v) => return v.is_some(), None => (), } @@ -536,10 +545,24 @@ impl Engine { if !r { if let Some(state) = state.as_mut() { if let Some(hash) = hash_script { - state.functions_cache.insert(hash, None); + if state.functions_caches.is_empty() { + state.functions_caches.push(Default::default()); + } + state + .functions_caches + .last_mut() + .unwrap() + .insert(hash, None); } if let Some(hash) = hash_fn { - state.functions_cache.insert(hash, None); + if state.functions_caches.is_empty() { + state.functions_caches.push(Default::default()); + } + state + .functions_caches + .last_mut() + .unwrap() + .insert(hash, None); } } } @@ -630,8 +653,14 @@ impl Engine { let hash_script = hash_script.unwrap(); // Check if function access already in the cache + if state.functions_caches.is_empty() { + state.functions_caches.push(Default::default()); + } + let (func, source) = state - .functions_cache + .functions_caches + .last_mut() + .unwrap() .entry(hash_script) .or_insert_with(|| { lib.iter()