当大多数第二维空是空的时,什么是可存储空间最多的内存向量?

时间:2017-12-18 21:22:12

标签: memory rust

我有一个大型固定大小的u32可变大小数组。大多数第二维数组都是空的(即第一个数组将被稀疏地填充)。我认为Vec是两个维度(Vec<Vec<u32>>)最合适的类型。因为我的第一个数组可能非常大,我想找到最节省空间的方式来代表这个。

我看到两个选项:

  1. 我可以使用Vec<Option<Vec<u32>>>。我猜测Option是一个标记联合,这会导致每个单元格sizeof(Vec<u32>)向上舍入到标记的下一个单词边界。

  2. 我可以直接将Vec::with_capacity(0)用于所有单元格。在使用之前,空Vec是否分配零堆?

  3. 哪种节省空间最方便?

2 个答案:

答案 0 :(得分:3)

实际上,Vec<Vec<T>>Vec<Option<Vec<T>>>都具有相同的空间效率。

A Vec contains a pointer that will never be null,因此编译器足够聪明,可以识别出Option<Vec<T>>的情况,它可以通过在指针字段中输入0来表示NoneWhat is the overhead of Rust's Option type?包含更多信息。

指针指向的后备存储怎么样?使用Vec::newVec::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亿个元素。