#binary #string #stack #rc #small #send-sync

abin

一个用于处理二进制和字符串的库。该库尽可能避免堆分配/内存复制,通过自动选择合理的策略:对小二进制使用栈;静态生命周期二进制或引用计数。

4 个版本

0.1.6 2020年10月8日
0.1.5 2020年10月7日
0.1.1 2020年10月6日
0.1.0 2020年10月6日

#334 in 内存管理

MIT/Apache

170KB
3.5K SLoC

abin

Crates.io Docs.rs CI Coverage Status Rust GitHub Template

一个用于处理二进制和字符串的库。该库尽可能避免堆分配/内存复制,通过自动选择合理的策略:对小二进制使用栈;静态生命周期二进制或引用计数。它易于使用(没有生命周期;二进制类型有大小),Send + Sync 是可选的(因此没有同步开销),提供可选的 serde 支持,并具有类似字符串和二进制的 API。可以实现自定义二进制/字符串类型以进行精细调整。

提供类似功能的库

许可协议

许可协议为以下之一

任选其一。

详细信息

使用说明

[dependencies]
abin = "*"
use std::iter::FromIterator;
use std::ops::Deref;

use abin::{AnyBin, AnyStr, Bin, BinFactory, NewBin, NewStr, Str, StrFactory};

#[test]
fn usage_basics() {
    // static binary / static string
    let static_bin: Bin = NewBin::from_static("I'm a static binary, hello!".as_bytes());
    let static_str: Str = NewStr::from_static("I'm a static binary, hello!");
    assert_eq!(&static_bin, static_str.as_bin());
    assert_eq!(static_str.as_str(), "I'm a static binary, hello!");
    // non-static (but small enough to be stored on the stack)
    let hello_bin: Bin = NewBin::from_iter([72u8, 101u8, 108u8, 108u8, 111u8].iter().copied());
    let hello_str: Str = NewStr::copy_from_str("Hello");
    assert_eq!(&hello_bin, hello_str.as_bin());
    assert_eq!(hello_str.as_ref() as &str, "Hello");

    // operations for binaries / strings

    // length (number of bytes / number of utf-8 bytes)
    assert_eq!(5, hello_bin.len());
    assert_eq!(5, hello_str.len());
    // is_empty
    assert_eq!(false, hello_bin.is_empty());
    assert_eq!(false, hello_str.is_empty());
    // as_slice / as_str / deref / as_bin
    assert_eq!(&[72u8, 101u8, 108u8, 108u8, 111u8], hello_bin.as_slice());
    assert_eq!("Hello", hello_str.as_str());
    assert_eq!("Hello", hello_str.deref());
    assert_eq!(&hello_bin, hello_str.as_bin());
    // slice
    assert_eq!(
        NewBin::from_static(&[72u8, 101u8]),
        hello_bin.slice(0..2).unwrap()
    );
    assert_eq!(NewStr::from_static("He"), hello_str.slice(0..2).unwrap());
    // clone
    assert_eq!(hello_bin.clone(), hello_bin);
    assert_eq!(hello_str.clone(), hello_str);
    // compare
    assert!(NewBin::from_static(&[255u8]) > hello_bin);
    assert!(NewStr::from_static("Z") > hello_str);
    // convert string into binary and binary into string
    let hello_bin_from_str: Bin = hello_str.clone().into_bin();
    assert_eq!(hello_bin_from_str, hello_bin);
    let hello_str_from_bin: Str = AnyStr::from_utf8(hello_bin.clone()).expect("invalid utf8!");
    assert_eq!(hello_str_from_bin, hello_str);
    // convert into Vec<u8> / String
    assert_eq!(
        Vec::from_iter([72u8, 101u8, 108u8, 108u8, 111u8].iter().copied()),
        hello_bin.into_vec()
    );
    assert_eq!("Hello".to_owned(), hello_str.into_string());
}

值得注意的结构体、特质和类型及命名

接口

  • Bin: 二进制(它是一个结构体)。
  • SBin: 同步二进制(它是一个结构体)。
  • Str: 字符串 (type Str = AnyStr<Bin>)
  • SStr: 同步字符串 (type SStr = AnyStr<SBin>)。

默认实现提供工厂

  • NewBin: 创建 Bin
  • NewSBin: 创建 SBin
  • NewStr:创建Str
  • NewSStr:创建SStr

参见

  • AnyBin:由BinSBin实现的特性。
  • AnyStr:参见StrSStr;由BinSBin支持的字符串。
  • BinFactory:由NewBinNewSBin实现的工厂特性。
  • StrFactory:由NewStrNewSStr实现的工厂特性。

学习

查看示例测试

成熟度

它相当年轻(开发始于2020年10月)。主要功能已实现。我可能要做的事情

  • API优化。
  • 使用loom的测试/更多测试。
  • 优化。
  • 基准测试。

问题和答案

已经有其他具有类似功能的crates,为什么还需要另一个?/ 特性

这个crate提供了一些在其他crate中找不到的特性(或者不是所有特性)

  • 提供对二进制和字符串的支持;字符串的API与二进制API紧密匹配。
  • 在不需要时,二进制/字符串不进行同步(同步是可选的)。
  • 允许自定义实现。
  • 小的二进制/字符串存储在栈上。
  • 支持serde零分配反序列化到所有者类型(在某些情况下)。
  • 高效的克隆(通常是零分配/零复制)。
  • 高效的切片到所有者类型(从Bin/StrBin/Str的切片)(通常是零分配/零复制)。
  • 保证零分配/零复制的借用切片(从Bin/Str&[u8]/&str的切片)。
  • 提供用于映射/serde支持的一切。

为什么是NewBinNewStr?这是什么?

为什么使用let string = NewStr::from_static("Hello")而不是仅仅使用let string = Str::from_static("Hello")(或者为From<&str> for Str实现)?这是因为决定将接口与实现解耦。其中Str是接口,而NewStr是内置实现的工厂。这个库旨在可扩展;你可以提供自己的实现,以适应你的特定需求。

默认实现NewBin / NewStr是如何工作的?

  • 小的二进制数据存储在栈上。最多3 * sizeof(word) - 1字节;在一个64位平台上这是23字节。例如,字符串Hello, world!只需要13字节,并且可以轻松地存储在栈上。
  • 静态二进制数据仅仅是实际数据的指针(因此仅在栈上)。
  • 较大的二进制数据通常使用引用计数(*1:有一种调整可以改变这种行为,请参阅GivenVecConfig)。引用计数器存储在向量数据内部。这有以下优点
    • 可以从Vec<u8>创建一个Bin而不进行分配(如果Vec<u8>还有空间留给引用计数器)——这是使用Rc<[u8]>所无法实现的。
    • ...同时(与Rc<Vec<u8>>不同)不会引入第二次间接引用。

NewBinNewSBin之间的唯一区别是引用计数的二进制数据:由NewSBin创建的SBin具有同步的引用计数器(AtomicUsize)。

注意:相同的声明也适用于字符串(因为字符串由二进制实现支持)。

哪些操作是不需要分配/零拷贝的?

它没有在文本中记录——当然,这取决于实现……但对于默认实现(NewBin/NewSBin/NewStr/NewSStr)),有一个测试,请参阅no_alloc_guarantees.rs

还可以查看这两个测试以确保单次分配保证

我想编写自己的实现,该如何做?

目前没有文档 - 但您可以使用默认实现进行参考。它在模块 implementation 中。

为什么是 Boo 而不是 Cow

Cow 需要 where B: 'a + ToOwned。由于实现与接口分离,这在这个包中不起作用。比如说我们有一个 &[u8](借用),要将其转换为所有者(BinSBin),则需要知道实现。我不想让 Cow 包含有关实现的信息。

BinStr 不大(栈大小)吗?

BinStr 的大小为 4 个词,并且是字对齐的。是的,它不是很小 - 但作为参考,一个 Vec<u8> 也需要 3 个词(指针、长度和容量)。

什么是重新集成?

比如说我们有这样一段代码(伪代码)

let large_binary_from_network : Vec<u8> = <...>;
let bin = NewBin::from_given_vec(large_binary_from_network);
let slice_of_that_bin : &[u8] = &bin.as_slice()[45..458];

// it's now possible to re-integrate that `slice_of_that_bin` into the `bin` it was sliced from.
// re-integration converts the borrowed type `&[u8]` (`slice_of_that_bin`) into an owned
// type (`Bin`) without memory-allocation or memory-copy.
let bin_re_integrated : Bin = bin.try_re_integrate(slice_of_that_bin).unwrap();

如果您想使用 serde 将序列化数据反序列化为所有者(不使用 Boo),这很有用。在反序列化类型时,我们从 serde 获取 slice_of_that_bin;使用重新集成,我们可以获得一个所有者二进制文件(Bin)而不进行分配。

技术细节:它会检查 slice_of_that_bin 是否位于 bin 的内存范围内;如果是,则将 bin 的引用计数增加一个,然后返回的二进制文件(bin_re_integrated)就是 bin 的切片引用。

命名 abin

这个名字来源于特质 AnyBin

基准测试

有关详细信息,请参阅 abin-benchmark 包。

cd benchmark
cargo bench
cargo test

基准测试是针对以下实现进行的

  • BytesBenchStr:使用 bytes 包。总的来说,这个实现与 abin 的表现相似(内存和性能;abin 分配得少一点)。
  • StdLibOptimized:使用 Arc<str> / Arc<String> / &'static str()(空)具有切片支持(手动优化)。这与 abin 内部所做的工作非常相似(除了在栈上存储小二进制文件)。总的来说,这个实现与 abin 的表现相似(abin 分配得少一点)。
  • StdLibStringOnly:始终使用 String(来自 Rust std-lib);没有优化。比 abin 差得多(速度慢得多,分配得多)。
  • StdLibArcStrOnly:始终使用 Arc<str>(来自 Rust std-lib);没有优化。比 abin 差得多(速度慢得多,分配得多)。

内存

abinStdLibOptimizedBytesBenchStr 稍好一些(特别是在分配数量上)——并且比 StdLibStringOnlyStdLibArcStrOnly 有所超越(参见分配的字节数,它为 380 MB 对 840 MB / 1.2 GB;分配的数量几乎是 10 倍)。

abin 的结果(使用 SStr

{ allocations: 3154, deallocations: 3154, reallocations: 12, bytes_allocated: 388755346,
bytes_deallocated: 388755346, bytes_reallocated: 11520 }

BytesBenchStr 的结果

{ allocations: 15454, deallocations: 15454, reallocations: 2212, bytes_allocated: 494895196,
bytes_deallocated: 494895196, bytes_reallocated: 520 }

StdLibOptimized 的结果

{ allocations: 18154, deallocations: 18154, reallocations: 12, bytes_allocated: 495272868,
bytes_deallocated: 495272868, bytes_reallocated: 14400 }

StdLibStringOnly 的结果

{ allocations: 21754, deallocations: 21754, reallocations: 1212, bytes_allocated: 848171274,
bytes_deallocated: 848171274, bytes_reallocated: 105981240 }

StdLibArcStrOnly 的结果

{ allocations: 34354, deallocations: 34354, reallocations: 1212, bytes_allocated: 1201859852,
bytes_deallocated: 1201859852, bytes_reallocated: 105978360 }

性能

如你所见,abinStdLibOptimizedBytesBenchStr 的表现大致相同(abin 略好,并且异常值更少);但几乎比 StdLibStringOnlyStdLibArcStrOnly 快两倍。

abin 的结果(使用 SStr

time:   [65.503 ms 67.157 ms 68.869 ms]
Found 1 outliers among 100 measurements (1.00%)

abin 的结果(使用 Str

time:   [71.207 ms 72.825 ms 74.546 ms]
Found 3 outliers among 100 measurements (3.00%)

BytesBenchStr 的结果

time:   [89.518 ms 91.279 ms 93.124 ms]
Found 13 outliers among 100 measurements (13.00%)

StdLibOptimized 的结果

time:   [78.972 ms 79.765 ms 80.556 ms]
Found 4 outliers among 100 measurements (4.00%)

StdLibStringOnly 的结果

time:   [118.53 ms 121.24 ms 124.15 ms]
Found 21 outliers among 100 measurements (21.00%)

StdLibArcStrOnly 的结果

time:   [118.36 ms 118.90 ms 119.56 ms]
Found 10 outliers among 100 measurements (10.00%)

贡献

除非你明确表示,否则,根据 Apache-2.0 许可证定义,任何有意提交以供你包含在工作中的贡献,均应如上所述双重许可,不附加任何额外条款或条件。

CONTRIBUTING.md

依赖

~250KB