第10章:优化数据结构
对齐第一版第10章 Optimize Data Structures:标准库 sequence/associative 容器实验,vector/string/deque/list/forward_list 的插入迭代排序,以及 map/set multi/unordered 家族与其他结构。
学习目标
- 能设计同语义 container experiment,覆盖 access/mutation/stability/representation 与完整生命周期
- 能比较 vector/string、deque、list/forward_list 的 reallocation、insertion/deletion、iteration、sort 与 lookup
- 能分析 ordered/multi/unordered associative contract,并判断标准容器或其他领域数据结构的 break-even
从“容器就是一组性能契约”开始
standard library containers 提供 value/lifetime、iterator、complexity 与 invalidation contract;它们不承诺每个 workload 上同样快。第 10 章的核心是 experimenting:保持 semantics/input/operation mix 一致,逐个比较 candidate,而不是只读复杂度表。
↡标准规定语义、iterator/invalidation 和复杂度要求的 sequence/associative/container adaptor 家族。 ↡在相同语义、输入与操作混合下替换一个 container/representation,采集时间、allocation、memory 与 correctness 的受控比较。sequence containers 按 sequence 组织 elements;associative containers 按 key/order/hash contract 组织。选择前写出 random index、front/back/middle mutation、exact/range lookup、duplicates、stable references、ordered iteration 与 peak memory。
↡按元素顺序保存值的 vector/deque/list/forward_list 等容器,访问与修改成本由表示和位置决定。 ↡按 key 的 ordering 或 hashing 组织 value/key,支持 exact/range/duplicate 等关联操作的容器。struct WorkloadResult {
std::chrono::nanoseconds build;
std::chrono::nanoseconds mutate;
std::chrono::nanoseconds iterate;
std::uint64_t checksum;
};
template <class Container, class Build, class Mutate, class Visit>
WorkloadResult runContainerScenario(Build build, Mutate mutate, Visit visit) {
Container values;
const auto buildTime = measure([&] { build(values); });
const auto mutateTime = measure([&] { mutate(values); });
std::uint64_t checksum = 0;
const auto iterateTime = measure([&] { checksum = visit(values); });
return {buildTime, mutateTime, iterateTime, checksum};
}harness 还应记录 allocations/bytes/RSS、key comparisons/hash、cache misses,并验证 checksum/order/reference contract。不要让不同 candidate 执行不同业务 work。
std::vector 与 std::string
std::vector contiguous、random access、适合 iteration/sorting;std::string 也是 contiguous character sequence(另有 terminator/value contract)。append 在 capacity 足够时便宜,超出则 reallocation:取得新 storage、move/copy elements、释放旧 storage并使 references/iterators 失效。
inserting and deleting 在 middle 会移动 suffix;erase many one-by-one 可能 quadratic,应使用 erase-remove、partition/compaction 或 batch rebuild。append/read-heavy 常先 reserve;不能为极端 max 无界预留。
void removeExpired(std::vector<Job>& jobs, Clock::time_point now) {
std::erase_if(jobs, [now](const Job& job) {
return job.expiresAt <= now;
});
}一次 compaction 保留 survivors,通常比循环 find+erase 少搬移。若 order 不重要,可 swap-with-back 降移动;这改变 order,必须显式 contract。
iterating contiguous values 通常 locality 好;sorting 可用 random-access standard algorithm;lookup 若 unsorted 用 linear,sorted 用 binary/range,read-mostly 可 amortize sort。vector 并不因 middle insert O(n) 就不适合插入偶尔、遍历频繁的 workload。
std::deque
std::deque 是 segmented random-access sequence;front/back growth 通常无需搬移全部 elements。deque insertion 在 middle 仍可能移动一侧 elements,并影响 iterators/references(按标准规则核对);不是“任意插入 O(1)”。
deque iteration 跨 blocks,通常比 vector 有更多 indirection;deque sorting 支持 random-access iterator,可用 std::sort,但 segmented layout 与 element move 影响 constants。适合 queue-like 双端增长且需要 random access 的场景;简单 FIFO 还应比较 deque adaptor 使用与 ring buffer。
std::list 与 std::forward_list
std::list doubly linked,std::forward_list singly linked。list insertion 只有在 insertion position iterator 已知时才 constant relink;search position 仍需 traversal。每 node allocation/metadata 和 pointer chasing 可能让它在 iteration-heavy workload 输给 vector。
list iteration/forward-list iteration 都是 pointer traversal;list sorting 必须用 member list::sort/forward_list::sort,它通过 relink nodes,不满足 random-access std::sort 要求。large nonmovable elements、frequent splice with known iterators 可能是 list 的真实优势。
Ordered Associative: map/multimap/set/multiset
std::map unique key/value,std::set unique key;std::multimap 与 std::multiset 允许 equivalent keys。它们提供 ordered iteration、lower/equal range 与 logarithmic search/update(典型 tree representation,但标准不强制具体节点布局)。
multi container 适合一 key 多记录且需 ordered group;若只需要 exact key→vector values,map<Key, vector<Value>> 可能更 compact,但改变 insertion/reference/erase semantics。transparent comparator 可避免临时 key。测 comparator/key copy、node allocations、range length 与 iteration。
Unordered map/multimap 与 Lookup
std::unordered_map unique key/value,std::unordered_multimap equivalent duplicate keys;unordered lookup 依 hash、bucket/probe 和 exact equality,average constant 不保证 ordered range 或 worst-case。rehash 会重建 buckets 并使 iterators 失效(reference rules另核对)。
std::unordered_multimap<Tag, ItemId> buildTagIndex(
std::span<const Item> items) {
std::size_t associations = 0;
for (const Item& item : items) associations += item.tags.size();
std::unordered_multimap<Tag, ItemId> index;
index.reserve(associations);
for (const Item& item : items) {
for (Tag tag : item.tags) index.emplace(tag, item.id);
}
return index;
}reserve 预估 associations,减少 rehash;仍要比较 alternative unordered_map<Tag, vector<ItemId>> 的 group locality/build/erase。duplicate semantics 与 order不可静默改变。
Other Data Structures(其他数据结构)
other data structures 包括 flat/sorted map、heap/priority queue、bitset、trie、ring buffer、pool/intrusive list、spatial index。它们不是“标准容器更快版”,而是表达不同 contract:bounded FIFO、prefix lookup、dense boolean set、priority top、spatial neighborhood 等。
↡在标准 sequence/associative contract 无法表达领域操作时采用的 flat、heap、bitset、trie、ring、pool 或 spatial 等结构。选择 custom/domain structure 要写 invariant、capacity/growth、ownership、iterator/reference、error/worst-case,并与组合标准 primitives baseline 同语义比较。若只有小常数收益却承担大量维护,保留标准方案更优。
小结
- 标准容器优化依靠同语义 experimenting,完整比较 access/mutation/stability/representation
- vector/string 连续且易 iterate/sort,reallocation 与 middle movement 要用 reserve/batch 管理
- deque 支持两端增长和 random access,但 segmented iteration/sort 有额外 constants
- list/forward_list 的 O(1) insertion 需要已知位置,node allocation 与 pointer traversal 必须计价
- map/set 提供 ordered unique,multi 允许 duplicates;unordered 家族提供 average exact lookup但无顺序
- unordered lookup 要审计 hash/equality/load/rehash/worst-case,而非只引用 O(1)
- other structures 只有在领域 contract 明确且同语义收益覆盖维护成本时才采用
名词解释
本章出现的专业名词,用大白话再讲一遍。
- standard library containers
具有标准语义、iterator/invalidation 和复杂度要求的容器。
- container experimenting
同语义替换一个 container 并比较完整性能和正确性。
- sequence containers
按 sequence 保存 elements 的容器家族。
- associative containers
按 ordering/hash key contract 组织值的容器家族。
- reallocation
capacity 超限后取得新 storage 并搬移 elements 的事件。
- std::deque
支持 random access 与两端增长的分段序列。
- std::list
- 双向 node-based sequence。
- std::forward_list
单向 node-based sequence。
- std::map
ordered unique key/value associative container。
- std::unordered_map
hashed unique key/value associative container。
- std::unordered_multimap
hashed duplicate-key associative container。
- other data structures
为领域操作定制的 flat/heap/bitset/trie/ring/pool/spatial 结构。
练习
- 问题 1:为编辑器撤销历史选择 sequence。 操作 90% append/back-pop,9% full iterate,1% middle erase;最多 200k 大型 movable commands,需要 occasional sort by timestamp。比较 vector/deque/list。
- 问题 2:设计 tag→多 item 索引。 需要 exact tag lookup、duplicates,无 ordered export;每天重建、每秒百万读。比较
unordered_multimap<Tag, Item>与unordered_map<Tag, vector<Item>>。
- 问题 3:何时用其他数据结构。 实时系统需要固定容量 4096 的 FIFO,禁止运行期 allocation,需 O(1) push/pop;比较 deque 与 ring buffer。