第 8 章:使用更好的库_《C++性能优化指南》_notes

使用更好的库

第八章核心知识点解析

  1. 优化标准库的使用
    知识点:选择合适的数据结构、预分配内存、减少拷贝
cpp 复制代码
#include <vector>
#include <chrono>
#include <iostream>

// 测试vector的reserve对性能的影响
void test_vector_reserve() {
    const int N = 1000000;
    
    // 不预分配内存
    {
        std::vector<int> v;
        auto start = std::chrono::high_resolution_clock::now();
        for(int i=0; i<N; ++i) {
            v.push_back(i);
        }
        auto end = std::chrono::high_resolution_clock::now();
        std::cout << "Without reserve: " 
                  << std::chrono::duration_cast<std::chrono::microseconds>(end-start).count()
                  << "μs\n";
    }

    // 预分配内存
    {
        std::vector<int> v;
        v.reserve(N);
        auto start = std::chrono::high_resolution_clock::now();
        for(int i=0; i<N; ++i) {
            v.push_back(i);
        }
        auto end = std::chrono::high_resolution_clock::now();
        std::cout << "With reserve: " 
                  << std::chrono::duration_cast<std::chrono::microseconds>(end-start).count()
                  << "μs\n";
    }
}

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

输出示例

复制代码
Without reserve: 5432μs
With reserve: 1276μs

关键点

  • reserve()预分配内存避免多次重新分配
  • 减少内存分配次数可提升3-4倍性能
  • 适用于vector、string等动态容器
  1. 优化现有库
    知识点:添加批量处理接口
cpp 复制代码
#include <vector>
#include <chrono>
#include <iostream>

// 原始单元素处理接口
void process_element(std::vector<int>& vec, int value) {
    vec.push_back(value * 2);
}

// 优化的批量处理接口
void process_batch(std::vector<int>& vec, const std::vector<int>& values) {
    vec.reserve(vec.size() + values.size());
    for(auto v : values) {
        vec.push_back(v * 2);
    }
}

void test_batch_processing() {
    const int N = 10000;
    std::vector<int> input(N, 5);
    
    // 单次处理测试
    {
        std::vector<int> result;
        auto start = std::chrono::high_resolution_clock::now();
        for(auto v : input) {
            process_element(result, v);
        }
        auto end = std::chrono::high_resolution_clock::now();
        std::cout << "Single processing: " 
                  << std::chrono::duration_cast<std::chrono::microseconds>(end-start).count()
                  << "μs\n";
    }

    // 批量处理测试
    {
        std::vector<int> result;
        auto start = std::chrono::high_resolution_clock::now();
        process_batch(result, input);
        auto end = std::chrono::high_resolution_clock::now();
        std::cout << "Batch processing: " 
                  << std::chrono::duration_cast<std::chrono::microseconds>(end-start).count()
                  << "μs\n";
    }
}

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

输出示例

复制代码
Single processing: 856μs
Batch processing: 213μs

关键点

  • 批量处理减少函数调用开销
  • 预分配内存进一步提升性能
  • 接口设计要考虑使用场景
  1. 设计高效库
    知识点:扁平化调用链
cpp 复制代码
#include <chrono>
#include <iostream>

// 深层次调用链
class DeepCallChain {
public:
    void level3(int x) { data = x * 2; }
    void level2(int x) { level3(x); }
    void level1(int x) { level2(x); }
    int get() const { return data; }
private:
    int data;
};

// 扁平化调用链
class FlatCallChain {
public:
    void process(int x) { data = x * 2; }
    int get() const { return data; }
private: 
    int data;
};

void test_call_chain() {
    const int N = 1000000;
    
    // 深层次调用
    {
        DeepCallChain obj;
        auto start = std::chrono::high_resolution_clock::now();
        for(int i=0; i<N; ++i) {
            obj.level1(i);
        }
        auto end = std::chrono::high_resolution_clock::now();
        std::cout << "Deep call chain: " 
                  << std::chrono::duration_cast<std::chrono::microseconds>(end-start).count()
                  << "μs\n";
    }

    // 扁平调用
    {
        FlatCallChain obj;
        auto start = std::chrono::high_resolution_clock::now();
        for(int i=0; i<N; ++i) {
            obj.process(i);
        }
        auto end = std::chrono::high_resolution_clock::now();
        std::cout << "Flat call chain: " 
                  << std::chrono::duration_cast<std::chrono::microseconds>(end-start).count()
                  << "μs\n";
    }
}

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

输出示例

复制代码
Deep call chain: 2563μs
Flat call chain: 1245μs

关键点

  • 减少函数调用层级
  • 避免不必要的中间调用层
  • 扁平调用提升约50%性能
  1. 避免动态查找
    知识点:用直接访问替代查找
cpp 复制代码
#include <vector>
#include <algorithm>
#include <chrono>
#include <iostream>

void test_access_method() {
    const int N = 100000;
    std::vector<int> data(N);
    std::iota(data.begin(), data.end(), 0);
    
    // 查找方式访问
    {
        int sum = 0;
        auto start = std::chrono::high_resolution_clock::now();
        for(int i=0; i<N; ++i) {
            auto it = std::find(data.begin(), data.end(), i);
            if(it != data.end()) sum += *it;
        }
        auto end = std::chrono::high_resolution_clock::now();
        std::cout << "Find access: " 
                  << std::chrono::duration_cast<std::chrono::microseconds>(end-start).count()
                  << "μs\n";
    }

    // 直接索引访问
    {
        int sum = 0;
        auto start = std::chrono::high_resolution_clock::now();
        for(int i=0; i<N; ++i) {
            sum += data[i];
        }
        auto end = std::chrono::high_resolution_clock::now();
        std::cout << "Direct access: " 
                  << std::chrono::duration_cast<std::chrono::microseconds>(end-start).count()
                  << "μs\n";
    }
}

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

输出示例

复制代码
Find access: 12563μs
Direct access: 256μs

关键点

  • 查找操作时间复杂度O(n)
  • 直接索引访问复杂度O(1)
  • 性能差异可达50倍以上

编译与测试建议

  1. 使用C++11及以上标准编译:
bash 复制代码
g++ -std=c++11 -O2 example.cpp -o example
  1. 运行测试:
bash 复制代码
./example
  1. 典型优化效果对比:
  • 内存预分配可提升3-5倍性能
  • 批量接口比单次处理快2-4倍
  • 扁平调用链比深层次快1.5-2倍
  • 直接访问比查找快10-50倍

总结优化原则

  1. 预分配原则:对已知大小的容器使用reserve()
  2. 批量处理:设计支持批量操作的接口
  3. 扁平设计:减少不必要的调用层次
  4. 直接访问:用索引替代查找操作
  5. 内存重用:避免重复分配/释放内存

重点内容:

  1. 标准库性能特征与实现差异
  2. 自定义内存分配器的设计与应用
  3. 避免抽象惩罚的编程技巧
  4. 高效算法和数据结构的选择
  5. 字符串处理优化策略
  6. 模板元编程的性能影响
  7. 异常处理的开销控制
  8. 线程安全与性能平衡
  9. 缓存友好型数据结构设计
  10. 编译器优化选项的合理使用

第一部分:多选题(10题)

  1. 关于标准库容器的性能优化,以下哪些说法正确?

    A) vector的push_back时间复杂度是O(n)

    B) unordered_map的查找复杂度总是O(1)

    C) deque在中间插入元素的时间复杂度是O(n)

    D) list的迭代器失效规则与vector相同

  2. 下列哪些方法可以有效减少动态内存分配?

    A) 使用对象池模式

    B) 优先使用emplace_back代替push_back

    C) 为string预先调用reserve()

    D) 使用std::make_shared创建智能指针

  3. 关于字符串优化,哪些做法正确?

    A) 小字符串优化(SSO)可以避免堆分配

    B) 使用+=拼接比多次operator+更高效

    C) 移动语义可以完全消除字符串拷贝

    D) c_str()调用会触发深拷贝

  4. 以下哪些算法选择可能提升性能?

    A) 用std::sort替代冒泡排序

    B) 用std::lower_bound替代线性查找

    C) 用std::list替代std::vector存储频繁修改的序列

    D) 用std::array替代原始数组

  5. 关于内存分配器,正确的有:

    A) 自定义分配器可以减少锁竞争

    B) std::allocator是线程安全的

    C) 内存池适合固定大小对象的分配

    D) 对齐分配对SIMD指令很重要

  6. 哪些模板使用可能影响性能?

    A) 深度嵌套的模板实例化

    B) 递归模板元编程

    C) 使用类型擦除的any类型

    D) 模板参数推导失败

  7. 关于异常处理,正确的有:

    A) 异常规范影响代码优化

    B) try块会增加函数调用开销

    C) noexcept声明帮助编译器优化

    D) 异常捕获应尽量精确

  8. 缓存友好的设计包括:

    A) 使用紧凑数据结构

    B) 预取相邻内存数据

    C) 随机访问链表节点

    D) 对齐内存访问边界

  9. 编译器优化相关:

    A) -O3可能增加代码体积

    B) LTO优化链接时代码

    C) PGO需要训练数据

    D) restrict关键字帮助别名分析

  10. 线程安全优化策略:

    A) 读写锁减少竞争

    B) thread_local变量避免锁

    C) 无锁数据结构消除等待

    D) 原子操作总是比锁高效


第二部分:设计题

设计题1:高效字符串拼接

cpp 复制代码
// 要求:实现零拷贝的字符串拼接,支持链式调用
class StringBuilder {
public:
    StringBuilder& append(const std::string& s);
    std::string build();
private:
    // 设计存储结构
};

设计题2:内存池分配器

cpp 复制代码
// 实现固定块大小的内存池,支持STL容器
template <typename T>
class PoolAllocator {
public:
    using value_type = T;
    // 必要接口实现
};

设计题3:类型擦除优化

cpp 复制代码
// 设计替代虚函数调用的高效多态方案
template <typename T>
class FunctionWrapper {
public:
    template <typename F>
    FunctionWrapper(F&& f);
    void operator()() const;
private:
    // 存储策略
};

设计题4:SIMD优化矩阵乘法

cpp 复制代码
// 使用AVX指令优化4x4矩阵乘法
void matrix_multiply_avx(const float* a, const float* b, float* result);

设计题5:无锁队列

cpp 复制代码
// 实现多生产者单消费者的无锁队列
template <typename T>
class LockFreeQueue {
public:
    void push(const T& value);
    bool pop(T& value);
private:
    // 设计节点结构和原子操作
};

答案与解析


多选题答案:
  1. BC

    C正确,deque中间插入O(n);B哈希表平均O(1)

    A错,摊销O(1);D错,vector插入会使迭代器失效

  2. ABCD

    全部正确,B/D减少临时对象,A/C预分配

  3. AB

    C错,移动可能保留容量;D错,c_str()不触发拷贝

  4. AB

    C错,vector更适合随机访问;D类型安全但性能相同

  5. ACD

    B错,标准分配器线程安全但可能有锁

  6. ABC

    D是编译错误,不影响运行性能

  7. ACD

    B错,try块本身无运行时开销

  8. ABD

    C链表导致缓存不命中

  9. ABCD

    全部正确

  10. ABC

    D错,原子操作可能更慢


设计题答案示例(部分):

设计题1实现:

cpp 复制代码
class StringBuilder {
    std::vector<std::reference_wrapper<const std::string>> parts;
public:
    StringBuilder& append(const std::string& s) {
        parts.emplace_back(s);
        return *this;
    }
    
    std::string build() const {
        size_t total = 0;
        for (const auto& s : parts) total += s.get().size();
        std::string result;
        result.reserve(total);
        for (const auto& s : parts) result += s.get();
        return result;
    }
};

// 测试用例
int main() {
    StringBuilder sb;
    sb.append("Hello").append(" ").append("World");
    assert(sb.build() == "Hello World");
}

设计题2实现:

cpp 复制代码
template <typename T>
class PoolAllocator {
    struct Block { Block* next; };
    Block* freeList = nullptr;
    
public:
    T* allocate(size_t n) {
        if (n != 1 || !freeList) {
            return static_cast<T*>(::operator new(n * sizeof(T)));
        }
        auto head = freeList;
        freeList = freeList->next;
        return reinterpret_cast<T*>(head);
    }
    
    void deallocate(T* p, size_t n) {
        if (n != 1) {
            ::operator delete(p);
            return;
        }
        auto block = reinterpret_cast<Block*>(p);
        block->next = freeList;
        freeList = block;
    }
};

剩下的设计题目, 后续补充


测试用例设计原则:

  1. 边界测试(空容器、最大容量)
  2. 并发测试(多线程竞争)
  3. 性能对比(与标准实现比较)
  4. 内存泄漏检测(Valgrind验证)
  5. 平台兼容性(不同编译器/架构)
相关推荐
夜松云32 分钟前
Python数据可视化与数据处理全解析:Matplotlib图形控制与Pandas高效数据分析实战
python·算法·信息可视化·pandas·matplotlib
Wils0nEdwards1 小时前
Leetcode 两数相除
算法·leetcode·职场和发展
Q186000000001 小时前
springboot 四层架构之间的关系整理笔记二
spring boot·笔记·架构
程序员老冯头1 小时前
第十一节 MATLAB关系运算符
开发语言·前端·数据结构·算法·matlab
2401_884810741 小时前
Spring-MVC笔记(下)
笔记·spring·mvc
19岁开始学习1 小时前
PHP之RabbitMQ笔记
笔记·rabbitmq·php
tzc_fly1 小时前
未来基于参考映射的单细胞数据分析
算法·机器学习·数据分析
zero.cyx1 小时前
DFS飞机降落
算法·深度优先
余华余华1 小时前
VSCODE npm: 因为在此系统上禁止运行脚本。有关详细信息,请参阅 ...
java·服务器·数据库·笔记·oracle