Do not cache "one-hit wonders"

This commit is contained in:
Stephen Chung
2022-09-12 19:47:29 +08:00
parent c1ae9e0405
commit 44219c732c
5 changed files with 84 additions and 29 deletions

View File

@@ -1,6 +1,7 @@
//! System caches.
use crate::func::{CallableFunction, StraightHashMap};
use crate::types::BloomFilterU64;
use crate::{Identifier, StaticVec};
use std::marker::PhantomData;
#[cfg(feature = "no_std")]
@@ -16,12 +17,27 @@ pub struct FnResolutionCacheEntry {
pub source: Option<Box<Identifier>>,
}
/// _(internals)_ A function resolution cache.
/// _(internals)_ A function resolution cache with a bloom filter.
/// Exported under the `internals` feature only.
///
/// [`FnResolutionCacheEntry`] is [`Box`]ed in order to pack as many entries inside a single B-Tree
/// level as possible.
pub type FnResolutionCache = StraightHashMap<u64, Option<FnResolutionCacheEntry>>;
#[derive(Debug, Clone, Default)]
pub struct FnResolutionCache {
/// Hash map containing cached functions.
pub map: StraightHashMap<u64, Option<FnResolutionCacheEntry>>,
/// Bloom filter to avoid caching "one-hit wonders".
pub filter: BloomFilterU64,
}
impl FnResolutionCache {
/// Clear the [`FnResolutionCache`].
#[inline(always)]
pub fn clear(&mut self) {
self.map.clear();
self.filter.clear();
}
}
/// _(internals)_ A type containing system-wide caches.
/// Exported under the `internals` feature only.
@@ -31,7 +47,7 @@ pub type FnResolutionCache = StraightHashMap<u64, Option<FnResolutionCacheEntry>
#[derive(Debug, Clone)]
pub struct Caches<'a> {
/// Stack of [function resolution caches][FnResolutionCache].
fn_resolution: StaticVec<FnResolutionCache>,
fn_resolution_caches: StaticVec<FnResolutionCache>,
/// Take care of the lifetime parameter.
dummy: PhantomData<&'a ()>,
}
@@ -42,7 +58,7 @@ impl Caches<'_> {
#[must_use]
pub const fn new() -> Self {
Self {
fn_resolution: StaticVec::new_const(),
fn_resolution_caches: StaticVec::new_const(),
dummy: PhantomData,
}
}
@@ -50,27 +66,27 @@ impl Caches<'_> {
#[inline(always)]
#[must_use]
pub fn fn_resolution_caches_len(&self) -> usize {
self.fn_resolution.len()
self.fn_resolution_caches.len()
}
/// Get a mutable reference to the current function resolution cache.
#[inline]
#[must_use]
pub fn fn_resolution_cache_mut(&mut self) -> &mut FnResolutionCache {
if self.fn_resolution.is_empty() {
if self.fn_resolution_caches.is_empty() {
// Push a new function resolution cache if the stack is empty
self.push_fn_resolution_cache();
}
self.fn_resolution.last_mut().unwrap()
self.fn_resolution_caches.last_mut().unwrap()
}
/// Push an empty function resolution cache onto the stack and make it current.
#[allow(dead_code)]
#[inline(always)]
pub fn push_fn_resolution_cache(&mut self) {
self.fn_resolution.push(StraightHashMap::default());
self.fn_resolution_caches.push(Default::default());
}
/// Rewind the function resolution caches stack to a particular size.
#[inline(always)]
pub fn rewind_fn_resolution_caches(&mut self, len: usize) {
self.fn_resolution.truncate(len);
self.fn_resolution_caches.truncate(len);
}
}

View File

@@ -257,8 +257,9 @@ impl Engine {
let hash = combine_hashes(hashes.native, hash);
let cache = caches.fn_resolution_cache_mut();
let local_entry: CallableFunction;
let func = match cache.entry(hash) {
let func = match cache.map.entry(hash) {
Entry::Vacant(entry) => {
let func = if args.len() == 2 {
get_builtin_binary_op_fn(name, operands[0], operands[1])
@@ -267,14 +268,22 @@ impl Engine {
};
if let Some(f) = func {
&entry
.insert(Some(FnResolutionCacheEntry {
func: CallableFunction::from_fn_builtin(f),
source: None,
}))
.as_ref()
.unwrap()
.func
if cache.filter.is_absent(hash) {
// Do not cache "one-hit wonders"
cache.filter.mark(hash);
local_entry = CallableFunction::from_fn_builtin(f);
&local_entry
} else {
// Cache repeated calls
&entry
.insert(Some(FnResolutionCacheEntry {
func: CallableFunction::from_fn_builtin(f),
source: None,
}))
.as_ref()
.unwrap()
.func
}
} else {
let result = self.exec_fn_call(
None, global, caches, lib, name, *hashes, operands, false, false, pos,