一个多线程和单线程的 字符串池,允许以最小的内存占用将字符串进行缓存,并为其分配一个唯一的 键,可以在任何时间通过该键获取字符串。一个 Rodeo
允许 O(1)
池化和解析,并且可以转换为 RodeoReader
以允许无争用解析,包括键到字符串和字符串到键的操作。它还可以转换为仅具有键到字符串操作的 RodeoResolver
,以实现最低的内存使用。
我应该使用哪个字符串池?
对于单线程工作负载,建议使用Rodeo
,而对于多线程应用程序应使用ThreadedRodeo
。这两种方法都是字符串内嵌的唯一方式,但大多数应用程序都会达到一个不再需要内嵌字符串的阶段,此时将选择RodeoReader
和RodeoResolver
。如果用户仍然需要获取字符串的键,则必须使用RodeoReader
(尽管此时它们仍然可以转换为RodeoResolver
)。对于只需要键到字符串解析的用户,RodeoResolver
在尽可能小的内存使用下提供无争访问。注意,为了访问ThreadedRodeo
,需要启用multi-threaded
功能。
Cargo功能
默认情况下,lasso2
有一个依赖项,hashbrown
,并且只暴露了Rodeo
。由于标准库的hashmap中当前的raw_entry
api是不稳定的,因此使用了Hashbrown。在hashmap中进行自定义哈希,这有助于显著降低内存使用。为了使用ThreadedRodeo
,必须启用multi-threaded
功能。
multi-threaded
- 启用多线程任务的内嵌器ThreadedRodeo
ahasher
- 使用ahash
的RandomState
作为默认哈希函数
no-std
- 启用Rodeo
和ThreadedRodeo
的no_std
+ alloc
支持
serialize
- 为所有Spur
类型和所有内嵌器实现Serialize
和Deserialize
inline-more
- 使用#[inline]
注释外部api
示例:使用Rodeo
use lasso2::Rodeo;
let mut rodeo = Rodeo::default();
let key = rodeo.get_or_intern("Hello, world!");
// Easily retrieve the value of a key and find the key for values
assert_eq!("Hello, world!", rodeo.resolve(&key));
assert_eq!(Some(key), rodeo.get("Hello, world!"));
// Interning the same string again will yield the same key
let key2 = rodeo.get_or_intern("Hello, world!");
assert_eq!(key, key2);
示例:使用ThreadedRodeo
use lasso2::ThreadedRodeo;
use std::{thread, sync::Arc};
let rodeo = Arc::new(ThreadedRodeo::default());
let key = rodeo.get_or_intern("Hello, world!");
// Easily retrieve the value of a key and find the key for values
assert_eq!("Hello, world!", rodeo.resolve(&key));
assert_eq!(Some(key), rodeo.get("Hello, world!"));
// Interning the same string again will yield the same key
let key2 = rodeo.get_or_intern("Hello, world!");
assert_eq!(key, key2);
// ThreadedRodeo can be shared across threads
let moved = Arc::clone(&rodeo);
let hello = thread::spawn(move || {
assert_eq!("Hello, world!", moved.resolve(&key));
moved.get_or_intern("Hello from the thread!")
})
.join()
.unwrap();
assert_eq!("Hello, world!", rodeo.resolve(&key));
assert_eq!("Hello from the thread!", rodeo.resolve(&hello));
示例:创建RodeoReader
use lasso2::Rodeo;
// Rodeo and ThreadedRodeo are interchangeable here
let mut rodeo = Rodeo::default();
let key = rodeo.get_or_intern("Hello, world!");
assert_eq!("Hello, world!", rodeo.resolve(&key));
let reader = rodeo.into_reader();
// Reader keeps all the strings from the parent
assert_eq!("Hello, world!", reader.resolve(&key));
assert_eq!(Some(key), reader.get("Hello, world!"));
// The Reader can now be shared across threads, no matter what kind of Rodeo created it
示例:创建RodeoResolver
use lasso2::Rodeo;
// Rodeo and ThreadedRodeo are interchangeable here
let mut rodeo = Rodeo::default();
let key = rodeo.get_or_intern("Hello, world!");
assert_eq!("Hello, world!", rodeo.resolve(&key));
let resolver = rodeo.into_resolver();
// Resolver keeps all the strings from the parent
assert_eq!("Hello, world!", resolver.resolve(&key));
// The Resolver can now be shared across threads, no matter what kind of Rodeo created it
示例:创建自定义范围的键
有时你希望你的键仅存在于(或不存在于)某个特定的值范围内,以便你可以有定制的niches。这允许你将更多数据打包到原本可能未使用的空间中,这对于内存敏感型应用程序可能是关键的。
use lasso2::{Key, Rodeo};
// First make our key type, this will be what we use as handles into our interner
#[derive(Copy, Clone, PartialEq, Eq)]
struct NicheKey(u32);
// This will reserve the upper 255 values for us to use as niches
const NICHE: usize = 0xFF000000;
// Implementing `Key` is unsafe and requires that anything given to `try_from_usize` must produce the
// same `usize` when `into_usize` is later called
unsafe impl Key for NicheKey {
fn into_usize(self) -> usize {
self.0 as usize
}
fn try_from_usize(int: usize) -> Option<Self> {
if int < NICHE {
// The value isn't in our niche range, so we're good to go
Some(Self(int as u32))
} else {
// The value interferes with our niche, so we return `None`
None
}
}
}
// To make sure we're upholding `Key`'s safety contract, let's make two small tests
#[test]
fn value_in_range() {
let key = NicheKey::try_from_usize(0).unwrap();
assert_eq!(key.into_usize(), 0);
let key = NicheKey::try_from_usize(NICHE - 1).unwrap();
assert_eq!(key.into_usize(), NICHE - 1);
}
#[test]
fn value_out_of_range() {
let key = NicheKey::try_from_usize(NICHE);
assert!(key.is_none());
let key = NicheKey::try_from_usize(u32::max_value() as usize);
assert!(key.is_none());
}
// And now we're done and can make `Rodeo`s or `ThreadedRodeo`s that use our custom key!
let mut rodeo: Rodeo<NicheKey> = Rodeo::new();
let key = rodeo.get_or_intern("It works!");
assert_eq!(rodeo.resolve(&key), "It works!");
示例:使用 FromIterator
创建
use lasso2::Rodeo;
use core::iter::FromIterator;
// Works for both `Rodeo` and `ThreadedRodeo`
let rodeo = Rodeo::from_iter(vec![
"one string",
"two string",
"red string",
"blue string",
]);
assert!(rodeo.contains("one string"));
assert!(rodeo.contains("two string"));
assert!(rodeo.contains("red string"));
assert!(rodeo.contains("blue string"));
use lasso2::Rodeo;
use core::iter::FromIterator;
// Works for both `Rodeo` and `ThreadedRodeo`
let rodeo: Rodeo = vec!["one string", "two string", "red string", "blue string"]
.into_iter()
.collect();
assert!(rodeo.contains("one string"));
assert!(rodeo.contains("two string"));
assert!(rodeo.contains("red string"));
assert!(rodeo.contains("blue string"));
基准测试
基准测试数据使用 Criterion.rs 收集
操作系统:Windows 10
处理器:Ryzen 9 3900X,主频 3800MHz
内存:3200MHz
Rustc:稳定版 1.44.1
Rodeo
STD 随机状态
方法 |
时间 |
吞吐量 |
resolve |
1.9251 微秒 |
13.285 GiB/s |
try_resolve |
1.9214 微秒 |
13.311 GiB/s |
resolve_unchecked |
1.4356 微秒 |
17.816 GiB/s |
get_or_intern (empty) |
60.350 微秒 |
433.96 MiB/s |
get_or_intern (filled) |
57.415 微秒 |
456.15 MiB/s |
try_get_or_intern (empty) |
58.978 微秒 |
444.06 MiB/s |
try_get_or_intern(filled) |
57.421 微秒 |
456.10 MiB/s |
get (empty) |
37.288 微秒 |
702.37 MiB/s |
get (filled) |
55.095 微秒 |
475.36 MiB/s |
AHash
方法 |
时间 |
吞吐量 |
try_resolve |
1.9282 微秒 |
13.264 GiB/s |
resolve |
1.9404 微秒 |
13.181 GiB/s |
resolve_unchecked |
1.4328 微秒 |
17.851 GiB/s |
get_or_intern (empty) |
38.029 微秒 |
688.68 MiB/s |
get_or_intern (filled) |
33.650 微秒 |
778.30 MiB/s |
try_get_or_intern (empty) |
39.392 微秒 |
664.84 MiB/s |
try_get_or_intern(filled) |
33.435 微秒 |
783.31 MiB/s |
get (empty) |
12.565 微秒 |
2.0356 GiB/s |
get (filled) |
26.545 微秒 |
986.61 MiB/s |
FXHash
方法 |
时间 |
吞吐量 |
resolve |
1.9014 微秒 |
13.451 GiB/s |
try_resolve |
1.9278 微秒 |
13.267 GiB/s |
resolve_unchecked |
1.4449 微秒 |
17.701 GiB/s |
get_or_intern (empty) |
32.523 微秒 |
805.27 MiB/s |
get_or_intern (filled) |
30.281 微秒 |
864.88 MiB/s |
try_get_or_intern (empty) |
31.630 微秒 |
828.00 MiB/s |
try_get_or_intern(filled) |
31.002 微秒 |
844.78 MiB/s |
get (empty) |
12.699 微秒 |
2.0141 GiB/s |
get (filled) |
29.220 微秒 |
896.28 MiB/s |
ThreadedRodeo
STD 随机状态
方法 |
时间(1 线程) |
吞吐量(1 线程) |
时间(24 线程) |
吞吐量(24 线程) |
resolve |
54.336 微秒 |
482.00 MiB/s |
364.27 微秒 |
71.897 MiB/s |
try_resolve |
54.582 微秒 |
479.82 MiB/s |
352.67 微秒 |
74.261 MiB/s |
get_or_intern (empty) |
266.03 微秒 |
98.447 MiB/s |
N\A |
N\A |
get_or_intern (filled) |
103.04 微秒 |
254.17 MiB/s |
441.42 微秒 |
59.331 MiB/s |
try_get_or_intern (empty) |
261.80 微秒 |
100.04 MiB/s |
N\A |
N\A |
try_get_or_intern (filled) |
102.61 微秒 |
255.25 MiB/s |
447.42 微秒 |
58.535 MiB/s |
get (empty) |
80.346 微秒 |
325.96 MiB/s |
N\A |
N\A |
get (filled) |
92.669 微秒 |
282.62 MiB/s |
439.24 微秒 |
59.626 MiB/s |
AHash
方法 |
时间(1 线程) |
吞吐量(1 线程) |
时间(24 线程) |
吞吐量(24 线程) |
resolve |
22.261 微秒 |
1.1489 GiB/s |
265.46 微秒 |
98.658 MiB/s |
try_resolve |
22.378 微秒 |
1.1429 GiB/s |
268.58 微秒 |
97.513 MiB/s |
get_or_intern (empty) |
157.86 微秒 |
165.91 MiB/s |
N\A |
N\A |
get_or_intern (filled) |
56.320 微秒 |
465.02 MiB/s |
357.13 微秒 |
73.335 MiB/s |
try_get_or_intern (empty) |
161.46 微秒 |
162.21 MiB/s |
N\A |
N\A |
try_get_or_intern (filled) |
55.874 微秒 |
468.73 MiB/s |
360.25 微秒 |
72.698 MiB/s |
get (empty) |
43.520 微秒 |
601.79 MiB/s |
N\A |
N\A |
get (filled) |
53.720 微秒 |
487.52 MiB/s |
360.66 微秒 |
72.616 MiB/s |
FXHash
方法 |
时间(1 线程) |
吞吐量(1 线程) |
时间(24 线程) |
吞吐量(24 线程) |
try_resolve |
17.289 微秒 |
1.4794 GiB/s |
238.29 微秒 |
109.91 MiB/s |
resolve |
19.833 微秒 |
1.2896 GiB/s |
237.05 微秒 |
110.48 MiB/s |
get_or_intern (empty) |
130.97 微秒 |
199.97 MiB/s |
N\A |
N\A |
get_or_intern (filled) |
42.630 微秒 |
614.35 MiB/s |
301.60 微秒 |
86.837 MiB/s |
try_get_or_intern (empty) |
129.30 微秒 |
202.55 MiB/s |
N\A |
N\A |
try_get_or_intern (filled) |
42.508 微秒 |
616.12 MiB/s |
337.29 微秒 |
77.648 MiB/s |
get (empty) |
28.001 微秒 |
935.30 MiB/s |
N\A |
N\A |
get (filled) |
37.700 微秒 |
694.68 MiB/s |
292.15 微秒 |
89.645 MiB/s |
RodeoReader
STD 随机状态
方法 |
时间(1 线程) |
吞吐量(1 线程) |
时间(24 线程) |
吞吐量(24 线程) |
resolve |
1.9398 微秒 |
13.185 GiB/s |
4.3153 微秒 |
5.9269 GiB/s |
try_resolve |
1.9315 微秒 |
13.242 GiB/s |
4.1956 微秒 |
6.0959 GiB/s |
resolve_unchecked |
1.4416 微秒 |
17.741 GiB/s |
3.1204 微秒 |
8.1964 GiB/s |
get (empty) |
38.886 微秒 |
673.50 MiB/s |
N\A |
N\A |
get (filled) |
56.271 微秒 |
465.42 MiB/s |
105.12 微秒 |
249.14 MiB/s |
AHash
方法 |
时间(1 线程) |
吞吐量(1 线程) |
时间(24 线程) |
吞吐量(24 线程) |
resolve |
1.9404 微秒 |
13.181 GiB/s |
4.1881 微秒 |
6.1069 吉比特每秒 |
try_resolve |
1.8932 微秒 |
13.509 吉比特每秒 |
4.2410 微秒 |
6.0306 吉比特每秒 |
resolve_unchecked |
1.4128 微秒 |
18.103 吉比特每秒 |
3.1691 微秒 |
8.0703 吉比特每秒 |
get (empty) |
11.952 微秒 |
2.1399 吉比特每秒 |
N\A |
N\A |
get (filled) |
27.093 微秒 |
966.65 兆比特每秒 |
56.269 微秒 |
465.44 兆比特每秒 |
FXHash
方法 |
时间(1 线程) |
吞吐量(1 线程) |
时间(24 线程) |
吞吐量(24 线程) |
resolve |
1.8987 微秒 |
13.471 吉比特每秒 |
4.2117 微秒 |
6.0727 吉比特每秒 |
try_resolve |
1.9103 微秒 |
13.389 吉比特每秒 |
4.2254 微秒 |
6.0529 吉比特每秒 |
resolve_unchecked |
1.4469 微秒 |
17.677 吉比特每秒 |
3.0923 微秒 |
8.2709 吉比特每秒 |
get (empty) |
12.994 微秒 |
1.9682 吉比特每秒 |
N\A |
N\A |
get (filled) |
29.745 微秒 |
880.49 兆比特每秒 |
52.387 微秒 |
499.93 兆比特每秒 |
RodeoResolver
方法 |
时间(1 线程) |
吞吐量(1 线程) |
时间(24 线程) |
吞吐量(24 线程) |
resolve |
1.9416 微秒 |
13.172 吉比特每秒 |
3.9114 微秒 |
6.5387 吉比特每秒 |
try_resolve |
1.9264 微秒 |
13.277 吉比特每秒 |
3.9289 微秒 |
6.5097 吉比特每秒 |
resolve_unchecked |
1.6638 微秒 |
15.372 吉比特每秒 |
3.1741 微秒 |
8.0578 吉比特每秒 |