#cache #memoization #lru

已撤回 接近缓存

是 https://github.com/jaemk/cached 的近亲

使用旧的 Rust 2015

0.8.0 2019 年 8 月 21 日

#24#caching

MIT 许可证

35KB
648

对 https://github.com/jaemk/cached crate 进行了优化


lib.rs:

Build Status crates.io docs

缓存结构和简化函数记忆化

cached 提供了几个缓存结构的实现,以及一个用于定义记忆化函数的方便宏。

使用 cached! 宏定义的记忆化函数是线程安全的,后端函数缓存被互斥锁封装。函数缓存在函数执行期间不会被锁定,因此对具有相同参数的长运行函数的初始(在空缓存上的)并发调用将各自完全执行,并在完成时覆盖记忆化值。这反映了 Python 的 functools.lru_cache 的行为。

有关可用的缓存存储的详细信息,请参阅 cached::stores 文档

使用 cached! 定义记忆化函数

cached! 定义的函数将使用函数的参数作为键(或使用 cached_key! 时为特定表达式)来缓存其结果。当调用 cached! 定义的函数时,首先检查函数的缓存以查找已计算(且仍然有效)的值,然后再评估函数体。

由于需要在全局缓存中存储参数和返回值的要求

  • 函数返回类型必须是拥有的并实现 Clone
  • 函数参数必须是拥有的并实现 Hash + Eq + Clone 或必须使用 cached_key! 宏将参数转换为拥有的 + Hash + Eq + Clone 类型。
  • 在插入和检索过程中将 cloned 参数和返回值。
  • cached! 函数不应用于产生副作用的结果!
  • cached! 函数不能直接在 impl 块下存在,因为 cached! 展开为 once_cell 初始化和一个函数定义。

注意:任何实现了 cached::Cached 的自定义缓存都可以与 cached 宏一起使用,代替内置的缓存。

请参阅 examples 了解基本用法以及自定义缓存存储的实现示例。

cached!cached_key! 的用法与选项

具体选项取决于您希望有多具体。下面是完整语法的分解。

1.) 使用缩写将使用无界缓存。

#[macro_use] extern crate cached;

/// Defines a function named `fib` that uses a cache named `FIB`
cached!{
FIB;
fn fib(n: u64) -> u64 = {
if n == 0 || n == 1 { return n }
fib(n-1) + fib(n-2)
}
}

2.) 使用完整语法需要指定完整的缓存类型并提供一个用于缓存的缓存实例。注意,缓存的关键类型是函数参数类型的元组。如果您想对键有更细粒度的控制,可以使用 cached_key! 宏。以下示例使用 SizedCache(LRU)

#[macro_use] extern crate cached;

use std::thread::sleep;
use std::time::Duration;
use cached::SizedCache;

/// Defines a function `compute` that uses an LRU cache named `COMPUTE` which has a
/// size limit of 50 items. The `cached!` macro will implicitly combine
/// the function arguments into a tuple to be used as the cache key.
cached!{
COMPUTE: SizedCache<(u64, u64), u64> = SizedCache::with_size(50);
fn compute(a: u64, b: u64) -> u64 = {
sleep(Duration::new(2, 0));
return a * b;
}
}

3.) cached_key 宏的功能与之前相同,但允许您将缓存键定义为表达式。

#[macro_use] extern crate cached;

use std::thread::sleep;
use std::time::Duration;
use cached::SizedCache;

/// Defines a function named `length` that uses an LRU cache named `LENGTH`.
/// The `Key = ` expression is used to explicitly define the value that
/// should be used as the cache key. Here the borrowed arguments are converted
/// to an owned string that can be stored in the global function cache.
cached_key!{
LENGTH: SizedCache<String, usize> = SizedCache::with_size(50);
Key = { format!("{}{}", a, b) };
fn length(a: &str, b: &str) -> usize = {
let size = a.len() + b.len();
sleep(Duration::new(size as u64, 0));
size
}
}

4.) cached_resultcached_key_result 宏的功能类似于 cachedcached_key,但缓存的函数必须返回 Result(或类似 io::Result 的类型别名)。如果函数返回 Ok(val),则 val 会被缓存,但错误不会。请注意,只需要成功类型实现 Clone,而不是错误类型。当使用 cached_resultcached_key_result 时,缓存类型不能推导,必须始终显式指定。

#[macro_use] extern crate cached;

use cached::UnboundCache;

/// Cache the successes of a function.
/// To use `cached_key_result` add a key function as in `cached_key`.
cached_result!{
MULT: UnboundCache<(u64, u64), u64> = UnboundCache::new(); // Type must always be specified
fn mult(a: u64, b: u64) -> Result<u64, ()> = {
if a == 0 || b == 0 {
return Err(());
} else {
return Ok(a * b);
}
}
}

语法

常见的宏语法是

cached_key!{
CACHE_NAME: CacheType = CacheInstance;
Key = KeyExpression;
fn func_name(arg1: arg_type, arg2: arg_type) -> return_type = {
// do stuff like normal
return_type
}
}

其中

  • CACHE_NAME 是用于持有缓存 static ref 的唯一名称
  • CacheType 是缓存的完整类型
  • CacheInstance 是任何产生 CacheType 实例的表达式,用作缓存存储,后跟 ;
  • 使用 cached_key! 宏时,必须指定“Key”行。此行必须以字面标记 Key = 开头,后跟一个计算为键的表达式,后跟 ;
  • fn func_name(arg1: arg_type) -> return_type 与常规函数签名形式相同,区别在于没有返回值的函数必须显式声明(例如 fn func_name(arg: arg_type) -> ()
  • 位于 = 后的表达式是分配给 func_name 的函数体。注意,函数体可以对其缓存的自身(func_name)进行递归调用。

使用 cached_control! 进行细粒度控制

cached_control! 宏允许您提供表达式,这些表达式将被插入到缓存函数的关键区域。虽然 cachedcached_result 变体对于大多数场景来说是足够的,但能够自定义宏的功能可能很有用。

#[macro_use] extern crate cached;

use cached::UnboundCache;

/// The following usage plugs in expressions to make the macro behave like
/// the `cached_result!` macro.
cached_control!{
CACHE: UnboundCache<String, String> = UnboundCache::new();

// Use an owned copy of the argument `input` as the cache key
Key = { input.to_owned() };

// If a cached value exists, it will bind to `cached_val` and
// a `Result` will be returned containing a copy of the cached
// evaluated body. This will return before the function body
// is executed.
PostGet(cached_val) = { return Ok(cached_val.clone()) };

// The result of executing the function body will be bound to
// `body_result`. In this case, the function body returns a `Result`.
// We match on the `Result`, returning an early `Err` if the function errored.
// Otherwise, we pass on the function's result to be cached.
PostExec(body_result) = {
match body_result {
Ok(v) => v,
Err(e) => return Err(e),
}
};

// When inserting the value into the cache we bind
// the to-be-set-value to `set_value` and give back a copy
// of it to be inserted into the cache
Set(set_value) = { set_value.clone() };

// Before returning, print the value that will be returned
Return(return_value) = {
println!("{}", return_value);
Ok(return_value)
};

fn can_fail(input: &str) -> Result<String, String> = {
let len = input.len();
if len < 3 { Ok(format!("{}-{}", input, len)) }
else { Err("too big".to_string()) }
}
}

依赖项

~1.5MB
~23K SLoC