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 内存管理
170KB
3.5K SLoC
abin
一个用于处理二进制和字符串的库。该库尽可能避免堆分配/内存复制,通过自动选择合理的策略:对小二进制使用栈;静态生命周期二进制或引用计数。它易于使用(没有生命周期;二进制类型有大小),Send + Sync
是可选的(因此没有同步开销),提供可选的 serde 支持,并具有类似字符串和二进制的 API。可以实现自定义二进制/字符串类型以进行精细调整。
提供类似功能的库
许可协议
许可协议为以下之一
- Apache 许可证版本 2.0 (LICENSE-APACHE 或 https://apache.ac.cn/licenses/LICENSE-2.0)
- MIT 许可证 (LICENSE-MIT 或 http://opensource.org/licenses/MIT)
任选其一。
详细信息
使用说明
[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
:由Bin
和SBin
实现的特性。AnyStr
:参见Str
和SStr
;由Bin
或SBin
支持的字符串。BinFactory
:由NewBin
和NewSBin
实现的工厂特性。StrFactory
:由NewStr
和NewSStr
实现的工厂特性。
学习
查看示例测试
- usage_1_basics.rs:基本用法。
- usage_2_creating.rs:如何创建二进制和字符串。
- usage_3_builder.rs:如何使用构建器创建二进制/字符串。
- usage_4_operations.rs:二进制和字符串提供的操作,例如切片、将二进制转换为字符串以及将二进制/字符串转换为
Vec<u8>
和String
。 - usage_5_boo.rs:"借用的或拥有的"(boo),
Cow
的替代方案,适用于未实现ToOwned
的类型。 - usage_6_serde_boo.rs:使用
Boo
与serde。 - usage_7_serde_ri.rs:使用serde与重集成(也参见问题和答案)。
- usage_8_send_sync.rs:同步(
Send + Sync
)和非同步的二进制/字符串。 - usage_9_re_integration.rs:重集成(也参见问题和答案)
成熟度
它相当年轻(开发始于2020年10月)。主要功能已实现。我可能要做的事情
- API优化。
- 使用
loom
的测试/更多测试。 - 优化。
- 基准测试。
问题和答案
已经有其他具有类似功能的crates,为什么还需要另一个?/ 特性
这个crate提供了一些在其他crate中找不到的特性(或者不是所有特性)
- 提供对二进制和字符串的支持;字符串的API与二进制API紧密匹配。
- 在不需要时,二进制/字符串不进行同步(同步是可选的)。
- 允许自定义实现。
- 小的二进制/字符串存储在栈上。
- 支持serde零分配反序列化到所有者类型(在某些情况下)。
- 高效的克隆(通常是零分配/零复制)。
- 高效的切片到所有者类型(从
Bin
/Str
到Bin
/Str
的切片)(通常是零分配/零复制)。 - 保证零分配/零复制的借用切片(从
Bin
/Str
到&[u8]
/&str
的切片)。 - 提供用于映射/serde支持的一切。
为什么是NewBin
、NewStr
?这是什么?
为什么使用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>>
不同)不会引入第二次间接引用。
- 可以从
NewBin
和NewSBin
之间的唯一区别是引用计数的二进制数据:由NewSBin
创建的SBin
具有同步的引用计数器(AtomicUsize
)。
注意:相同的声明也适用于字符串(因为字符串由二进制实现支持)。
哪些操作是不需要分配/零拷贝的?
它没有在文本中记录——当然,这取决于实现……但对于默认实现(NewBin/
NewSBin/
NewStr/
NewSStr
)),有一个测试,请参阅no_alloc_guarantees.rs。
还可以查看这两个测试以确保单次分配保证
我想编写自己的实现,该如何做?
目前没有文档 - 但您可以使用默认实现进行参考。它在模块 implementation
中。
为什么是 Boo
而不是 Cow
?
Cow
需要 where B: 'a + ToOwned
。由于实现与接口分离,这在这个包中不起作用。比如说我们有一个 &[u8]
(借用),要将其转换为所有者(Bin
或 SBin
),则需要知道实现。我不想让 Cow
包含有关实现的信息。
Bin
和 Str
不大(栈大小)吗?
Bin
和 Str
的大小为 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
差得多(速度慢得多,分配得多)。
内存
abin
比 StdLibOptimized
和 BytesBenchStr
稍好一些(特别是在分配数量上)——并且比 StdLibStringOnly
和 StdLibArcStrOnly
有所超越(参见分配的字节数,它为 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 }
性能
如你所见,abin
、StdLibOptimized
和 BytesBenchStr
的表现大致相同(abin
略好,并且异常值更少);但几乎比 StdLibStringOnly
和 StdLibArcStrOnly
快两倍。
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 许可证定义,任何有意提交以供你包含在工作中的贡献,均应如上所述双重许可,不附加任何额外条款或条件。
依赖
~250KB