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

弃用 夜间版 lru-cache-macros

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

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

Download history 6/week @ 2024-02-26 109/week @ 2024-04-01

每月 109 次下载

MIT 许可证

31KB
392

lru-cache-macros

Build Status lru-cache-macros on docs.rs lru-cache-macros on crates.io

一个属性过程宏,根据一组输入自动缓存函数的结果。

由于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