第 6 章:优化动态分配内存的变量_《C++性能优化指南》_notes

优化动态分配内存的变量

第六章核心知识点详解

  1. 动态内存分配的开销
    知识点:动态内存分配需要调用操作系统API,涉及锁竞争和内存碎片,频繁分配释放会导致性能瓶颈。

代码示例(比较动态数组 vs 静态数组):

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

void test_dynamic_allocation() {
    auto start = std::chrono::high_resolution_clock::now();
    for (int i=0; i<100000; ++i) {
        int* arr = new int[100]; // 频繁动态分配
        delete[] arr;
    }
    auto end = std::chrono::high_resolution_clock::now();
    std::cout << "Dynamic: " 
              << std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count() 
              << " ms\n";
}

void test_static_allocation() {
    auto start = std::chrono::high_resolution_clock::now();
    for (int i=0; i<100000; ++i) {
        int arr[100]; // 栈上分配,快速
    }
    auto end = std::chrono::high_resolution_clock::now();
    std::cout << "Static: " 
              << std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count() 
              << " ms\n";
}

int main() {
    test_dynamic_allocation();
    test_static_allocation();
    return 0;
}

输出

复制代码
Dynamic: 120 ms
Static: 0 ms

分析:动态分配比栈分配慢百倍,需避免高频使用。


  1. 智能指针优化
    知识点std::shared_ptr引用计数有性能损耗,优先使用std::unique_ptr

代码示例

cpp 复制代码
#include <memory>
#include <iostream>

struct Widget {
    Widget() { std::cout << "Widget created\n"; }
    ~Widget() { std::cout << "Widget destroyed\n"; }
};

void use_shared() {
    auto ptr = std::make_shared<Widget>(); // 引用计数+1
    auto ptr2 = ptr; // 引用计数+1
}

void use_unique() {
    auto ptr = std::make_unique<Widget>(); // 独占所有权
    // auto ptr2 = ptr; // 错误!无法复制unique_ptr
}

int main() {
    std::cout << "Shared ptr:\n";
    use_shared();
    std::cout << "Unique ptr:\n";
    use_unique();
}

输出

复制代码
Shared ptr:
Widget created
Widget destroyed
Unique ptr:
Widget created
Widget destroyed

分析shared_ptr适合共享所有权,但引用计数带来开销;unique_ptr零开销,更高效。


  1. 避免不必要的复制
    知识点 :C++11引入移动语义,通过std::move转移资源,避免深拷贝。

代码示例

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

class HeavyData {
    std::vector<int> data;
public:
    HeavyData(size_t size) : data(size, 42) {
        std::cout << "HeavyData constructed\n";
    }
    // 移动构造函数
    HeavyData(HeavyData&& other) noexcept : data(std::move(other.data)) {
        std::cout << "HeavyData moved\n";
    }
    // 移动赋值运算符
    HeavyData& operator=(HeavyData&& other) noexcept {
        data = std::move(other.data);
        std::cout << "HeavyData moved assigned\n";
        return *this;
    }
};

int main() {
    HeavyData a(1000);
    HeavyData b = std::move(a); // 调用移动构造
    HeavyData c(2000);
    c = std::move(b); // 调用移动赋值
    return 0;
}

输出

复制代码
HeavyData constructed
HeavyData moved
HeavyData constructed
HeavyData moved assigned

分析 :移动操作避免了复制vector内容,性能显著提升。


  1. 返回值优化(RVO)
    知识点:编译器优化,消除函数返回时的临时对象复制。

代码示例

cpp 复制代码
#include <iostream>

struct Data {
    Data() { std::cout << "Data created\n"; }
    Data(const Data&) { std::cout << "Data copied\n"; }
    Data(Data&&) { std::cout << "Data moved\n"; }
};

Data createData() {
    return Data(); // 可能触发RVO
}

int main() {
    Data d = createData();
    return 0;
}

输出(启用RVO时):

复制代码
Data created

分析:RVO跳过了拷贝和移动构造,直接构造目标对象。


  1. 自定义内存分配器
    知识点 :通过重载new/delete或使用内存池减少分配开销。

代码示例(简单内存池):

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

class MemoryPool {
    std::vector<void*> blocks;
public:
    void* allocate(size_t size) {
        void* block = ::operator new(size);
        blocks.push_back(block);
        return block;
    }
    ~MemoryPool() {
        for (auto p : blocks) ::operator delete(p);
    }
};

struct FastObject {
    static MemoryPool pool;
    void* operator new(size_t size) {
        return pool.allocate(size);
    }
    void operator delete(void* p) {
        // 内存池统一释放
    }
};

MemoryPool FastObject::pool;

int main() {
    FastObject* obj = new FastObject;
    delete obj;
    return 0;
}

分析:集中管理内存分配,减少碎片和系统调用次数。


总结

第六章的核心在于减少动态内存操作,优先使用栈对象和移动语义,合理选择智能指针,利用编译器优化如RVO。每个优化点都有对应的代码实践,通过测量性能差异可验证优化效果。实际项目中需结合性能分析工具(如Valgrind、perf)定位热点,针对性优化。

第六章 动态内存优化 重点难点梳理

核心知识点:

  1. 智能指针所有权管理(shared_ptr/unique_ptr)
  2. 减少动态内存分配策略(预分配、对象池)
  3. 移动语义与右值引用优化
  4. 自定义内存分配器实现
  5. 写时复制(Copy-on-Write)优化模式
  6. 扁平数据结构设计
  7. 内存碎片管理
  8. 线程安全的内存管理

一、多选题(每题至少2个正确答案)

  1. 关于shared_ptr线程安全性,正确的是:

    A) 引用计数是原子操作

    B) 指向的对象本身线程安全

    C) 多个线程写同一个shared_ptr需要同步

    D) make_shared比new更高效

  2. 哪些方法能有效减少动态内存分配?

    A) 使用内存池

    B) 优先使用栈分配

    C) 使用vector::reserve预分配

    D) 全局静态对象

  3. 移动语义的优势包括:

    A) 消除深拷贝开销

    B) 允许资源转移

    C) 保证异常安全

    D) 自动处理循环引用

  4. 实现高效内存分配器的关键点:

    A) 固定大小内存块管理

    B) 线程本地存储

    C) 内存对齐保证

    D) 使用系统malloc

  5. 关于写时复制(COW),正确的是:

    A) 修改时触发真实复制

    B) 适用于高频读取场景

    C) 需要引用计数

    D) C++11后推荐使用

  6. 扁平数据结构的优势:

    A) 更好的缓存局部性

    B) 减少间接指针

    C) 简化内存管理

    D) 支持快速插入

  7. 自定义内存分配器的应用场景:

    A) 高频小对象分配

    B) 实时系统

    C) 多线程环境

    D) 持久化存储

  8. 优化动态数组性能的方法:

    A) reserve预分配容量

    B) 使用emplace_back

    C) 避免中间临时对象

    D) 使用链表替代

  9. 移动构造函数应具备:

    A) noexcept声明

    B) 转移资源所有权

    C) 深拷贝实现

    D) 修改原对象状态

  10. 线程安全内存管理策略:

    A) 使用TLS分配器

    B) 全局互斥锁保护

    C) 无锁队列分配

    D) 静态内存池


二、设计题

  1. 实现线程安全的内存池

    要求:支持固定大小内存块的分配/释放,多线程环境下高效工作,提供性能对比测试

  2. 优化动态数组高频插入

    设计一个支持快速插入的优化版vector,避免频繁扩容,提供基准测试对比

  3. 移动语义优化矩阵运算

    实现矩阵类,使用移动语义优化矩阵运算返回值,消除临时对象复制

  4. 写时复制字符串优化

    实现COW字符串类,支持高效的拷贝和修改操作,测量性能提升

  5. 自定义STL分配器

    实现符合C++标准的固定大小内存分配器,与std::vector集成并测试性能


答案与详解

多选题答案

  1. ACD

    B错误:指向对象需自行保证线程安全

    D正确:make_shared合并内存分配

  2. ABC

    D错误:全局对象可能导致初始化顺序问题

  3. AB

    C错误:移动可能抛出异常

    D错误:无关循环引用

  4. ABC

    D错误:自定义分配器应避免直接调用malloc

  5. ABC

    D错误:C++11后移动语义更优

  6. ABC

    D错误:扁平结构插入效率低

  7. ABC

    D错误:持久化需要其他机制

  8. ABC

    D错误:链表缓存不友好

  9. ABD

    C错误:移动应转移而非深拷贝

  10. AC

    B错误:全局锁影响性能

    D错误:静态池灵活性差


设计题答案示例

  1. 线程安全内存池实现
cpp 复制代码
#include <iostream>
#include <vector>
#include <memory>
#include <mutex>
#include <chrono>

template <size_t BlockSize, size_t PoolSize>
class ThreadSafeMemoryPool {
public:
    ThreadSafeMemoryPool() {
        for (int i = 0; i < PoolSize; ++i) {
            free_blocks.push(new char[BlockSize]);
        }
    }

    void* allocate() {
        std::lock_guard<std::mutex> lock(mtx);
        if (free_blocks.empty()) {
            return ::operator new(BlockSize);
        }
        void* block = free_blocks.top();
        free_blocks.pop();
        return block;
    }

    void deallocate(void* block) {
        std::lock_guard<std::mutex> lock(mtx);
        free_blocks.push(static_cast<char*>(block));
    }

    ~ThreadSafeMemoryPool() {
        while (!free_blocks.empty()) {
            delete[] free_blocks.top();
            free_blocks.pop();
        }
    }

private:
    std::mutex mtx;
    std::stack<char*> free_blocks;
};

// 测试用例
int main() {
    constexpr size_t TEST_SIZE = 1000000;
    ThreadSafeMemoryPool<64, 1000> pool;

    auto start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < TEST_SIZE; ++i) {
        void* p = pool.allocate();
        pool.deallocate(p);
    }
    auto end = std::chrono::high_resolution_clock::now();

    std::cout << "Pool time: " 
              << std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()
              << "μs\n";

    // 对比系统malloc
    start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < TEST_SIZE; ++i) {
        void* p = malloc(64);
        free(p);
    }
    end = std::chrono::high_resolution_clock::now();

    std::cout << "Malloc time: " 
              << std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()
              << "μs\n";
}
  1. 优化版Vector设计
cpp 复制代码
template <typename T>
class OptimizedVector {
public:
    OptimizedVector() : capacity_(4), size_(0) {
        data_ = static_cast<T*>(::operator new(capacity_ * sizeof(T)));
    }

    void push_back(T&& value) {
        if (size_ >= capacity_) {
            reserve(capacity_ * 2);
        }
        new (&data_[size_]) T(std::move(value));
        ++size_;
    }

    void reserve(size_t new_cap) {
        if (new_cap <= capacity_) return;

        T* new_data = static_cast<T*>(::operator new(new_cap * sizeof(T)));
        for (size_t i = 0; i < size_; ++i) {
            new (&new_data[i]) T(std::move(data_[i]));
            data_[i].~T();
        }
        ::operator delete(data_);
        data_ = new_data;
        capacity_ = new_cap;
    }

    // ...其他接口

private:
    T* data_;
    size_t capacity_;
    size_t size_;
};

// 测试用例
int main() {
    OptimizedVector<std::string> vec;
    constexpr int TEST_SIZE = 1000000;

    auto start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < TEST_SIZE; ++i) {
        vec.push_back(std::string(100, 'a'));
    }
    auto end = std::chrono::high_resolution_clock::now();

    std::cout << "Optimized vector: "
              << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
              << "ms\n";

    // 对比std::vector
    std::vector<std::string> std_vec;
    start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < TEST_SIZE; ++i) {
        std_vec.emplace_back(100, 'a');
    }
    end = std::chrono::high_resolution_clock::now();

    std::cout << "std::vector: "
              << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
              << "ms\n";
}

其他设计题目, 稍后补充


测试验证要点:

  1. 编译命令:g++ -std=c++17 -O2 test.cpp -o test
  2. 内存泄漏检测:Valgrind工具检查
  3. 性能对比:至少3次测试取平均值
  4. 多线程测试:使用std::async创建并发任务

通过这些问题和实现,可以有效巩固动态内存优化的核心概念,并在实践中验证各种优化技术的实际效果。

相关推荐
Scott9999HH6 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
2401_841495647 小时前
【操作系统】进程同步与互斥实验报告
c++·算法·操作系统·进程·并发·同步·互斥
fqbqrr7 小时前
2607C++,soui与安卓
c++·soui
码智社7 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海7 小时前
python 列表、元组、集合和字典
开发语言·python
克里斯蒂亚诺更新7 小时前
ruoyi角色权限的对应关系
笔记
萧瑟余晖8 小时前
JDK 26 新特性详解
java·开发语言
马优晨9 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
a11177610 小时前
2FA 验证码生成器(github登录验证 app)
笔记·学习
fqbqrr10 小时前
2607C++,使用微软detours勾挂工具
c++