#lru-cache #cache #lru #proc-macro #memoization

cache-macro-stable-rust

用于自动缓存函数输出的过程宏

1 个不稳定版本

0.4.1 2023年9月22日

#4#procedural-macro

Download history 5/week @ 2024-03-10 16/week @ 2024-03-31 1/week @ 2024-04-07 6/week @ 2024-05-05 13/week @ 2024-05-12 68/week @ 2024-05-19 29/week @ 2024-05-26 82/week @ 2024-06-02 32/week @ 2024-06-09 45/week @ 2024-06-16 26/week @ 2024-06-23

每月下载量 187

MIT 许可证

28KB
336 行代码(不含注释)

cache-macro

Build Status cache-macro on docs.rs cache-macro on crates.io

一个用于自动缓存给定输入集的函数结果的进程宏。

之前命名为 '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