2 个版本
0.1.1 | 2024 年 7 月 11 日 |
---|---|
0.1.0 | 2024 年 7 月 4 日 |
#141 in 内存管理
79 每月下载量
41KB
767 行
sinter
一个易于使用且快速的全球互斥池。
已互斥的字符串在内存中连续存储,这有助于提高内存局部性或减少碎片。根据需要为互斥器分配额外的内存页面,每个后续页面的大小加倍 - 分摊底层分配的成本。
在字符串已被先前互斥的情况下调用 intern
是快速且无锁的,尽管可能比保留你已有的 IStr
更昂贵。
在最坏的情况下,对 intern
的调用可能相对昂贵,因为如果字符串不存在,则需要与其他线程进行一些同步,并且操作还可能需要为池分配一个新的内存页面。
IStr
到 &'static str
或 &'static CStr
的零成本转换
# use sinter::IStr;
# use ::core::ffi::CStr;
let istr = IStr::new("hello, sinter!");
let s: &'static str = istr.as_str();
let cstr: &'static CStr = istr.as_c_str();
IStr
解引用到 &str
# use sinter::IStr;
# use ::core::ffi::CStr;
let istr = IStr::new("hello, sinter!");
let s: &str = &*istr;
IStr
可以非常便宜地与另一个 IStr
进行比较;在底层 [Eq
] 是通过单个指针比较实现的
# use sinter::intern;
# use ::core::ffi::CStr;
let a = intern("aaa");
let a2 = intern("aaa");
let b = intern("bbb");
assert!(a == a2);
assert!(a != b);
或者您可以与常规 &str
进行比较
# use sinter::IStr;
assert!(IStr::new("sinter") == "sinter");
可灵活构建
# use sinter::{intern, IStr};
# use ::std::ffi::{CStr, CString};
let a = intern("a");
let b = IStr::new("b");
let c = IStr::from("c");
let d = IStr::from(String::from("d"));
let e: IStr = "e".into();
let f = IStr::try_from(CString::new("f").unwrap()).unwrap();
let g = IStr::try_from(CString::new("g").unwrap().as_c_str()).unwrap();
# assert_eq!(
# [a, b, c, d, e, f, g],
# [
# intern("a"),
# intern("b"),
# intern("c"),
# intern("d"),
# intern("e"),
# intern("f"),
# intern("g"),
# ],
# );
使用 get_interned
查找给定的字符串是否已被互斥。这将始终快速/无锁,并在找到时返回 IStr
# use sinter::{get_interned, intern};
intern("exists");
assert!(get_interned("exists").is_some());
assert!(get_interned("doesn't exist").is_none());
《::core::ops::Deref》实现提供了所有有用的 &str
方法及操作,例如子切片
# use sinter::IStr;
let hello_world = IStr::new("hello, world!");
let world: &str = &hello_world[7..];
assert_eq!(world, "world!");
《::core::borrow::BorrowIStr
键的 HashMap
,然后使用 &str
进行优雅的值查找
# use sinter::IStr;
# use ::std::collections::HashMap;
let mut map: HashMap<IStr, f32> = HashMap::new();
map.insert(IStr::new("e"), 2.718);
let val = map.get("e");
# assert_eq!(val, Some(&2.718));
架构
在内部,一个 Interner
数据结构管理已合并字符串的池。
当向池中添加新字符串时,Interner 会锁定池的一半。如果与其他线程有很多竞争(尽管这通常非常不可能),这可能是一个相对缓慢的操作。
另一方面,Interner 使用无锁并发原语来启用读取器(调用 intern
的调用者不需要分配新字符串,而是可以获取现有的 IStr
实例),从而完全避免锁定,使得多余的 intern
调用仍然非常快速。
并发方案如下
-
我们维护一个包含存储字符串的内存页面的链表。新字符串严格追加到最后一个内存页的尾部,并且根据需要分配新页面。这意味着所有现有的
IStr
都有稳定的静态内存位置和数据。 -
我们维护一对冗余的哈希表,将字符串的哈希映射到
IStr
(指向内存页中字符串数据的指针),这有助于快速查找已合并的字符串。这些表由写入者原子交换,允许读取者安全地获取新更新而不锁定。当线程想要检查“可读表”时,它们会递增一个原子计数器。当读取器完成时,这个计数器会再次递增。这允许写入者可靠地等待在原子表交换后的持久读取。
如果每个线程的计数器是偶数,则写入者知道它们根本未进行读取。如果线程的计数器是奇数,则写入者等待计数器至少递增一次。注意,等待单个递增就足够了,并且应该相当快(因为读取很快)。在任何递增之后,写入者都可以确信读取器将在开始新的读取之前获取新表的指针。
-
当线程终止时,它会调用包含指向我们的时代原子计数器的
LocalKey
的析构函数。在这个析构函数中,我们将时代的值设置为特殊值以标记此线程已死亡。稍后,当某些其他代码持有 internner 的 write_lock 时,它会检查时代列表以查看是否有线程已死亡,然后释放包含原子的内存并从 Interner 的列表中删除该时代。这解决了用户不断生成大量临时线程时可能发生的少量内存泄漏问题。它不需要 LocalKey 析构函数等待它能够获取 write_lock,并且它避免了在 Interner 数据结构中积累悬挂指针。
许可证
此软件包的许可证适用于Apache许可证第2.0版、MIT许可证或Zlib许可证中的任何一种,具体由您选择。Apache许可证,版本2.0,或MIT许可证,或Zlib许可证。
除非明确说明,否则您有意提交的任何贡献,旨在包含在此作品中,应据此获得许可。
依赖项
~1–6MB
~21K SLoC