Rust中Vec扩容时容量大小

有没有小伙伴拯救我,带我一起学习呀。

又是一个人的无聊的周末,打开极客时间,于是开始刷,养了很久的Rust视频课,第19节讲集合,在阅读doc文档时,有一部分提到了Vec扩容的问题。如下所示:

https://doc.rust-lang.org/std/vec/struct.Vec.html#capacity-and-reallocation

视频中讲的是:

容量是成倍增加,比如指定了一个容量为10的vec,那么扩容时会以10的倍数来自动增长,第一次扩容时就变成了20。

初一听好像确实没什么问题,和文档中说的一样。但是突然想到了,声明时指定容量的是方法with_capacity(usize),这时就是上面说的情况,但是使用new()方法声明时并没有指定容量,那容量应该是多少呢?说到就打开ide,写出如下代码开始测试:

1
2
3
4
5
6
7
fn main() {
    let mut vec = Vec::new();
    for _ in 0..20 {
        vec.push(42);
        dbg!(vec.capacity());
    }
}

截取部分打印结果,如下:

1
2
3
4
5
6
7
[src\main.rs:5] vec.capacity() = 4
...
[src\main.rs:5] vec.capacity() = 8
...
[src\main.rs:5] vec.capacity() = 16
[src\main.rs:5] vec.capacity() = 32
...

可以看到,初始是4,后面以4为倍数开始增长。就很疑惑,这个4是哪里来的呢,emmm,去看看源码吧(大家一般可以先看源码,我比较菜,就忘记了)。

首先查看vec.capacity()这个方法到底是在干什么。

1
2
3
4
5
6
7
8
9
//==========vec.rs
    pub fn capacity(&self) -> usize {
        self.buf.capacity()
    }

//==========raw_vec.rs
    pub fn capacity(&self) -> usize {
        if mem::size_of::<T>() == 0 { usize::MAX } else { self.cap }
    }

可以看到,其实就是返回了结构体里的cap字段,并没有更多的操作。然后查看new()方法,看看有啥操作没:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//==========vec.rs
	pub const fn new() -> Vec<T> {
        Vec { buf: RawVec::NEW, len: 0 }
    }

//==========raw_vec.rs
    pub const NEW: Self = Self::new();

    pub const fn new() -> Self {
        Self::new_in(Global)
    }

    pub const fn new_in(alloc: A) -> Self {
        // `cap: 0` means "unallocated". zero-sized types are ignored.
        Self { ptr: Unique::dangling(), cap: 0, alloc }
    }

发现还是没有任何操作,就是简单的设置了一下,在new()方法里,cap初始化就是0,没有出现任何和4这个数字有关系的代码。那相关代码只能在是push()里了:

1
2
3
4
5
6
7
//==========vec.rs
	pub fn push(&mut self, value: T) {
        if self.len == self.buf.capacity() {
            self.reserve(1);
        }
        ... //省略下面的操作代码
    }

这里的reserve()方法好像就是在做扩容,继续点进去查看,一路关键点下去:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//==========vec.rs
	pub fn reserve(&mut self, additional: usize) {
        self.buf.reserve(self.len, additional);
    }
//==========raw_vec.rs
    pub fn reserve(&mut self, len: usize, additional: usize) {
        handle_reserve(self.try_reserve(len, additional));
    }

    pub fn try_reserve(&mut self, len: usize, additional: usize) -> Result<(), TryReserveError> {
        if self.needs_to_grow(len, additional) {
            self.grow_amortized(len, additional)
        } else {
            Ok(())
        }
    }

最终,目光停留在了self.grow_amortized(len, additional)这句代码上,内心感觉这里应该就是关键了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    fn grow_amortized(&mut self, len: usize, additional: usize) -> Result<(), TryReserveError> {
        ... //省略部分无关代码
        let required_cap = len.checked_add(additional).ok_or(CapacityOverflow)?;

        let cap = cmp::max(self.cap * 2, required_cap);

        // Tiny Vecs are dumb. Skip to:
        // - 8 if the element size is 1, because any heap allocators is likely
        //   to round up a request of less than 8 bytes to at least 8 bytes.
        // - 4 if elements are moderate-sized (<= 1 KiB).
        // - 1 otherwise, to avoid wasting too much space for very short Vecs.
        // Note that `min_non_zero_cap` is computed statically.
        let elem_size = mem::size_of::<T>();
        let min_non_zero_cap = if elem_size == 1 {
            8
        } else if elem_size <= 1024 {
            4
        } else {
            1
        };
        let cap = cmp::max(min_non_zero_cap, cap);
        
        ... //省略部分无关代码
    }

可以看到,这里有这么大一段判断的代码,成倍增长是来自于cmp::max(self.cap * 2, required_cap),而初始大小则是下面那段if else,这个总共分了三种情况:

  • 首先如果元素的大小是1,则初始cap给了8
  • 否则如果元素的大小 小于等于1024(即1Kb),则初始cap是4
  • 最后,即元素大小大于1024的情况,初始cap给了1

所以上面最初的测试代码,因为数字42的默认类型是i32,而i32类型的大小是4,满足第二种情况,所以打印的初始值是4。

最后,测试一下第1,3种情况:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fn main() {
    use std::mem;

    let elem_size = mem::size_of::<bool>();
    dbg!(elem_size);
    let mut vec = Vec::new();
    vec.push(false);
    dbg!(vec.capacity());

    let elem_size = mem::size_of::<[bool; 1025]>();
    dbg!(elem_size);
    let mut vec = Vec::new();
    vec.push([false; 1025]);
    dbg!(vec.capacity());
}

运行结果如下:

1
2
3
4
[src\main.rs:5] elem_size = 1
[src\main.rs:8] vec.capacity() = 8
[src\main.rs:11] elem_size = 1025
[src\main.rs:14] vec.capacity() = 4

完美契合。

嗯,这就样水了一篇博客,主要还是害怕自己如果不写下这些简单的,就难以去写那些复杂的,有意义的文章,终归还是要偿还上学时不写作文的债呀。

updatedupdated2022-09-082022-09-08
加载评论