bigywater.tech
Rust · 56

Vec 扩容机制

往 Vec 里 push 看起来是 O(1),背后其实是「装满就换更大的缓冲区」。这篇把容量变化序列、不同元素大小的起始容量、分配器真实收到的调用次数,以及搬家带来的借用限制都真实跑了一遍。

×2
装满之后 Vec 的扩容倍数(4→8→…→2048)
1 + 8
逐个 push 1000 个 u64 的 alloc + realloc 次数
1 + 0
with_capacity(1000) 的 alloc + realloc 次数

容量怎么变

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    new: len=0 cap=0
    push 第    1 个后 cap    0 ->    4
    push 第    5 个后 cap    4 ->    8
    push 第    9 个后 cap    8 ->   16
    push 第   17 个后 cap   16 ->   32
    push 第   33 个后 cap   32 ->   64
    push 第   65 个后 cap   64 ->  128
    push 第  129 个后 cap  128 ->  256
    push 第  257 个后 cap  256 ->  512
    push 第  513 个后 cap  512 -> 1024
    push 第 1025 个后 cap 1024 -> 2048

元素大小与起始容量

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    u8         size_of=1     第一次 push 后 cap=8
    u32        size_of=4     第一次 push 后 cap=4
    u64        size_of=8     第一次 push 后 cap=4
    [u64;16]   size_of=128   第一次 push 后 cap=4
    1000 字节    size_of=1000  第一次 push 后 cap=4
    1024 字节    size_of=1024  第一次 push 后 cap=4
    1025 字节    size_of=1025  第一次 push 后 cap=1
    5000 字节    size_of=5000  第一次 push 后 cap=1
    Vec<()> cap = 18446744073709551615 (usize::MAX = 18446744073709551615)

这些起始容量是在当前工具链(rustc 1.98.1)上量到的数字,标准库的实现细节可能随版本变化。

分配器看到的次数

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    逐个 push 1000 次(Vec::new): alloc 1 realloc 8 最终 cap 1024
    with_capacity(1000) 再 push 1000 次: alloc 1 realloc 0 最终 cap 1000
    vec![1,2,3]: cap 3 ; reserve(10) 后 cap 13 (realloc 1 次)
    vec![1,2,3] 再 reserve_exact(10): cap 13
    cap 8 已满再 reserve(1): cap 16 ; 同样情形 reserve_exact(1): cap 9
    with_capacity(100) 存 10 个: cap 100 ; shrink_to_fit 后 cap 10

Box 与堆分配的基础见Box<T> 与堆分配。

搬家与借用

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    cap 4 时地址 0x101581300
    push 第 5 个后 cap 8 地址 0x101581170,地址变了
    cap 8 里 push 第 5 个:地址没变
  源码:
      1 | fn main() {
      2 |     let mut v = vec![1, 2, 3];
      3 |     let first = &v[0];
      4 |     v.push(4);
      5 |     println!("{}", first);
      6 | }
  编译:失败
    error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
     --> <实验目录>/src/u56e.rs:4:5
      |
    3 |     let first = &v[0];
      |                  - immutable borrow occurs here
    4 |     v.push(4);
      |     ^^^^^^^^^ mutable borrow occurs here
    5 |     println!("{}", first);
      |                    ----- immutable borrow later used here
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0502`.

借用规则见可变借用为什么不能同时存在。

分步演示

Vec 扩容
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。容量序列在 release 模式下记录;分配次数用 #[global_allocator] 包装 System 计数
    • 没有验证:其它版本标准库的增长策略、realloc 是否原地扩展(只量了地址变化,没区分系统是否原地)、Vec::shrink_to、try_reserve、大容量(接近 isize::MAX)的错误
    京ICP备2026067502号-1京公网安备11010502063487号