当前位置: 首页 > article >正文

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

使用更好的库

      • 第八章核心知识点解析
      • 编译与测试建议
      • 总结优化原则
      • 重点内容:
      • 第一部分:多选题(10题)
      • 第二部分:设计题
      • 答案与解析
        • 多选题答案:
        • 设计题答案示例(部分):
      • 测试用例设计原则:

第八章核心知识点解析

  1. 优化标准库的使用
    知识点:选择合适的数据结构、预分配内存、减少拷贝
#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. 优化现有库
    知识点:添加批量处理接口
#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. 设计高效库
    知识点:扁平化调用链
#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. 避免动态查找
    知识点:用直接访问替代查找
#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及以上标准编译:
g++ -std=c++11 -O2 example.cpp -o example
  1. 运行测试:
./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:高效字符串拼接

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

设计题2:内存池分配器

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

设计题3:类型擦除优化

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

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

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

设计题5:无锁队列

// 实现多生产者单消费者的无锁队列
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实现:

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实现:

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. 平台兼容性(不同编译器/架构)

http://www.kler.cn/a/613143.html

相关文章:

  • 数据库——MySQL字符、日期函数
  • 字符串相加和字符串相乘
  • 通过Map类和List类的List<Map<>>组合类体会JSON
  • 7. 实现命名路由和反向路由
  • windows第十八章 菜单、工具栏、状态栏
  • leetcode102 二叉树的层次遍历 递归
  • [贪心算法] 加油站 单调递增的数字 坏了的计算器 合并区间 无重叠区间
  • MUX VLAN -- PVLAN
  • 每日一题之杨辉三角
  • 雕琢健康生活,奏响养生乐章
  • 一周掌握Flutter开发--9. 与原生交互(上)
  • Jmeter触发脚本备份
  • 《白帽子讲 Web 安全》之服务端请求伪造(SSRF)深度剖析:从攻击到防御
  • Sa-Token
  • 探索高效的图像处理:SMFA 模块与 DMlp 类的实现解析
  • 《 C语言实现:金字塔式星号图案打印》
  • FALL靶机攻略
  • B/S阅片项目算法梳理
  • 蓝桥杯省模拟赛 阶乘求值
  • OMI(operating mode indication)