#string-interning #key-string #string #interning #str #intern #symbol

无 std lasso2

一个多线程和单线程的字符串池,允许以最小的内存占用将字符串进行缓存,并为其分配一个唯一的键,可以在任何时间通过该键获取字符串

3 个版本

0.8.2 2024 年 5 月 22 日
0.8.1 2024 年 5 月 22 日
0.8.0 2024 年 5 月 22 日

#51 in 并发

Download history 238/week @ 2024-05-16 128/week @ 2024-05-23 6/week @ 2024-05-30 5/week @ 2024-06-06 3/week @ 2024-06-13 2/week @ 2024-06-20 57/week @ 2024-06-27 723/week @ 2024-07-04 1529/week @ 2024-07-11 585/week @ 2024-07-18 725/week @ 2024-07-25 1352/week @ 2024-08-01 1079/week @ 2024-08-08 2739/week @ 2024-08-15

每月 6,041 次下载

MIT/Apache

325KB
6K SLoC

CI Security Audit Coverage Docs.rs Crates.io

一个多线程和单线程的 字符串池,允许以最小的内存占用将字符串进行缓存,并为其分配一个唯一的 ,可以在任何时间通过该键获取字符串。一个 Rodeo 允许 O(1) 池化和解析,并且可以转换为 RodeoReader 以允许无争用解析,包括键到字符串和字符串到键的操作。它还可以转换为仅具有键到字符串操作的 RodeoResolver,以实现最低的内存使用。

我应该使用哪个字符串池?

对于单线程工作负载,建议使用Rodeo,而对于多线程应用程序应使用ThreadedRodeo。这两种方法都是字符串内嵌的唯一方式,但大多数应用程序都会达到一个不再需要内嵌字符串的阶段,此时将选择RodeoReaderRodeoResolver。如果用户仍然需要获取字符串的键,则必须使用RodeoReader(尽管此时它们仍然可以转换为RodeoResolver)。对于只需要键到字符串解析的用户,RodeoResolver在尽可能小的内存使用下提供无争访问。注意,为了访问ThreadedRodeo,需要启用multi-threaded功能。

内嵌器 线程安全 内嵌字符串 str到键 键到str 无争访问 内存使用
Rodeo N/A 中等
ThreadedRodeo 大多数
RodeoReader 中等
RodeoResolver 最少

Cargo功能

默认情况下,lasso2有一个依赖项,hashbrown,并且只暴露了Rodeo。由于标准库的hashmap中当前的raw_entry api是不稳定的,因此使用了Hashbrown。在hashmap中进行自定义哈希,这有助于显著降低内存使用。为了使用ThreadedRodeo,必须启用multi-threaded功能。

  • multi-threaded - 启用多线程任务的内嵌器ThreadedRodeo
  • ahasher - 使用ahashRandomState作为默认哈希函数
  • no-std - 启用RodeoThreadedRodeono_std + alloc支持
    • 自动启用以下所需功能
      • ahasher - no_std哈希函数
  • serialize - 为所有Spur类型和所有内嵌器实现SerializeDeserialize
  • 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 吉比特每秒

依赖

~1.4–7MB
~29K SLoC