14 个版本 (1 个稳定版)

1.0.0 2024 年 1 月 20 日
0.10.0 2023 年 6 月 11 日
0.9.0 2022 年 7 月 21 日
0.8.1 2021 年 8 月 28 日
0.2.1 2019 年 11 月 30 日

#5缓存

Download history 5196/week @ 2024-04-29 2646/week @ 2024-05-06 3282/week @ 2024-05-13 3278/week @ 2024-05-20 2852/week @ 2024-05-27 2770/week @ 2024-06-03 3072/week @ 2024-06-10 3660/week @ 2024-06-17 3248/week @ 2024-06-24 3585/week @ 2024-07-01 3989/week @ 2024-07-08 4203/week @ 2024-07-15 4640/week @ 2024-07-22 4379/week @ 2024-07-29 4363/week @ 2024-08-05 3329/week @ 2024-08-12

每月 16,877 次下载
51 个 Crates 中使用 (17 个直接使用)

BSD-2-Clause-Patent

360KB
916

ustr

快速、FFI 友好的字符串池化。

Build Status Latest Version Docs Badge

A Ustr (Unique str) 是一个轻量级的句柄,代表全局字符串缓存中的静态不可变条目,允许进行

  • 极快的字符串赋值和比较。

  • 高效存储。只在内存中保留一个字符串副本,访问它只是指针间接。

  • 快速哈希 - 预计算的哈希值与字符串一起存储。

  • 快速 FFI - 字符串以终止空字节存储,可以直接传递给 C 而无需进行 CString 舞蹈。

缺点是永远不会释放字符串,所以如果你创建了很多很多字符串,可能会耗尽内存。另一方面,战争与和平 只有 3MB,所以应该没问题。

此 crate 基于 OpenImageIO 的 (OIIO) ustring,但它不是二进制兼容的(目前还不是)。底层哈希图实现直接从 OIIO 端口。

用法

use ustr::{Ustr, ustr};

// Creation is quick and easy using either `Ustr::from` or the `ustr` short
// function and only one copy of any string is stored
let h1 = Ustr::from("hello");
let h2 = ustr("hello");

// Comparisons and copies are extremely cheap
let h3 = h1;
assert_eq!(h2, h3);

// You can pass straight to FFI
let len = unsafe {
    libc::strlen(h1.as_char_ptr())
};
assert_eq!(len, 5);

// For best performance when using Ustr as key for a HashMap or HashSet,
// you'll want to use the precomputed hash. To make this easier, just use
// the UstrMap and UstrSet exports:
use ustr::UstrMap;

// Key type is always Ustr
let mut map: UstrMap<usize> = UstrMap::default();
map.insert(u1, 17);
assert_eq!(*map.get(&u1).unwrap(), 17);

通过启用 "serde" 功能,您可以使用 serde 序列化单个 Ustr 或整个缓存。

use ustr::{Ustr, ustr};

let u_ser = ustr("serialization is fun!");
let json = serde_json::to_string(&u_ser).unwrap();
let u_de : Ustr = serde_json::from_str(&json).unwrap();

assert_eq!(u_ser, u_de);

由于缓存是全局的,请使用 ustr::DeserializedCache 模拟对象来驱动反序列化。

ustr("Send me to JSON and back");
let json = serde_json::to_string(ustr::cache()).unwrap();

// ... some time later ...
let _: ustr::DeserializedCache = serde_json::from_str(&json).unwrap();
assert_eq!(ustr::num_entries(), 1);
assert_eq!(ustr::string_cache_iter().collect::<Vec<_>>(), vec!["Send me to JSON and back"]);

C/C++ 调用

如果您正在编写使用 ustr 的库,并希望用户能够从 C 创建 Ustr 并将其传递给您的 API,请将 ustr_extern.rs 添加到您的 crate 中,并使用 include/ustr.hinclude/ustr.hpp 进行函数声明。

变更日志

自 0.10 以来更改

  • 实际上将 "serialization" 功能的名称重命名为 "serde"

自 0.9 以来更改

并感谢 virtualritz

  • Ustr::get_cache() 已更名为 cache()

  • 所有依赖项都已升级到最新版本

  • 移除了所有功能(有良好的默认值)除 serialization

  • serialization 功能已更名为 serde

  • ustr 现在使用 Rust 2021

自 0.8 以来更改

  • 添加 existing_ustr 函数(由 macprog-guy 贡献)

    这个想法是在 Ustr 已经存在的情况下才创建一个 Ustr。这在使用不受信任的用户输入(例如来自 Web 服务器或 API)创建 Ustr 时特别有用。在这种情况下,通过在每次调用中提供不同的值,我们最终会消耗越来越多的内存,最终耗尽(DoS)。

  • 添加 Ord 实现(由 zigazeljko 贡献)

  • 内联了一些简单函数(由 g-plane 贡献)

  • 修复了测试,改为锁定而不是依赖于 RUST_TEST_THREADS=1(由 kornelski 贡献)

  • 修复了测试,以便在启用时正确处理 serialization 功能(由 kornelski 贡献)

  • 添加了对分配器中潜在分配失败检查(由 kornelski 贡献)

  • 添加 FromStr 实现(由 martinmr 贡献)

  • rustfmt.toml 添加到仓库

自 0.7 以来的更改

  • 更新依赖项

    已更新 parking_lotahash 的版本。

  • 使用 NonNull 进行空间优化

    内部指针现在是 NonNull,以利用 Option 等中的布局优化。

  • 添加 as_cstr() 方法

    添加了 as_cstr(&self) -> std::ffi::CStr 以便于与依赖于 CStr 的 API 进行交互。

自 0.6 以来的更改

  • 为 Ustr 导入 Ord

    因此现在您可以按字典顺序对 Vec 中的 Ustr 进行排序。

自 0.5 以来的更改

  • &str 添加 From<Ustr>

    impl 使将 Ustr 传递给期望 Into<&str> 的方法的函数变得更加容易。

自 0.4 以来的更改

  • 添加了对 32 位支持

    已移除对64位系统的限制,并修复了与指针算术相关的错误。感谢agaussman提出这个问题

  • 重新启用了Miri泄漏检查

    感谢RalfJung指出Miri现在忽略了“从静态中泄漏”

  • PartialOrd现在是字典序的

  • 感谢macprog-guy实现了通过延迟到&str来实现的PartialOrd的PR。这将比之前仅进行指针比较的派生实现慢,但要好得多令人惊讶

自0.3以来的变更

  • 将Miri添加到CI测试中

    Miri会检查代码的不安全部分以防止某些类型的未定义行为。

  • 将默认哈希器切换到ahash

    Ahash是一个快速的非加密纯Rust哈希器。纯Rust很重要,以便能够运行Miri,ahash是我能找到的运行速度最快的。旧版本的fasthash/cityhash可以通过启用--features=hashcity来使用

自0.2以来的变更

  • 添加了Serde支持

    Ustr现在可以在启用--features=serialization的情况下使用Serde进行序列化。如果真的需要,全局字符串缓存也可以进行序列化。

  • 将默认的同步方式切换到parking_lot::Mutex

    由于自旋锁最近受到了一些批评,因此字符串缓存现在使用parking_lot::Mutex作为默认的同步原语。spin::Mutex仍然可以通过--features=spinlock特性门使用,如果你真的想要那个额外的5%速度。

  • 清理了unsafe

    更好地记录了不安全块的归纳,并用checked_add()及其朋友替换了一些盲目的添加,以避免潜在的(但非常不可能的)溢出。

  • string-cache相比

    string-cache提供了一种全局缓存,可以在编译时以及在运行时创建。缓存中的动态字符串似乎被引用计数,因此当它们不再使用时将被释放,而Ustr永远不会被删除。

    创建一个string_cache::DefaultAtom比创建一个Ustr慢得多,尤其是在多线程环境中。另一方面,如果你可以在编译时将所有你的Atom放入二进制文件中,这就不会成为问题。

  • string-interner相比

    string-interner为你提供单独的Interner对象来操作,而不是全局缓存,这可能更灵活。创建它的速度比string-cache快,但仍然比Ustr慢得多。

速度

Ustr的创建速度比string-internerstring-cache快得多。创建100,000个循环的~20,000个路径字符串的副本

/cgi-bin/images/admin
/modules/templates/cache
/libraries/themes/wp-includes
... etc.

raft bench

为什么?

在某些类型的应用程序中,通常使用字符串作为标识符,但实际上并不对它们进行任何处理。引用OIIO的ustring文档

与标准字符串相比,Ustr有几个优点

  • 每个单独的Ustr都非常小——事实上,我们保证一个Ustr的大小和内存布局与普通的 *u8 相同。

  • 存储是经济的,因为在整个程序的生命周期中,每个唯一的字符序列只有一个分配的副本。

  • 从一个 Ustr 赋值到另一个,只是指针的复制;没有分配,没有字符复制,没有引用计数。

  • 相等性测试(字符串是否包含相同的字符)是单个操作,即指针的比较。

  • 内存分配仅在第一次从原始字符构建新的 Ustr 时发生 —— 之后相同字符串的构建只是在规范字符串集中找到它,但不需要分配新的存储。销毁一个 Ustr 是微不足道的,因为没有重新分配,因为规范版本仍然在集合中。因此,不会有用户代码错误导致内存泄漏。

    但是也存在一些问题。规范字符串永远不会从表中释放。所以在某种程度上,所有字符串“泄漏”,但它们只泄漏程序遇到的每个唯一字符串的一个副本。创建一个 Ustr 比在单个线程上使用 String::from() 慢,如果尝试在多线程中在紧密循环中创建许多 Ustr,由于全局缓存的锁定竞争,性能会更差。

总的来说,Ustr 是一个非常棒的字符串表示形式

  • 如果你倾向于有(相对较少的)唯一字符串,但有许多这些字符串的副本;

  • 如果你倾向于反复创建相同的字符串,并且单个唯一字符序列在整个程序生命周期中只使用一次的情况相对较少; —— 如果你的最常见字符串操作是赋值和相等性测试,并且你希望它们尽可能快;

  • 如果你进行相对较少的字符级字符串组装、字符串连接或其他“字符串操作”(除了相等性测试)。

Ustr 并不好

  • 如果你的程序在整个生命周期中每个字符序列的副本很少;

  • 如果你的程序在其生命周期中倾向于生成大量独特的字符串,每个字符串都只使用一次然后丢弃,永远不再需要;

  • 如果你不需要做很多字符串赋值或相等性测试,但有很多更复杂的字符串操作。

安全和兼容性

这个包包含大量的不安全代码,但已进行检查并得到了很好的文档。它还作为 CI 流程的一部分通过 Miri 运行。

我在64位系统上经常使用它,它也在32位系统上通过了 Miri,但32位不是定期检查的。如果您想在32位上使用它,请确保运行 Miri 并在发现任何问题时提出问题。

许可证

BSD+ 许可证

版权所有 © 2019—2020 Anders Langlands

在满足以下条件的情况下,允许重新分发和使用源代码和二进制形式,无论是否修改:

  1. 源代码的重新分发必须保留上述版权声明、本条款和以下免责声明。

  2. 二进制形式的重新分发必须复制上述版权声明、本条款和以下免责声明在随附的文档和其他材料中。

在本许可证的条款和条件下,每个版权所有者和贡献者特此授予根据本许可证获得权利的人一项永久性、全球性、非独占性、无偿、免版税、不可撤销(但以不满足本许可证的条件为限)的专利许可,用于制造、委托制造、使用、提供销售、销售、进口以及以其他方式转让此软件,其中此类许可仅适用于以下专利权要求,无论是否已经获得或今后获得,是否可由该版权所有者或贡献者许可,且必然被以下内容所侵犯:

(a) 他们的贡献(版权所有者的许可版权和非版权性增加的贡献者,以源代码或二进制形式);或者

(b) 将其贡献与版权所有者或贡献者添加该贡献的作品结合在一起,如果在该贡献添加时,此类添加必然导致结合被侵权。专利许可不适用于包括贡献在内的任何其他结合。

除上述明确声明外,本许可证下不授予任何版权所有者或贡献者的权利或许可,无论是明确授予、暗示、禁止还是其他方式。

免责声明

本软件由版权所有者和贡献者“按原样”提供,任何明示或暗示的保证,包括但不限于适销性和特定用途的适用性保证均予以放弃。在任何情况下,版权所有者或贡献者均不对任何直接、间接、偶然、特殊、示范性或后果性的损害(包括但不限于替代商品或服务的采购;使用、数据或利润的损失;或业务中断)承担责任,无论损害原因是什么,也不论基于何种责任理论,包括合同、严格责任或侵权(包括疏忽或不计)责任,即使被告知可能发生此类损害。

包含从 OpenImageIO 端口移植的代码,BSD 3-clause 许可证。

包含 Max Woolf 的 Big List of Naughty Strings 的副本,MIT 许可证。

包含来自 SecLists 的某些字符串,MIT 许可证。

依赖关系

~1.1–7MB
~27K SLoC