我有一个大型固定大小的u32
可变大小数组。大多数第二维数组都是空的(即第一个数组将被稀疏地填充)。我认为Vec
是两个维度(Vec<Vec<u32>>
)最合适的类型。因为我的第一个数组可能非常大,我想找到最节省空间的方式来代表这个。
我看到两个选项:
我可以使用Vec<Option<Vec<u32>>>
。我猜测Option
是一个标记联合,这会导致每个单元格sizeof(Vec<u32>)
向上舍入到标记的下一个单词边界。
我可以直接将Vec::with_capacity(0)
用于所有单元格。在使用之前,空Vec
是否分配零堆?
哪种节省空间最方便?
答案 0 :(得分:3)
实际上,Vec<Vec<T>>
和Vec<Option<Vec<T>>>
都具有相同的空间效率。
A Vec
contains a pointer that will never be null,因此编译器足够聪明,可以识别出Option<Vec<T>>
的情况,它可以通过在指针字段中输入0来表示None
。 What is the overhead of Rust's Option type?包含更多信息。
指针指向的后备存储怎么样?使用Vec::new
或Vec::with_capacity(0)
创建A Vec
doesn't allocate时(与第一个相同的链接);在这种情况下,它使用一个特殊的非空“空指针”。 Vec
仅在push
某事物或者强制分配时才在堆上分配空间。因此,Vec
本身及其后备存储使用的空间是相同的。
答案 1 :(得分:1)
Vec<Vec<T>>
是一个不错的起点。每个条目花费3个指针,即使它是空的,对于填充条目,可能会有额外的每个分配开销。但是,根据您愿意做出的权衡取舍,可能会有更好的解决方案。
Vec<Box<[T]>>
这会将条目的大小从3个指针减少到2个指针。缺点是更改框中的元素数量既不方便(转换为Vec<T>
)又更昂贵(重新分配)。HashMap<usize, Vec<T>>
如果外部集合足够稀疏,这可以节省大量内存。缺点是更高的访问成本(散列,扫描)和更高的每元素内存开销。如果集合只填充一次并且您从未调整内部集合的大小,则可以使用拆分数据结构:
这不仅将每个条目的大小减少到1个指针,而且还消除了每个分配的开销。
struct Nested<T> {
data: Vec<T>,
indices: Vec<usize>,// points after the last element of the i-th slice
}
impl<T> Nested<T> {
fn get_range(&self, i: usize) -> std::ops::Range<usize> {
assert!(i < self.indices.len());
if i > 0 {
self.indices[i-1]..self.indices[i]
} else {
0..self.indices[i]
}
}
pub fn get(&self, i:usize) -> &[T] {
let range = self.get_range(i);
&self.data[range]
}
pub fn get_mut(&mut self, i:usize) -> &mut [T] {
let range = self.get_range(i);
&mut self.data[range]
}
}
为了节省更多内存,您可以将索引减少到u32
,每个集合限制为40亿个元素。