7 个版本
0.1.6 | 2023 年 8 月 28 日 |
---|---|
0.1.5 | 2023 年 8 月 20 日 |
0.1.1 | 2023 年 7 月 22 日 |
#82 in 缓存
74 每月下载量
67KB
1K SLoC
Interned 通过 Interned<T>
提供高度优化的、线程本地的泛型内联,并在此内联层之上构建了 记忆化 层,由 Memoized<I, T>
提供,可以缓存任意输入 I: Hash
的结果并将其在此内联层中内联。
为所有基本类型、Sized
T
的切片(包括 &[u8]
)以及 str
切片提供了泛型实现(&str
)。可以通过实现 DataType
、Staticize
和 Hash
来添加对其他任意类型的支持。str
切片有一个自定义实现,因为它们是唯一具有切片支持的内置无大小类型。
所有值都是堆分配的 'static
,并从堆中受益于 TypeId
特定的引用局部性。任何两个具有相同 T
值的 Interned<T>
实例都将保证指向堆中的同一内存地址。在其他方面,这允许进行 O(1)
(数据大小的)等价比较,因为比较的是堆地址而不是逐位比较底层数据。这使得内部类型特别适合解析和类似低熵数据任务。
'static
生存期和底层堆数据的不可变性有一个缺点是 Interned<T>
和 Memoized<I, T>
的唯一值在某种意义上是 泄漏 的,这意味着它们永远不能被重新分配。这使得我们可以为所有内部类型实现 Copy
,因为我们可以在为特定值创建后依赖于堆指针在程序生命周期内继续存在。因此,你不应该使用此crate为长时间运行的程序,这些程序将遇到无限数量的唯一值,例如由不断流入的用户输入创建的。
由于 Interned<T>
在堆栈上的内部大小是一个 usize
(指针)和一个 u64
(缓存的哈希码)的大小,所以直接使用整数类型与 Interned<T>
一起使用是愚蠢的,但是通过 Memoized<I, T>
缓存昂贵的计算是有意义的。
还提供了一个内部字符串类型 InStr
,它是 Interned<&'static str>
的便捷包装。它有几个额外的实现,如果你想处理内部字符串,它应该是你的首选类型。
内部示例
#[test]
fn test_interned_showcase() {
let a: Interned<i32> = 1289.into();
let b = Interned::from(1289);
let c: Interned<i32> = 47.into();
assert_eq!(a, b);
assert_ne!(a, c);
assert_eq!(a.as_ptr(), b.as_ptr());
assert_ne!(b.as_ptr(), c.as_ptr());
let d: Interned<&str> = "asdf".into();
assert_ne!(d, "fdsa".into());
assert_eq!(Interned::from("asdf"), d);
let e = Interned::from([1, 2, 3, 4, 5].as_slice());
let f = InStr::from("abc");
let g: InStr = "abc".into();
assert_eq!(f, g);
assert_eq!(f.as_ptr(), g.as_ptr());
assert_eq!(e, [1, 2, 3, 4, 5].as_slice().into());
assert_ne!(e, [4, 1, 7].as_slice().into());
assert_eq!(format!("{b:?}"), "Interned<i32> { value: 1289 }");
assert_eq!(format!("{d:?}"), "Interned<&str> { str: \"asdf\" }");
assert_eq!(e[3], 4);
assert_eq!(e[0], 1);
assert_eq!(
format!("{e:?}"),
"Interned<&[i32]> { slice: [1, 2, 3, 4, 5] }"
);
}
记忆化示例
#[test]
fn test_memoized_basic() {
let initial_interned = num_interned::<usize>();
let initial_memoized = num_memoized::<usize>();
let a = Memoized::from("scope a", "some_input", |input| input.len().into());
let b = Memoized::from("scope a", "other", |input| input.len().into());
assert_ne!(a, b);
let c = Memoized::from("scope a", "some_input", |input| input.len().into());
assert_eq!(a, c);
assert_ne!(b, c);
assert_eq!(a.as_value(), &10);
assert_ne!(*a.as_value(), 11);
assert_eq!(*b.interned().interned_value(), 5);
assert_eq!(*c.as_value(), 10);
assert_eq!(num_interned::<usize>(), initial_interned + 2);
assert_eq!(num_memoized::<usize>(), initial_memoized + 2);
}
以下演示了“作用域”如何与 Memoized
一起工作
#[test]
fn test_memoized_showcase() {
fn expensive_fn(a: usize, b: usize, c: usize) -> String {
format!("{}", a * a + b * b + c * c)
}
let a = Memoized::from("my_scope", (1, 2, 3), |tup: (usize, usize, usize)| {
expensive_fn(tup.0, tup.1, tup.2).as_str().into()
});
assert_eq!(a.as_str(), "14");
}
依赖关系
~0–8.5MB
~51K SLoC