bigywater.tech
Rust · 58

LinkedList 为什么通常不是首选

教科书里链表两端插入 O(1)、拼接 O(1),但在 Rust 里它很少是第一选择。这篇用分配器计数和 release 计时验证:每个元素一次堆分配、遍历慢几倍、连 push_back 也不占优,链表只赢在 append。

1000
存 1000 个 u64 的 alloc 次数(Vec 只要 1 次 alloc 加 8 次 realloc)
5×
遍历 100 万个 u64,LinkedList 比 Vec 慢的倍数
E0608
对 LinkedList 使用下标

每个元素一次分配

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    LinkedList<u64> 1000 个元素: alloc 1000 次, 每次 24 字节(元素本身 8 字节), 共申请 24000 字节
    Vec<u64>        1000 个元素: alloc 1 次 realloc 8 次, 共申请 16352 字节(含扩容过程中的所有申请)
    size_of::<LinkedList<u64>>() = 24  size_of::<Vec<u64>>() = 24

遍历

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    遍历求和 100 万个 u64: LinkedList 最快 0.659 ms  Vec 最快 0.132 ms

数字是本机 release 模式下的实测;节点在一个干净的堆上连续分配,真实程序里堆碎片化后链表通常更慢,但这一点没有测。

拼接才是优势

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    push_back 100 万次: LinkedList    8.50 ms  VecDeque   1.41 ms
    push_back 100 万次: LinkedList    7.22 ms  VecDeque   1.43 ms
    push_back 100 万次: LinkedList    6.35 ms  VecDeque   1.22 ms
    两个 100 万元素的容器拼接: LinkedList::append 209ns  Vec::append 715.5µs  (拼接后长度 2000000 / 2000000)

两端队列用 VecDeque 环形缓冲区。

没有随机访问

实测 · 本机执行输出
  源码:
      1 | use std::collections::LinkedList;
      2 | fn main() {
      3 |     let l: LinkedList<i32> = (1..=5).collect();
      4 |     println!("{}", l[2]);
      5 | }
  编译:失败
    error[E0608]: cannot index into a value of type `LinkedList<i32>`
     --> <实验目录>/src/u58d.rs:4:21
      |
    4 |     println!("{}", l[2]);
      |                     ^^^
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0608`.
  编译:成功
  运行(退出码 0):
    iter().nth(    10) = Some(10)  用时 417ns
    iter().nth(  1000) = Some(1000)  用时 2.542µs
    iter().nth(100000) = Some(100000)  用时 75.083µs
    iter().nth(900000) = Some(900000)  用时 805.625µs
    split_off(2): 前 [0, 1] 后 [2, 3, 4]

分步演示

LinkedList
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。计时 release + black_box;分配用 #[global_allocator] 计数
    • 没有验证:堆碎片化之后链表的遍历性能、cursor API(在当前稳定版里没有用到)、LinkedList 的 Drop 在超长链表上的栈深度、在 split_off 之后做大量中间插入的场景
    京ICP备2026067502号-1京公网安备11010502063487号