7 个版本
0.3.1 | 2018年11月25日 |
---|---|
0.3.0 | 2018年11月20日 |
0.2.3 | 2018年11月19日 |
0.1.0 | 2018年11月17日 |
#5 in #procedural-macro
每月 109 次下载
31KB
392 行
lru-cache-macros
一个属性过程宏,根据一组输入自动缓存函数的结果。
由于cache-macro 提供了相同的功能并支持不仅仅是 lru 缓存,因此该软件包已被弃用。新代码应使用该软件包。
示例
use lru_cache_macros::lru_cache;
#[lru_cache(20)]
fn fib(x: u32) -> u64 {
println!("{:?}", x);
if x <= 1 {
1
} else {
fib(x - 1) + fib(x - 2)
}
}
assert_eq!(fib(19), 6765);
上述示例仅调用 fib
二十次,使用从 0 到 19 的值。所有中间结果由于递归而命中缓存。
用法
只需在您的函数上方放置 #[lru_cache([size])]
。函数必须遵守一些属性才能使用 lru_cache
- 所有参数和返回值都必须实现
Clone
。 - 函数不能以任何形式接受
self
。
默认情况下,宏将使用位于 ::lru_cache::LruCache
的 LruCache。这可以通过设置配置部分中显示的 cache_type
配置变量来更改。
使用的 LruCache
类型必须接受两个泛型参数 <Args, Return>
并必须支持方法 get_mut(&K)
和 insert(K, V)
。 lru-cache
软件包符合这些要求。
目前,该软件包仅在 nightly rust 上工作。但是,一旦 2018 版本稳定以及过程宏诊断接口,它应该能够在稳定版本上运行。
配置
可以在 #[lru_cache(size)]
下方添加额外的属性来配置 lru_cache 宏。
所有配置属性都采用以下形式 #[lru_config(...)]
。可用的属性包括
-
#[lru_config(cache_type= ...)]
这允许更改内部使用的缓存类型。默认值相当于
#[lru_config(cache_type=::lru_cache::LruCache)]
-
#[lru_config(ignore_args= ...)]
这允许在缓存的目的下忽略某些参数。这意味着它们不是哈希表键的一部分,因此不应影响函数的输出。这对于诊断设置、返回执行次数或其他内省目的可能很有用。
ignore_args
接受一个逗号分隔的变量标识符列表以忽略。示例
use lru_cache_macros::lru_cache; #[lru_cache(20)] #[lru_config(ignore_args = call_count)] fn fib(x: u64, call_count: &mut u32) -> u64 { *call_count += 1; if x <= 1 { 1 } else { fib(x - 1, call_count) + fib(x - 2, call_count) } } let mut call_count = 0; assert_eq!(fib(39, &mut call_count), 102_334_155); assert_eq!(call_count, 40);
call_count
参数可以变化,缓存仅基于x
进行。 -
#[lru_config(thread_local)]
将缓存存储在线程局部存储中,而不是全局静态存储。这样可以避免互斥锁定的开销,但每个线程都将获得自己的缓存,并且所有缓存都不会影响任何其他线程。
关于第一个示例的扩展
use lru_cache_macros::lru_cache; #[lru_cache(20)] #[lru_config(thread_local)] fn fib(x: u32) -> u64 { println!("{:?}", x); if x <= 1 { 1 } else { fib(x - 1) + fib(x - 2) } } assert_eq!(fib(19), 6765);
详细信息
除非添加了配置#[lru_config(thread_local)]
,否则创建的缓存将作为一个由互斥锁保护的静态变量存储。
使用默认设置,fibonacci示例将生成以下代码
fn __lru_base_fib(x: u32) -> u64 {
if x <= 1 { 1 } else { fib(x - 1) + fib(x - 2) }
}
fn fib(x: u32) -> u64 {
use lazy_static::lazy_static;
use std::sync::Mutex;
lazy_static! {
static ref cache: Mutex<::lru_cache::LruCache<(u32,), u64>> =
Mutex::new(::lru_cache::LruCache::new(20usize));
}
let cloned_args = (x.clone(),);
let mut cache_unlocked = cache.lock().unwrap();
let stored_result = cache_unlocked.get_mut(&cloned_args);
if let Some(stored_result) = stored_result {
return stored_result.clone();
};
drop(cache_unlocked);
let ret = __lru_base_fib(x);
let mut cache_unlocked = cache.lock().unwrap();
cache_unlocked.insert(cloned_args, ret.clone());
ret
}
而如果使用#[lru_config(thread_local)]
,生成的代码将类似于
fn __lru_base_fib(x: u32) -> u64 {
if x <= 1 { 1 } else { fib(x - 1) + fib(x - 2) }
}
fn fib(x: u32) -> u64 {
use std::cell::UnsafeCell;
use std::thread_local;
thread_local!(
static cache: UnsafeCell<::lru_cache::LruCache<(u32,), u64>> =
UnsafeCell::new(::lru_cache::LruCache::new(20usize));
);
cache.with(|c|
{
let mut cache_ref = unsafe { &mut *c.get() };
let cloned_args = (x.clone(),);
let stored_result = cache_ref.get_mut(&cloned_args);
if let Some(stored_result) = stored_result {
stored_result.clone()
} else {
let ret = __lru_base_fib(x);
cache_ref.insert(cloned_args, ret.clone());
ret
}
})
}
依赖
~2MB
~45K SLoC