1 个不稳定版本
0.4.1 | 2023年9月22日 |
---|
#4 在 #procedural-macro
每月下载量 187
28KB
336 行代码(不含注释)
cache-macro
一个用于自动缓存给定输入集的函数结果的进程宏。
之前命名为 'lru-cache-macros',但更名以反映范围的扩展。
示例
use cache_macro::cache;
use lru_cache::LruCache;
#[cache(LruCache : LruCache::new(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。所有中间结果因为递归而命中缓存。
使用方法
只需将 #[cache(CacheType : 构造函数)]
放置在函数上方。函数必须遵守一些属性才能使用 lru_cache
- 所有参数和返回值都必须实现
Clone
。 - 函数不能以任何形式接受
self
。
使用的 LruCache
类型必须接受两个泛型参数 <Args, Return>
并必须支持方法 get_mut(&K) -> Option<&mut V>
和 insert(K, V)
。目前,lru-cache
(用于 LRU 缓存)和 expiring_map
(用于生存时间缓存)库满足这些要求。
目前,此库仅在 nightly rust 上运行。但是,一旦 2018 版本稳定以及过程宏诊断接口稳定,它应该可以在稳定版本上运行。
配置
可以通过在 #[cache(...)]
下添加额外的属性来配置 lru_cache
宏。
所有配置属性都采用以下形式:#[cache_cfg(...)]
。可用的属性包括:
#[cache_cfg(ignore_args= ...)]
这允许某些参数在缓存的目的下被忽略。这意味着它们不是哈希表键的一部分,因此永远不会影响函数的输出。这在诊断设置、返回执行次数或其他内省目的时可能很有用。
ignore_args
接受一个逗号分隔的变量标识符列表,表示要忽略的参数。
示例
use cache_macro::cache;
use lru_cache::LruCache;
#[cache(LruCache : LruCache::new(20))]
#[cache_cfg(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
进行。
#[cache_cfg(thread_local)]
将缓存存储在线程本地存储中,而不是全局静态存储中。这避免了互斥锁定的开销,但每个线程都将获得自己的缓存,并且所有缓存都不会影响其他线程。
关于第一个示例的扩展
use cache_macro::cache;
use lru_cache::LruCache;
#[cache(LruCache : LruCache::new(20))]
#[cache_cfg(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);
详细信息
除非添加了 #[cache_cfg(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 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
~46K SLoC