1 个不稳定版本
0.1.0-dev.1 | 2023年12月15日 |
---|
在 内存管理 中排名第 558
每月下载量 2,380
用于 4 个 crates (通过 timely-container-master)
42KB
643 行
列化
一个实验性的列式竞技场
列化借用了来自 Abomonation 的名字,这是一个非常快且非常不安全的 Rust 序列化框架。在 Abomonation 的不安全性中,它托管了由 [u8]
切片支持的类型数据,这引起了关注对对齐、填充字节可见性以及可能我尚未了解的其他事物的担忧。
列化稍微好一些,因为它只维护类型分配 Vec<T>
,并且不会调用 mem::transmute
来更改类型。这并不意味着它是 安全的,只是意味着代码中有更少的地方可能是不安全的,一旦 Rust 的不安全性故事明朗化。
相反,列化通过将类型可能拥有的所有者分配(例如,一个 String
或一个 Vec<T>
用于其他类型 T
)合并成相对较少且较大的分配,并将这些类型中的指针重写为指向更大的分配来工作。这使得这些类型不适用于任何其他用途,而只能通过不可变引用使用。这也意味着列化非常不安全,目前只是一个实验。
示例用法
某些类型实现了Columnation
,这是一个Rust特质,它没有任何方法,但允许实例化一个ColumnStack
,你可以将类型的实例复制到其中。然后,通过栈的Deref
实现,可以访问这些实例,该实现将栈呈现为一个&[Type]
。索引元素呈现为&Type
,原则上你可以使用这些引用执行所有常规操作,尽管实际上并没有实际的Type
作为它们的后盾。
// Some data are suitable for translation into columnar form.
let my_data = vec![vec![(0u64, vec![(); 1 << 40], format!("grawwwwrr!")); 32]; 32];
// Such data can be copied in to a columnar region.
let mut my_region = ColumnStack::default();
for _ in 0 .. 1024 {
my_region.copy(&my_data);
}
// The copying above is substantially faster than cloning the
// data, 21ms versus 198ms, when cloned like so:
let mut my_vec = Vec::with_capacity(1024);
for _ in 0 .. 1024 {
my_vec.push(my_data.clone());
}
// At this point, `my_region` has just tens of allocations,
// despite presenting as if a thousand records which would
// normally have a thousand allocations behind each of them.
assert_eq!(&my_region[..], &my_vec[..]);
测量
我选取了各种类型的记录,通常包含大约一千次分配,并将它们在容器中复制或克隆1024次,就像上面一样。以下是Rust的cargo bench
工具提供的基准时间,其中_clone
是克隆到向量中,而_copy
是复制到基于区域的容器中。
running 16 tests
test empty_clone ... bench: 835 ns/iter (+/- 114)
test empty_copy ... bench: 3,103 ns/iter (+/- 346)
test string10_clone ... bench: 108,914,118 ns/iter (+/- 7,864,343)
test string10_copy ... bench: 4,654,702 ns/iter (+/- 312,409)
test string20_clone ... bench: 59,302,789 ns/iter (+/- 8,014,183)
test string20_copy ... bench: 2,818,970 ns/iter (+/- 191,415)
test u32x2_clone ... bench: 1,920,494 ns/iter (+/- 198,815)
test u32x2_copy ... bench: 282,235 ns/iter (+/- 40,851)
test u64_clone ... bench: 1,951,842 ns/iter (+/- 129,288)
test u64_copy ... bench: 234,412 ns/iter (+/- 25,186)
test u8_u64_clone ... bench: 1,931,056 ns/iter (+/- 162,882)
test u8_u64_copy ... bench: 266,326 ns/iter (+/- 35,203)
test vec_u_s_clone ... bench: 120,642,691 ns/iter (+/- 9,124,488)
test vec_u_s_copy ... bench: 5,801,229 ns/iter (+/- 522,024)
test vec_u_vn_s_clone ... bench: 134,171,625 ns/iter (+/- 18,599,137)
test vec_u_vn_s_copy ... bench: 8,580,739 ns/iter (+/- 451,180)
在每种情况下,除了故意简单的empty
情况外,_copy
版本都比_clone
版本明显更快。这在某种程度上是有道理的,因为在_copy
情况下,我们可以跨运行重用所有的分配,而在_clone
情况下,只有向量的脊柱被重用(我们可以尝试更复杂的缓冲池,但我们没有这样做)。
empty
情况有一个有趣的故事。当我们把多个Vec<()>
分配合并到一个Vec<()>
中时,我们引入了维护该向量的长度和容量的成本。它应该是一个“无界”的零大小类型,但实际上我们需要验证它不会达到usize::MAX
。而empty_clone
情况不需要这样做,并优化为memcpy
,而empty_copy
情况必须在每次插入时检查容量。
描述
像Vec<String>
这样的类型将使用类似于以下内存使用方式编码在Columnation中
struct Roughly {
/// Where each `Vec<String>` record is put.
records: Vec<Vec<String>>,
/// All of the `String` in all of the records.
strings: Vec<String>
/// All of the bytes in all of the strings in all of the records.
bytes: Vec<u8>,
}
事实上,每个成员都是一个分配序列,因为我们无法重新调整我们的任何分配(除非进行大量工作),我们而是创建新的几何级增加的分配。该结构具有特殊的Drop
实现,它释放了三个分配包,但不递归调用其成员的Drop
实现(以避免尝试释放指向分配内部指针,我们知道这些指针是无效的)。
更通用的类型需要描述如何安全地存储它们可能拥有的所有权分配。它们实现的Columnation
特质允许它们指定一个关联类型,InnerRegion
,这是吸收类型所拥有的分配。
与Abomonation的关系
Columnation的意图与Abomonation相似,即在不需要实际所有类型存在的情况下,向Rust类型提供不可变引用,但它们的内存布局不同。
Abomonation会将每个记录连续序列化,而Columnation“序列化”每个记录后面的分配,分别进入不同的静态类型缓冲区。当您想调查整个记录时,Abomonation具有引用局部性的优势,而Columnation在您经常只需要记录背后分配的子集时,具有内存紧凑性的优势。
安全性
推动代码的一部分目标是让一些人来看看。它可能是安全的,但我对确保安全所需的指针别名规则并不清楚(也不清楚它们在哪里记录)。此外,可能完全存在错误,但我应该修复它们。