第10章:优化数据结构_《C++性能优化指南》notes

优化数据结构


一、重点知识解析

  1. std::vector
    特点
  • 连续内存存储,随机访问O(1)
  • 尾部插入/删除高效,中间操作O(n)
  • 动态扩容机制(容量翻倍)

优化点

  • 预分配内存(reserve)
  • 优先使用emplace_back减少拷贝
  1. std::deque
    特点
  • 分块连续存储,支持高效头尾插入
  • 中间插入效率低于list
  • 迭代器非连续,随机访问较慢
  1. std::list
    特点
  • 双向链表结构,插入/删除O(1)
  • 不支持随机访问
  • 内存占用高(每个节点含两个指针)
  1. std::map vs std::unordered_map
    | 特性 | std::map | std::unordered_map |
    |---------------------|-------------------|---------------------|
    | 实现 | 红黑树 | 哈希表 |
    | 查找复杂度 | O(log n) | 平均O(1) |
    | 元素有序 | 是 | 否 |
    | 内存占用 | 较低 | 较高(桶结构) |

二、代码示例与性能测试

示例1:vector预分配优化

cpp 复制代码
#include <iostream>
#include <vector>
#include <chrono>

void test_vector_reserve() {
    const int N = 1e6;
    
    // 不预分配
    auto start = std::chrono::high_resolution_clock::now();
    std::vector<int> v1;
    for (int i=0; i<N; ++i) v1.push_back(i);
    auto end = std::chrono::high_resolution_clock::now();
    std::cout << "Without reserve: " 
              << std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count() 
              << " ms\n";

    // 预分配
    start = std::chrono::high_resolution_clock::now();
    std::vector<int> v2;
    v2.reserve(N);
    for (int i=0; i<N; ++i) v2.push_back(i);
    end = std::chrono::high_resolution_clock::now();
    std::cout << "With reserve: " 
              << std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count() 
              << " ms\n";
}

int main() {
    test_vector_reserve();
    return 0;
}

编译运行

bash 复制代码
g++ -O2 -std=c++11 vector_test.cpp -o vector_test
./vector_test

输出示例

复制代码
Without reserve: 23 ms
With reserve: 8 ms

示例2:list vs vector遍历性能

cpp 复制代码
#include <iostream>
#include <vector>
#include <list>
#include <chrono>

const int N = 1e6;

template<typename T>
void traverse_container(T& container) {
    auto start = std::chrono::high_resolution_clock::now();
    for (auto& x : container) {
        volatile int temp = x; // 防止优化
    }
    auto end = std::chrono::high_resolution_clock::now();
    std::cout << "Traverse time: "
              << std::chrono::duration_cast<std::chrono::microseconds>(end-start).count()
              << " μs\n";
}

int main() {
    // 测试vector
    std::vector<int> vec(N);
    for (int i=0; i<N; ++i) vec[i] = i;
    std::cout << "Vector: ";
    traverse_container(vec);

    // 测试list
    std::list<int> lst;
    for (int i=0; i<N; ++i) lst.push_back(i);
    std::cout << "List: ";
    traverse_container(lst);

    return 0;
}

输出示例

复制代码
Vector: 1254 μs
List: 6328 μs

结论:连续存储的vector遍历速度明显快于list


示例3:map vs unordered_map查找性能

cpp 复制代码
#include <iostream>
#include <map>
#include <unordered_map>
#include <chrono>
#include <random>

const int N = 1e6;

template<typename T>
void test_map(T& m) {
    std::mt19937 rng;
    std::uniform_int_distribution<int> dist(0, N*2);

    // 插入测试
    auto start = std::chrono::high_resolution_clock::now();
    for (int i=0; i<N; ++i) {
        m[dist(rng)] = i;
    }
    auto end = std::chrono::high_resolution_clock::now();
    std::cout << "Insert: " 
              << std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count()
              << " ms\n";

    // 查找测试
    start = std::chrono::high_resolution_clock::now();
    for (int i=0; i<N; ++i) {
        volatile auto it = m.find(dist(rng));
    }
    end = std::chrono::high_resolution_clock::now();
    std::cout << "Find: "
              << std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count()
              << " ms\n";
}

int main() {
    std::cout << "std::map:\n";
    std::map<int, int> m;
    test_map(m);

    std::cout << "\nstd::unordered_map:\n";
    std::unordered_map<int, int> um;
    test_map(um);

    return 0;
}

输出示例

复制代码
std::map:
Insert: 342 ms
Find: 289 ms

std::unordered_map:
Insert: 128 ms 
Find: 85 ms

结论:哈希表在插入和查找上显著快于红黑树实现的有序map


三、关键优化策略总结

  1. vector优化

    • 优先使用reserve()预分配内存
    • 使用emplace_back代替push_back
    • 避免在中间位置插入
  2. 关联容器选择

    • 需要有序访问 → map/set
    • 追求查找速度 → unordered_map/unordered_set
    • 预设置哈希表桶数量(reserve
  3. 迭代器选择

    • 随机访问 → vector/deque
    • 频繁插入删除 → list
  4. 内存局部性

    • 优先选择连续存储容器(vector)
    • 避免过度使用链表结构

第十章核心知识点总结

第十章重点讲解了C++标准库容器的性能特性和优化策略,核心内容包括:

  1. 序列容器性能比较

    • vector:连续内存,快速随机访问,尾部插入高效,中间插入O(n)
    • deque:分块连续内存,头尾插入高效,中间插入O(n)
    • list:双向链表,任意位置插入O(1),内存不连续
    • forward_list:单向链表,更省内存但功能受限
  2. 关联容器特点

    • map/set:红黑树实现,有序存储,查找O(log n)
    • unordered_map/set:哈希表实现,平均O(1)查找,最差O(n)
  3. 内存管理机制

    • vector动态扩容策略(2倍增长)
    • 预分配内存(reserve/resize)
  4. 算法复杂度与缓存局部性

    • 连续内存结构对缓存更友好
    • 链表结构指针跳转导致缓存命中率低

多选题

  1. 关于std::vector插入操作的正确说法是?

    A. 尾部插入时间复杂度O(1)

    B. 中间插入需要移动后续所有元素

    C. 预分配内存可以消除所有复制开销

    D. 扩容时采用倍增策略减少内存分配次数

  2. std::unordered_map相比std::map的优势包括?

    A. 支持范围查询

    B. 内存占用更小

    C. 平均查找时间更优

    D. 元素自动排序

  3. 优化std::list遍历性能的有效方法是?

    A. 使用迭代器代替索引访问

    B. 预分配节点内存池

    C. 改用std::forward_list

    D. 增加缓存块大小

  4. std::deque适合的场景是?

    A. 需要频繁中间插入

    B. 同时进行头尾操作

    C. 内存碎片敏感场景

    D. 要求严格连续内存

  5. 关于容器迭代器失效规则,正确的有?

    A. vector插入导致所有迭代器失效

    B. map插入不会使迭代器失效

    C. unordered_maprehash后迭代器失效

    D. list删除仅影响被删元素迭代器

  6. 提升关联容器查找性能的方法包括?

    A. 为unordered_map设计高质量哈希函数

    B. 对map使用自定义排序谓词

    C. 预分配桶数量减少冲突

    D. 使用lower_bound进行二分查找

  7. std::forward_list相比std::list的缺点是?

    A. 不支持反向遍历

    B. 删除元素需要前驱节点

    C. 每个节点额外存储两个指针

    D. 插入速度更慢

  8. 关于缓存局部性的正确描述是?

    A. vector遍历比list更快

    B. 随机访问破坏空间局部性

    C. 小对象更适合用链表存储

    D. 内存对齐影响缓存利用率

  9. 选择std::array而非std::vector的理由包括?

    A. 需要运行时动态扩容

    B. 避免堆内存分配

    C. 更快的随机访问速度

    D. 支持STL算法

  10. 优化容器内存使用的策略有?

    A. 使用shrink_to_fit释放多余内存

    B. 为vector预留精确容量

    C. 用emplace_back避免临时对象

    D. 优先选择连续内存容器


多选题答案与解析

  1. ABD

    C错误:预分配减少但无法完全消除复制(如中间插入仍需移动)

  2. BC

    A/D是map的特性,哈希表查找平均O(1)但最差O(n)

  3. B

    内存池减少动态分配开销,链表遍历本身无法优化缓存局部性

  4. B

    deque设计目标就是高效头尾操作,C错误因分块存储仍有碎片

  5. BCD

    vector插入可能导致扩容使所有迭代器失效,但未扩容时仅后部失效

  6. AC

    D适用于有序容器,B不影响查找复杂度

  7. AB

    forward_list每个节点只需一个指针,但删除需要前驱节点

  8. ABD

    C错误:小对象用链表反而增加内存碎片

  9. B

    array栈上分配,但随机访问速度与vector相当

  10. ACD

    B过度预留会增加内存占用


设计题

题目1:实时日志系统
需求:实现一个高并发日志系统,支持每秒百万级日志条目追加和批量读取,要求内存高效。

设计要点

  1. 使用std::deque分块存储,避免vector扩容时的全量复制
  2. 批量处理时利用缓存局部性
  3. 测试代码:
cpp 复制代码
#include <deque>
#include <chrono>

struct LogEntry { /*...*/ };

int main() {
    std::deque<LogEntry> logs;
    // 并发写入测试
    auto start = std::chrono::high_resolution_clock::now();
    for (int i=0; i<1'000'000; ++i) {
        logs.emplace_back(/*...*/);
    }
    auto end = std::chrono::high_resolution_clock::now();
    // 输出写入耗时
}

题目2:游戏实体管理
需求:管理数万个动态变化的游戏实体,需快速遍历和频繁插入删除。

解决方案

  1. 使用std::vector配合标记删除,牺牲删除速度换取遍历性能
  2. 测试代码:
cpp 复制代码
std::vector<Entity> entities;
std::vector<size_t> free_indices;

void RemoveEntity(size_t id) {
    entities[id].alive = false;
    free_indices.push_back(id);
}

void AddEntity(const Entity& e) {
    if (!free_indices.empty()) {
        entities[free_indices.back()] = e;
        free_indices.pop_back();
    } else {
        entities.push_back(e);
    }
}

// 遍历时跳过标记为dead的实体

题目3:金融交易缓存
需求:实现股票代码到最新报价的映射,要求纳秒级查询延迟。

设计选择

  1. 使用std::unordered_map预分配足够桶数量
  2. 测试代码:
cpp 复制代码
#include <unordered_map>
#include <string>

struct Quote { /*...*/ };
std::unordered_map<std::string, Quote> cache(1000000);

// 预分配桶
cache.reserve(1000000);

// 查询测试
auto it = cache.find("AAPL");

题目4:科学计算矩阵存储
需求:存储大型稀疏矩阵,支持快速按行列访问。

数据结构

  1. 使用std::map<tuple<int,int>, double>存储非零元素
  2. 测试代码:
cpp 复制代码
#include <map>
#include <tuple>

std::map<std::tuple<int,int>, double> matrix;

void SetValue(int row, int col, double val) {
    matrix[{row, col}] = val;
}

// 行遍历优化:使用lower_bound搜索行号

题目5:内存敏感型应用优化
需求:在嵌入式系统中管理动态数据,避免内存碎片。

方案

  1. 使用自定义内存池+std::vector预分配
  2. 测试代码:
cpp 复制代码
#include <vector>
#include <memory_pool>

template<typename T>
class PoolAllocator { /*...*/ };

std::vector<int, PoolAllocator<int>> data;
data.reserve(1024); // 预分配固定内存块

设计题答案解析

  1. 实时日志系统

    选择deque因其分块特性避免大规模数据复制,适合高频追加。测试中可对比vector的扩容耗时差异。

  2. 游戏实体管理

    Vector+标记删除在遍历时通过紧凑内存布局提升缓存命中率,适合每帧遍历全部实体的场景。

  3. 金融交易缓存
    unordered_map预分配桶减少rehash,哈希查找O(1)满足低延迟要求。测试需验证不同哈希函数性能。

  4. 稀疏矩阵存储

    使用有序map支持高效范围查询,但更新较慢。对比unordered_map可权衡插入/查询性能。

  5. 嵌入式内存优化

    内存池分配器避免频繁堆操作,vector预分配消除动态增长开销。可通过valgrind验证内存碎片情况。


测试用例示例(矩阵存储)

cpp 复制代码
#include <iostream>
#include <map>
#include <tuple>

int main() {
    std::map<std::tuple<int,int>, double> matrix;
    
    // 插入测试
    matrix[{100, 200}] = 3.14;
    matrix[{500, 600}] = 2.718;
    
    // 查找测试
    auto it = matrix.find({100, 200});
    if (it != matrix.end()) {
        std::cout << "Found: " << it->second << std::endl;
    }
    
    // 行遍历示例
    int target_row = 100;
    auto lower = matrix.lower_bound({target_row, 0});
    auto upper = matrix.lower_bound({target_row+1, 0});
    for (auto it=lower; it!=upper; ++it) {
        std::cout << "Row " << target_row << " has value " << it->second << std::endl;
    }
}

其他详细答案,稍后补充

相关推荐
rqtz1 小时前
【C++指针】搭建起程序与内存深度交互的桥梁(下)
开发语言·c++·指针
熬夜造bug1 小时前
LeetCode Hot100 刷题笔记(4)—— 二叉树、图论
笔记·算法·leetcode
小陈的进阶之路1 小时前
数据结构(并查集,图)
数据结构·c++·算法
乌恩大侠1 小时前
笔记:基于环境语义的通感融合技术,将传统通信由“被动接收”转为“主动感知”
笔记
SNAKEpc121382 小时前
在MFC中使用Qt(三):通过编辑项目文件(.vcxproj)实现Qt的自动化编译流程
c++·qt·mfc
sealaugh322 小时前
aws(学习笔记第三十六课) apigw-http-api-lambda-dynamodb
笔记·学习·aws
dokii12 小时前
leetcode199 二叉树的右视图
数据结构·算法·leetcode
UP_Continue2 小时前
排序--归并排序--非递归
数据结构·算法·排序算法
*TQK*2 小时前
CSS学习笔记5——渐变属性+盒子模型阶段案例
css·笔记·学习
魑魅魍魉都是鬼3 小时前
音视频 一 看书的笔记 基础视频知识
笔记·音视频