7 个版本
0.3.0 | 2023年6月22日 |
---|---|
0.2.3 | 2023年4月24日 |
0.1.1 | 2023年4月24日 |
#1037 在 解析器实现 中
30KB
372 行
TinyBuf
针对不可变所有权的优化容器。存储 Arc<dyn AsRef<[u8]>>
、Box<[u8]>
、Box<dyn AsRef<[u8]>>
、Vec<u8>
和 [u8; N]
(对于小 N
值),在一个单一类型中(没有泛型)以直接移动的方式进行存储——没有额外的堆分配或复制。
所有 TinyBuf 值都是切片,因此您可以直接获得所有 标准切片方法。TinyBuf 还实现了
AsRef<[u8]>
Clone
Hash
PartialEq
和Eq
PartialOrd
和Ord
serde::Serialize
和serde::Deserialize
(带有可选的serde
功能)
目录
用法
将其添加到依赖项中
cargo add tinybuf
在类型位置中使用不可变的 Vec<u8>
替换为 TinyBuf
。
用例
TinyBuf 适用于以下场景
- 您想提供或接受不同类型和长度的不可变所有者字节数据。
- 您想提供或接受长度小于等于
N <= 23
的不可变[u8; N]
。
以下是一些演示 TinyBuf 用例的示例。为了说明目的,让我们假设我们正在构建一个内存中的键值存储,并关注值。
接受数据
通常,如果您想接受一些所有者字节数据,您会使用 Vec
struct MyKV {
entries: HashMap<&'static str, Vec<u8>>,
}
impl MyKV {
pub fn set(&mut self, key: &'static str, value: Vec<u8>) {
self.entries.insert(key, value);
}
}
然而,这对于有数组、boxed slice 等字节的调用者来说并不理想。他们必须重新分配到新的 Vec
let kv: MyKV = MyKV::new();
// Fast, direct move, no reallocation or copying.
kv.set("vec", vec![0, 4, 2]);
// Requires allocating a new Vec and copying into it.
kv.set("boxslice", Box::new(&[0, 4, 2]).to_vec());
// Requires allocating a new Vec and copying into it.
kv.set("array", [0, 4, 2].to_vec());
所以下一个方法可能就是使用泛型
struct MyKV<V: AsRef<[u8]>> {
entries: HashMap<&'static str, V>,
}
impl<V: AsRef<[u8]>> MyKV<V> {
pub fn set(&mut self, key: &'static str, value: V) {
self.entries.insert(key, value);
}
}
如果值类型始终相同,则此方法有效。但也许您想接受不同类型的字节——也许不同的用户或子系统使用不同的类型,或者有时使用数组来存储小数据以避免堆分配。如果是这样,则上述泛型方法不可行。相反,另一种方法可能是使用boxed特质
struct MyKV {
entries: HashMap<&'static str, Box<dyn AsRef<[u8]>>>,
}
impl MyKV {
pub fn get(&self, key: &'static str) -> Option<&Box<dyn AsRef<[u8]>>> {
self.entries.get(key)
}
pub fn set(&mut self, key: &'static str, value: impl AsRef<[u8]>) {
self.entries.insert(key, Box::new(value));
}
}
虽然这种方法允许在不复制到 Vec
的情况下存储不同类型的字节,但装箱实际上逆转了这种优化,因为调用 Box::new
总是将它移动到堆上,即使对于 Box<[u8]>
值也是如此。这对于已经位于堆上的数据可能不是太糟糕,因为只有结构体本身而不是数据本身是堆分配的,但它仍然需要分配一个相对较小的堆。
这就是 TinyBuf 的作用所在
struct MyKV {
entries: HashMap<&'static str, TinyBuf>,
}
impl MyKV {
pub fn get(&self, key: &'static str) -> Option<&TinyBuf> {
self.entries.get(key)
}
pub fn set<D: Into<TinyBuf>>(&mut self, key: &'static str, value: D) {
self.entries.insert(key, value);
}
}
let kv: MyKV = MyKV::new();
// Fast, direct move, stored inline in TinyBuf, no heap allocation or copying of data.
kv.set("boxslice", Box::new(&[0, 4, 2]));
kv.set("static", b"my static value");
kv.set("vec", vec![0, 4, 2]);
// For small arrays: fast, direct move, stored inline in TinyBuf, no heap allocation or copying of data.
kv.set("smallarray", [0, 4, 2]);
// If an array is too big, heap allocation is required, so you must explicitly opt in.
kv.set("largearray", TinyBuf::from_slice(&[0u8; 24]));
// If you already have a Box<dyn AsRef[u8]> or Arc<dyn AsRef<[u8]>> value, TinyBuf will accept it too.
kv.set("boxdyn", my_boxed_asref);
kv.set("arc", my_arced_asref);
// If you have some other type that TinyBuf doesn't support and cannot convert it to one of the supported types, you can created a boxed trait value, which will require heap allocation.
kv.set("custom", Box::<dyn AsRef[u8]>::new(my_asrefable_value));
提供数据
如先前的 MyKV
示例所示,TinyBuf 的一个好处是能够在不进行堆分配或复制的情况下,接受和存储许多不同类型的所有者字节切片。
另一个优点是在从切片读取并希望将其转换为所有者值时,能够动态利用小的内联数组。
fn read_from_vm_memory(&self, start: usize, end: usize) -> Vec<u8> {
// This will always heap allocate, even if the length is tiny.
self.mem[start..end].to_vec()
}
fn read_from_vm_memory(&self, start: usize, end: usize) -> TinyBuf {
// This will only heap allocate if the length is large.
TinyBuf::from_slice(&self.mem[start..end])
}
请注意,在这两种情况下,由于我们想要拥有数据,所以数据都被复制了,但在第二个 TinyBuf 变体中,如果长度小于等于 23,则不会发生堆分配。这种优化是自动且透明的,不需要额外的条件代码。
类型
为这些类型实现了 From<T>
,以便于快速转换。
请注意,这些总是会执行快速移动操作。如果您希望 TinyBuf 尝试在长度足够小的情况下将数据复制到内联 TinyBuf::Array*
变体中,并执行标准的 .into()
操作,请使用 TinyBuf::copy_if_small
。
源类型 | 注释 |
---|---|
[u8; N] 其中 N <= 23 |
内联存储,没有堆分配或数据复制。 |
Arc<dynAsRef<[u8]> + 发送 + 同步 + 静态> |
|
盒子<dynAsRef<[u8]> + 发送 + 同步 + 静态> |
|
盒子<[u8]> |
这是一个独立的变体,因为将其转换为 Box<dyn AsRef<u8>> 需要进一步的封装。 |
&静态 [u8] |
|
Vec<u8> |
这是一个独立的变体,因为 into_boxed_slice 可能会重新分配。容量必须小于 2^56 。 |
克隆
当你调用 TinyVec::clone
时,它将尝试执行最佳复制。
- 如果长度小于或等于 23,数据将被复制到一个新的
TinyBuf::Array*
变体中,即使它目前不是一个TinyBuf::Array*
。 - 如果是
TinyBuf::Arc
,它将使用Arc::clone
以低成本复制。 - 否则,它将通过封装(即堆分配)复制到一个新的
TinyBuf::BoxSlice
中。
比较
Vec<u8>
TinyBuf 是一个不可变数据的容器,不支持任何变异方法,与 Vec<u8>
不同。
TinyBuf 在长度足够小的情况下将避免堆分配,而 Vec<u8>
除非是零字节,否则总是进行堆分配。
SmallVec
和 TinyVec
TinyBuf 仅持有 u8
值,不支持变异。
TinyBuf 不需要提前声明长度或容量,并将动态优化长度小于或等于 23 的数组。
TinyBuf 可以从各种类型转换,不仅仅是 Vec<u8>
,并且只使用移动,不进行数据复制。
一般来说,TinyBuf 更像是不可变 Box<dyn AsRef<[u8]>>
的替代品,而不是可变 Vec<u8>
。
&[u8]
如果可能使用切片,则首选。然而,当不可能使用时,通常是由于生命周期限制,TinyBuf
提供了几乎相同的功能。
Cow<u8>
Cow
仍然具有生命周期,因为它可能包含借用数据,所以它不像 Vec<u8>
和 TinyBuf 那样是所有者。
依赖项
~1.6–2.4MB
~46K SLoC