C++学习:六个月从基础到就业——内存管理:自定义内存管理(下篇)

C++学习:六个月从基础到就业——内存管理:自定义内存管理(下篇)

本文是我C++学习之旅系列的第二十二篇技术文章,也是第二阶段"C++进阶特性"的第七篇,主要介绍C++中的自定义内存管理技术(下篇)。查看完整系列目录了解更多内容。

引言

在上篇文章中,我们介绍了C++自定义内存管理的基础知识、核心技术和简单实现,包括重载new/delete运算符、使用placement new以及实现简单的内存池。本文将继续深入探讨更高级的内存管理技术,包括针对STL容器的自定义分配器、自定义内存管理在特定应用场景中的应用,以及最佳实践和性能考量。

针对STL容器的自定义分配器

C++标准库容器允许我们通过自定义分配器来控制内存分配行为,这是一种将自定义内存管理与标准容器结合的强大方式。

分配器的基本要求

STL分配器必须满足以下要求:

  1. 定义各种类型别名(如value_typepointer等)
  2. 提供allocatedeallocate方法
  3. 实现复制构造函数,特别是从其他类型的相同分配器构造
  4. 提供rebind模板以支持不同类型的分配

以下是一个跟踪内存分配和释放的自定义分配器示例:

#include <iostream>
#include <vector>
#include <list>
#include <string>
#include <memory>

// 跟踪分配的自定义分配器
template <typename T>
class TracingAllocator {
public:
    // 必须的类型定义
    using value_type = T;
    using pointer = T*;
    using const_pointer = const T*;
    using reference = T&;
    using const_reference = const T&;
    using size_type = std::size_t;
    using difference_type = std::ptrdiff_t;
    
    // 用于rebind的模板
    template <typename U>
    struct rebind {
        using other = TracingAllocator<U>;
    };
    
    // 构造函数
    TracingAllocator() noexcept = default;
    
    // 复制构造函数,允许从其他类型的TracingAllocator构造
    template <typename U>
    TracingAllocator(const TracingAllocator<U>&) noexcept {}
    
    // 分配内存
    T* allocate(std::size_t n) {
        totalAllocated += n * sizeof(T);
        allocCount++;
        
        std::cout << "Allocating " << n << " objects of size " << sizeof(T)
                  << " (total: " << totalAllocated << " bytes, count: " << allocCount << ")" << std::endl;
        
        return static_cast<T*>(::operator new(n * sizeof(T)));
    }
    
    // 释放内存
    void deallocate(T* p, std::size_t n) noexcept {
        totalAllocated -= n * sizeof(T);
        allocCount--;
        
        std::cout << "Deallocating " << n << " objects of size " << sizeof(T)
                  << " (remaining: " << totalAllocated << " bytes, count: " << allocCount << ")" << std::endl;
        
        ::operator delete(p);
    }
    
    // 判断两个分配器是否等价
    bool operator==(const TracingAllocator&) const noexcept {
        return true;
    }
    
    bool operator!=(const TracingAllocator& other) const noexcept {
        return !(*this == other);
    }
    
    // 静态计数器
    static size_t totalAllocated;
    static size_t allocCount;
    
    // 重置计数器
    static void resetCounters() {
        totalAllocated = 0;
        allocCount = 0;
    }
};

// 静态成员初始化
template <typename T>
size_t TracingAllocator<T>::totalAllocated = 0;

template <typename T>
size_t TracingAllocator<T>::allocCount = 0;

void stlAllocatorDemo() {
    // 使用自定义分配器的vector
    std::cout << "=== Vector with tracing allocator ===" << std::endl;
    TracingAllocator<int>::resetCounters();
    {
        std::vector<int, TracingAllocator<int>> v;
        
        // 添加元素
        for (int i = 0; i < 10; ++i) {
            v.push_back(i);
            std::cout << "Vector size: " << v.size() << ", capacity: " << v.capacity() << std::endl;
        }
    }  // vector离开作用域,自动释放内存
    std::cout << "Final stats: total=" << TracingAllocator<int>::totalAllocated
              << ", count=" << TracingAllocator<int>::allocCount << std::endl;
    
    // 使用自定义分配器的list
    std::cout << "\n=== List with tracing allocator ===" << std::endl;
    TracingAllocator<int>::resetCounters();
    {
        std::list<int, TracingAllocator<int>> l;
        
        // 添加元素
        for (int i = 0; i < 10; ++i) {
            l.push_back(i);
        }
        
        // 移除一些元素
        l.pop_front();
        l.pop_back();
    }
    std::cout << "Final stats: total=" << TracingAllocator<int>::totalAllocated
              << ", count=" << TracingAllocator<int>::allocCount << std::endl;
    
    // 使用自定义分配器的string
    std::cout << "\n=== String with tracing allocator ===" << std::endl;
    TracingAllocator<char>::resetCounters();
    {
        std::basic_string<char, std::char_traits<char>, TracingAllocator<char>> s;
        
        // 增加字符串长度
        s = "Hello";
        std::cout << "String: " << s << std::endl;
        
        s += ", World!";
        std::cout << "String: " << s << std::endl;
        
        // 减少字符串长度
        s = "Hi";
        std::cout << "String: " << s << std::endl;
    }
    std::cout << "Final stats: total=" << TracingAllocator<char>::totalAllocated
              << ", count=" << TracingAllocator<char>::allocCount << std::endl;
}

基于内存池的分配器

结合上篇文章介绍的内存池技术,我们可以实现一个高效的基于内存池的STL分配器:

#include <iostream>
#include <vector>
#include <list>
#include <cassert>

// 简化版内存池实现(基于上篇文章的内容)
template <size_t BlockSize = 4096>
class MemoryPool {
private:
    // 内存块结构
    struct Block {
        char* data;
        Block* next;
        
        Block(size_t size) : next(nullptr) {
            data = new char[size];
        }
        
        ~Block() {
            delete[] data;
        }
    };
    
    Block* currentBlock;
    char* currentPos;
    char* currentEnd;
    size_t blockSize;
    
public:
    MemoryPool() : currentBlock(nullptr), currentPos(nullptr), currentEnd(nullptr), blockSize(BlockSize) {}
    
    ~MemoryPool() {
        while (currentBlock) {
            Block* temp = currentBlock->next;
            delete currentBlock;
            currentBlock = temp;
        }
    }
    
    // 分配内存
    void* allocate(size_t size, size_t alignment = 8) {
        // 计算对齐调整
        size_t adjustment = 0;
        if (currentPos) {
            size_t misalignment = reinterpret_cast<std::uintptr_t>(currentPos) & (alignment - 1);
            if (misalignment != 0) {
                adjustment = alignment - misalignment;
            }
        }
        
        // 检查当前块剩余空间
        if (currentPos + adjustment + size > currentEnd) {
            // 分配新块
            size_t allocSize = std::max(blockSize, size + alignment);
            Block* newBlock = new Block(allocSize);
            
            // 更新链表
            newBlock->next = currentBlock;
            currentBlock = newBlock;
            
            // 更新指针
            currentPos = currentBlock->data;
            currentEnd = currentPos + allocSize;
        }
        
        // 应用对齐调整
        currentPos += adjustment;
        
        // 分配内存并更新当前位置
        char* result = currentPos;
        currentPos += size;
        
        return result;
    }
    
    // 释放内存(在我们的内存池中不做任何事情)
    void deallocate(void*, size_t) {
        // 内存池使用批量释放策略,这里不做任何操作
    }
};

// 基于内存池的STL分配器
template <typename T, size_t BlockSize = 4096>
class PoolAllocator {
private:
    // 类型特定的内存池
    static MemoryPool<BlockSize>& getPool() {
        static MemoryPool<BlockSize> pool;
        return pool;
    }
    
public:
    // STL分配器必须的类型定义
    using value_type = T;
    using pointer = T*;
    using const_pointer = const T*;
    using reference = T&;
    using const_reference = const T&;
    using size_type = std::size_t;
    using difference_type = std::ptrdiff_t;
    
    template <typename U>
    struct rebind {
        using other = PoolAllocator<U, BlockSize>;
    };
    
    // 构造函数
    PoolAllocator() noexcept = default;
    
    template <typename U>
    PoolAllocator(const PoolAllocator<U, BlockSize>&) noexcept {}
    
    // 分配内存
    T* allocate(std::size_t n) {
        return static_cast<T*>(getPool().allocate(n * sizeof(T), alignof(T)));
    }
    
    // 释放内存
    void deallocate(T* p, std::size_t n) noexcept {
        getPool().deallocate(p, n * sizeof(T));
    }
    
    // 相等比较运算符
    bool operator==(const PoolAllocator&) const noexcept {
        return true;
    }
    
    bool operator!=(const PoolAllocator& other) const noexcept {
        return !(*this == other);
    }
};

void poolAllocatorDemo() {
    std::cout << "=== Pool Allocator Demo ===" << std::endl;
    
    // 创建使用池分配器的vector
    std::vector<int, PoolAllocator<int>> poolVector;
    
    // 添加元素
    for (int i = 0; i < 1000; ++i) {
        poolVector.push_back(i);
    }
    
    std::cout << "Vector size: " << poolVector.size() << std::endl;
    
    // 创建使用池分配器的list
    std::list<double, PoolAllocator<double>> poolList;
    
    // 添加元素
    for (int i = 0; i < 1000; ++i) {
        poolList.push_back(i * 1.1);
    }
    
    std::cout << "List size: " << poolList.size() << std::endl;
    
    // 使用对象
    std::cout << "First vector element: " << poolVector.front() << std::endl;
    std::cout << "Last vector element: " << poolVector.back() << std::endl;
    std::cout << "First list element: " << poolList.front() << std::endl;
    std::cout << "Last list element: " << poolList.back() << std::endl;
}

使用内存池的分配器可以显著减少内存分配的开销,特别是对于频繁分配小对象的情况,如许多小型STL容器实例。

自定义内存管理在特定应用场景中的应用

游戏开发中的内存管理

游戏开发对内存管理有特殊要求,包括高性能、可预测的延迟和减少内存碎片。以下是一个为游戏对象设计的对象池实现:

#include <iostream>
#include <vector>
#include <array>
#include <bitset>
#include <cassert>

// 简单的游戏对象
struct GameObject {
    float x, y, z;  // 位置
    float vx, vy, vz;  // 速度
    int health;  // 生命值
    int id;  // 唯一标识符
    bool active;  // 是否活跃
    
    GameObject() : x(0), y(0), z(0), vx(0), vy(0), vz(0), health(100), id(0), active(false) {}
    
    void update(float deltaTime) {
        if (!active) return;
        
        // 简单的物理更新
        x += vx * deltaTime;
        y += vy * deltaTime;
        z += vz * deltaTime;
    }
    
    void activate(int newId, float posX, float posY, float posZ) {
        id = newId;
        x = posX;
        y = posY;
        z = posZ;
        health = 100;
        active = true;
    }
    
    void deactivate() {
        active = false;
    }
};

// 固定大小的游戏对象池
template <typename T, size_t PoolSize>
class GameObjectPool {
private:
    std::array<T, PoolSize> objects;  // 对象存储
    std::bitset<PoolSize> activeFlags;  // 活跃状态标记
    size_t nextId;  // 下一个可用ID
    
public:
    GameObjectPool() : nextId(1) {
        // 初始化所有对象为非活跃
        activeFlags.reset();
    }
    
    // 获取一个新对象
    T* create(float x, float y, float z) {
        // 查找第一个非活跃的对象
        for (size_t i = 0; i < PoolSize; ++i) {
            if (!activeFlags[i]) {
                // 找到非活跃对象
                T& obj = objects[i];
                obj.activate(nextId++, x, y, z);
                activeFlags.set(i);
                return &obj;
            }
        }
        
        // 池已满
        std::cout << "Warning: Object pool full!" << std::endl;
        return nullptr;
    }
    
    // 释放对象回池
    void destroy(T* obj) {
        if (!obj) return;
        
        // 计算对象在池中的索引
        size_t index = obj - &objects[0];
        
        // 检查索引有效性
        if (index < PoolSize && activeFlags[index]) {
            objects[index].deactivate();
            activeFlags.reset(index);
        }
    }
    
    // 更新所有活跃对象
    void updateAll(float deltaTime) {
        for (size_t i = 0; i < PoolSize; ++i) {
            if (activeFlags[i]) {
                objects[i].update(deltaTime);
            }
        }
    }
    
    // 获取活跃对象数量
    size_t getActiveCount() const {
        return activeFlags.count();
    }
    
    // 获取容量
    size_t getCapacity() const {
        return PoolSize;
    }
};

void gameObjectPoolDemo() {
    std::cout << "=== Game Object Pool Demo ===" << std::endl;
    
    // 创建一个容量为1000的游戏对象池
    GameObjectPool<GameObject, 1000> pool;
    
    std::cout << "Initial pool state: " << pool.getActiveCount() << " active, "
              << pool.getCapacity() << " capacity" << std::endl;
    
    // 创建一些对象
    std::vector<GameObject*> objects;
    for (int i = 0; i < 100; ++i) {
        GameObject* obj = pool.create(i * 1.0f, i * 2.0f, i * 3.0f);
        if (obj) {
            // 设置一些速度
            obj->vx = (i % 3) * 1.0f;
            obj->vy = (i % 5) * 0.5f;
            obj->vz = (i % 7) * 0.3f;
            objects.push_back(obj);
        }
    }
    
    std::cout << "After creation: " << pool.getActiveCount() << " active objects" << std::endl;
    
    // 模拟游戏循环
    for (int frame = 0; frame < 10; ++frame) {
        // 模拟帧更新
        pool.updateAll(0.016f);  // ~60fps
        
        // 每隔几帧销毁一些对象并创建新的对象
        if (frame % 3 == 0 && !objects.empty()) {
            // 销毁一些对象
            size_t removeCount = objects.size() / 10;  // 销毁10%
            for (size_t i = 0; i < removeCount; ++i) {
                if (!objects.empty()) {
                    pool.destroy(objects.back());
                    objects.pop_back();
                }
            }
            
            // 创建一些新对象
            for (int i = 0; i < 15; ++i) {
                GameObject* obj = pool.create(frame * 10.0f + i, frame * 5.0f, frame * 2.0f);
                if (obj) {
                    obj->vx = frame * 0.1f;
                    obj->vy = i * 0.2f;
                    obj->vz = (frame + i) * 0.05f;
                    objects.push_back(obj);
                }
            }
        }
        
        std::cout << "Frame " << frame << ": " << pool.getActiveCount() << " active objects" << std::endl;
    }
    
    // 清理所有对象
    for (auto obj : objects) {
        pool.destroy(obj);
    }
    objects.clear();
    
    std::cout << "Final state: " << pool.getActiveCount() << " active objects" << std::endl;
}

这种对象池实现非常适合游戏中的实体管理,如粒子系统、子弹、敌人等,它避免了频繁的动态内存分配和释放,提高了游戏性能和稳定性。

嵌入式系统中的内存管理

在嵌入式系统中,内存资源通常非常有限,而且可能没有堆内存管理器。以下是一个适用于嵌入式系统的静态内存分配器:

#include <iostream>
#include <cstring>
#include <cassert>
#include <array>

// 嵌入式系统的静态内存分配器
class StaticAllocator {
private:
    struct MemoryBlock {
        size_t size;  // 块大小
        bool used;    // 是否已使用
    };
    
    static constexpr size_t MemorySize = 4096;  // 总内存大小
    static constexpr size_t BlockHeaderSize = sizeof(MemoryBlock);
    
    // 静态内存区域
    std::array<char, MemorySize> memory;
    MemoryBlock* firstBlock;
    
public:
    StaticAllocator() {
        // 初始化为单个大的空闲块
        firstBlock = reinterpret_cast<MemoryBlock*>(memory.data());
        firstBlock->size = MemorySize - BlockHeaderSize;
        firstBlock->used = false;
    }
    
    // 分配内存
    void* allocate(size_t size) {
        // 对齐到4字节边界
        size = (size + 3) & ~3;
        
        // 查找足够大的空闲块
        MemoryBlock* current = firstBlock;
        while (current) {
            if (!current->used && current->size >= size) {
                // 找到合适的块
                
                // 如果块足够大,分割它
                if (current->size >= size + BlockHeaderSize + 4) {  // 4是最小可用块大小
                    // 计算新块的位置
                    char* currentEnd = reinterpret_cast<char*>(current) + BlockHeaderSize + size;
                    MemoryBlock* newBlock = reinterpret_cast<MemoryBlock*>(currentEnd);
                    
                    // 初始化新块
                    newBlock->size = current->size - size - BlockHeaderSize;
                    newBlock->used = false;
                    
                    // 调整当前块大小
                    current->size = size;
                }
                
                // 标记为已使用
                current->used = true;
                
                // 返回数据区域指针
                return reinterpret_cast<char*>(current) + BlockHeaderSize;
            }
            
            // 移动到下一个块
            current = nextBlock(current);
        }
        
        return nullptr;  // 没有足够大的块
    }
    
    // 释放内存
    void deallocate(void* ptr) {
        if (!ptr || !containsAddress(ptr)) return;
        
        // 获取块头
        MemoryBlock* block = reinterpret_cast<MemoryBlock*>(
            reinterpret_cast<char*>(ptr) - BlockHeaderSize
        );
        
        // 标记为未使用
        block->used = false;
        
        // 合并相邻空闲块
        coalesce();
    }
    
    // 打印内存使用情况
    void printStatus() const {
        std::cout << "Static Allocator Status:" << std::endl;
        
        MemoryBlock* current = firstBlock;
        size_t blockCount = 0;
        size_t freeBlocks = 0;
        size_t freeMemory = 0;
        
        while (current) {
            std::cout << "Block " << blockCount
                      << ": Size=" << current->size
                      << ", Status=" << (current->used ? "Used" : "Free") << std::endl;
                      
            if (!current->used) {
                freeBlocks++;
                freeMemory += current->size;
            }
            
            blockCount++;
            current = nextBlock(current);
        }
        
        std::cout << "Total blocks: " << blockCount << std::endl;
        std::cout << "Free blocks: " << freeBlocks << std::endl;
        std::cout << "Free memory: " << freeMemory << " bytes" << std::endl;
    }
    
private:
    // 获取下一个块
    MemoryBlock* nextBlock(MemoryBlock* block) const {
        if (!block) return nullptr;
        
        char* nextAddress = reinterpret_cast<char*>(block) + BlockHeaderSize + block->size;
        if (nextAddress >= memory.data() + MemorySize) {
            return nullptr;  // 超出内存范围
        }
        
        return reinterpret_cast<MemoryBlock*>(nextAddress);
    }
    
    // 检查地址是否在内存池范围内
    bool containsAddress(void* ptr) const {
        return ptr >= memory.data() && ptr < memory.data() + MemorySize;
    }
    
    // 合并相邻空闲块
    void coalesce() {
        MemoryBlock* current = firstBlock;
        
        while (current) {
            MemoryBlock* next = nextBlock(current);
            
            // 如果当前块和下一块都是空闲的,合并它们
            if (next && !current->used && !next->used) {
                // 增加当前块大小
                current->size += BlockHeaderSize + next->size;
                
                // 不需要更新指针,因为下一次循环仍会检查current
            } else {
                // 移动到下一块
                current = next;
            }
        }
    }
};

void staticAllocatorDemo() {
    std::cout << "=== Static Allocator Demo ===" << std::endl;
    
    StaticAllocator allocator;
    
    // 打印初始状态
    std::cout << "Initial state:" << std::endl;
    allocator.printStatus();
    
    // 分配一些内存
    void* p1 = allocator.allocate(100);
    void* p2 = allocator.allocate(200);
    void* p3 = allocator.allocate(300);
    
    // 写入一些数据
    if (p1) std::strcpy(static_cast<char*>(p1), "Hello from static allocator");
    if (p2) std::strcpy(static_cast<char*>(p2), "This is a test of embedded memory management");
    if (p3) std::memset(p3, 0xAA, 300);  // 填充模式
    
    std::cout << "\nAfter allocations:" << std::endl;
    allocator.printStatus();
    
    if (p1) std::cout << "p1 data: " << static_cast<char*>(p1) << std::endl;
    
    // 释放中间的块
    allocator.deallocate(p2);
    
    std::cout << "\nAfter deallocating p2:" << std::endl;
    allocator.printStatus();
    
    // 再次分配,应该重用之前释放的空间
    void* p4 = allocator.allocate(150);
    
    std::cout << "\nAfter allocating p4:" << std::endl;
    allocator.printStatus();
    
    // 释放所有内存
    allocator.deallocate(p1);
    allocator.deallocate(p3);
    allocator.deallocate(p4);
    
    std::cout << "\nAfter deallocating all memory:" << std::endl;
    allocator.printStatus();
}

这种静态分配器适用于没有堆内存的嵌入式系统,它使用预分配的静态内存区域,并通过简单的首次适应算法来分配内存。

高性能计算中的对齐内存分配

在高性能计算和SIMD(单指令多数据)编程中,内存对齐对性能有显著影响。以下是一个自定义的对齐内存分配器:

#include <iostream>
#include <vector>
#include <chrono>
#include <random>
#include <algorithm>
#include <cstring>

#ifdef _WIN32
#include <malloc.h>
#else
#include <cstdlib>
#endif

// 对齐内存分配器
template <typename T, size_t Alignment = 32>  // 默认32字节对齐,适合AVX指令
class AlignedAllocator {
public:
    // 必须的类型定义
    using value_type = T;
    using pointer = T*;
    using const_pointer = const T*;
    using reference = T&;
    using const_reference = const T&;
    using size_type = std::size_t;
    using difference_type = std::ptrdiff_t;
    
    template <typename U>
    struct rebind {
        using other = AlignedAllocator<U, Alignment>;
    };
    
    AlignedAllocator() noexcept = default;
    
    template <typename U>
    AlignedAllocator(const AlignedAllocator<U, Alignment>&) noexcept {}
    
    // 分配对齐的内存
    T* allocate(std::size_t n) {
        if (n == 0) return nullptr;
        
        void* ptr = nullptr;
        size_t size = n * sizeof(T);
        
#ifdef _WIN32
        ptr = _aligned_malloc(size, Alignment);
#else
        // POSIX系统
        int result = posix_memalign(&ptr, Alignment, size);
        if (result != 0) ptr = nullptr;
#endif
        
        if (!ptr) throw std::bad_alloc();
        return static_cast<T*>(ptr);
    }
    
    // 释放对齐的内存
    void deallocate(T* p, std::size_t) noexcept {
        if (!p) return;
        
#ifdef _WIN32
        _aligned_free(p);
#else
        std::free(p);
#endif
    }
    
    // 判断相等
    bool operator==(const AlignedAllocator&) const noexcept {
        return true;
    }
    
    bool operator!=(const AlignedAllocator& other) const noexcept {
        return !(*this == other);
    }
};

// 简单的向量加法测试
void vectorAddTest() {
    std::cout << "=== Vector Addition Performance Test ===" << std::endl;
    
    const size_t size = 10000000;  // 1000万元素
    
    // 使用标准分配器
    {
        auto start = std::chrono::high_resolution_clock::now();
        
        std::vector<float> a(size);
        std::vector<float> b(size);
        std::vector<float> c(size);
        
        // 初始化数据
        for (size_t i = 0; i < size; ++i) {
            a[i] = static_cast<float>(i);
            b[i] = static_cast<float>(i * 2);
        }
        
        // 向量加法
        for (size_t i = 0; i < size; ++i) {
            c[i] = a[i] + b[i];
        }
        
        auto end = std::chrono::high_resolution_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
        
        std::cout << "Standard allocator time: " << duration << " ms" << std::endl;
        std::cout << "a[0] address: " << &a[0] << ", alignment: " << (reinterpret_cast<uintptr_t>(&a[0]) % Alignment) << std::endl;
    }
    
    // 使用对齐分配器
    {
        auto start = std::chrono::high_resolution_clock::now();
        
        std::vector<float, AlignedAllocator<float>> a(size);
        std::vector<float, AlignedAllocator<float>> b(size);
        std::vector<float, AlignedAllocator<float>> c(size);
        
        // 初始化数据
        for (size_t i = 0; i < size; ++i) {
            a[i] = static_cast<float>(i);
            b[i] = static_cast<float>(i * 2);
        }
        
        // 向量加法
        for (size_t i = 0; i < size; ++i) {
            c[i] = a[i] + b[i];
        }
        
        auto end = std::chrono::high_resolution_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
        
        std::cout << "Aligned allocator time: " << duration << " ms" << std::endl;
        std::cout << "a[0] address: " << &a[0] << ", alignment: " << (reinterpret_cast<uintptr_t>(&a[0]) % Alignment) << std::endl;
    }
}

使用对齐分配器可以确保数据位于对齐的内存地址上,这对于SIMD指令(如SSE、AVX等)的性能至关重要,因为它们通常要求数据在特定的内存边界上对齐。

多线程环境中的内存管理

在多线程环境中,内存分配必须是线程安全的。以下是一个线程安全的内存池实现:

#include <iostream>
#include <vector>
#include <mutex>
#include <thread>
#include <atomic>

// 线程安全的内存池
template <size_t BlockSize = 4096>
class ThreadSafeMemoryPool {
private:
    struct Block {
        char data[BlockSize];
        Block* next;
    };
    
    struct Chunk {
        Chunk* next;
    };
    
    // 加锁访问的数据
    mutable std::mutex mutex;
    Block* firstBlock;
    Chunk* freeList;
    size_t chunkSize;
    
    // 原子计数器,不需要锁
    std::atomic<size_t> allocatedChunks;
    std::atomic<size_t> totalAllocated;
    
public:
    ThreadSafeMemoryPool(size_t chunkSz = 32) 
        : firstBlock(nullptr), freeList(nullptr), chunkSize(chunkSz), 
          allocatedChunks(0), totalAllocated(0) {
        // 确保chunk大小不小于sizeof(Chunk),并且是8的整数倍
        chunkSize = std::max(chunkSize, sizeof(Chunk));
        chunkSize = (chunkSize + 7) & ~7;
    }
    
    ~ThreadSafeMemoryPool() {
        // 释放所有块
        std::lock_guard<std::mutex> lock(mutex);
        while (firstBlock) {
            Block* next = firstBlock->next;
            delete firstBlock;
            firstBlock = next;
        }
    }
    
    // 分配内存
    void* allocate(size_t size) {
        // 如果请求大小大于chunkSize,直接使用标准分配
        if (size > chunkSize) {
            void* ptr = ::operator new(size);
            totalAllocated += size;
            return ptr;
        }
        
        std::lock_guard<std::mutex> lock(mutex);
        
        // 如果空闲列表为空,分配新块并将其分割成chunks
        if (!freeList) {
            Block* newBlock = new Block;
            newBlock->next = firstBlock;
            firstBlock = newBlock;
            
            // 计算每个块中的chunk数量
            size_t chunksPerBlock = BlockSize / chunkSize;
            
            // 将块分成chunks并添加到空闲列表
            char* blockStart = newBlock->data;
            for (size_t i = 0; i < chunksPerBlock; ++i) {
                char* chunkPos = blockStart + i * chunkSize;
                Chunk* chunk = reinterpret_cast<Chunk*>(chunkPos);
                chunk->next = freeList;
                freeList = chunk;
            }
        }
        
        // 从空闲列表中取出第一个chunk
        Chunk* chunk = freeList;
        freeList = freeList->next;
        
        allocatedChunks++;
        totalAllocated += chunkSize;
        
        return chunk;
    }
    
    // 释放内存
    void deallocate(void* ptr, size_t size) {
        if (!ptr) return;
        
        // 如果大小大于chunkSize,直接使用标准释放
        if (size > chunkSize) {
            ::operator delete(ptr);
            totalAllocated -= size;
            return;
        }
        
        std::lock_guard<std::mutex> lock(mutex);
        
        // 将ptr放回空闲列表
        Chunk* chunk = reinterpret_cast<Chunk*>(ptr);
        chunk->next = freeList;
        freeList = chunk;
        
        allocatedChunks--;
        totalAllocated -= chunkSize;
    }
    
    // 打印统计信息
    void printStatistics() const {
        std::lock_guard<std::mutex> lock(mutex);
        
        std::cout << "Memory Pool Statistics:" << std::endl;
        std::cout << "Chunk size: " << chunkSize << " bytes" << std::endl;
        std::cout << "Allocated chunks: " << allocatedChunks << std::endl;
        std::cout << "Total allocated memory: " << totalAllocated << " bytes" << std::endl;
    }
};

// 多线程测试函数
void threadSafePoolTest() {
    std::cout << "=== Thread-Safe Memory Pool Test ===" << std::endl;
    
    ThreadSafeMemoryPool<8192> pool(64);  // 8KB块,64字节chunk
    
    // 创建多个线程并发分配和释放内存
    std::vector<std::thread> threads;
    const int numThreads = 4;
    const int allocsPerThread = 10000;
    
    std::atomic<int> readyCount(0);
    bool startFlag = false;
    
    auto threadFunc = [&](int threadId) {
        std::vector<void*> allocations;
        allocations.reserve(allocsPerThread);
        
        // 线程同步点:等待所有线程准备好
        readyCount.fetch_add(1);
        while (!startFlag) {
            std::this_thread::yield();
        }
        
        // 执行分配
        for (int i = 0; i < allocsPerThread; ++i) {
            size_t size = 32 + (i % 32);  // 32-63字节
            void* ptr = pool.allocate(size);
            if (ptr) {
                // 写入一些数据
                std::memset(ptr, threadId, size);
                allocations.push_back(ptr);
            }
            
            // 释放一些先前分配的内存
            if (i % 3 == 0 && !allocations.empty()) {
                size_t index = i % allocations.size();
                void* toFree = allocations[index];
                size_t freeSize = 32 + (index % 32);
                pool.deallocate(toFree, freeSize);
                allocations[index] = nullptr;
            }
        }
        
        // 释放所有剩余分配
        for (void* ptr : allocations) {
            if (ptr) {
                size_t size = 32 + (rand() % 32);
                pool.deallocate(ptr, size);
            }
        }
    };
    
    // 启动所有线程
    for (int i = 0; i < numThreads; ++i) {
        threads.emplace_back(threadFunc, i);
    }
    
    // 等待所有线程准备好
    while (readyCount < numThreads) {
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    
    // 计时开始
    auto start = std::chrono::high_resolution_clock::now();
    
    // 让所有线程同时开始
    startFlag = true;
    
    // 等待所有线程完成
    for (auto& t : threads) {
        t.join();
    }
    
    // 计时结束
    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
    
    // 打印结果
    std::cout << "All threads completed in " << duration << " ms" << std::endl;
    pool.printStatistics();
}

这个线程安全的内存池使用互斥锁保护共享数据结构,同时使用原子计数器跟踪分配统计,以最小化锁的开销。

自定义内存管理的最佳实践

何时使用自定义内存管理

并非所有场景都适合使用自定义内存管理。以下是一些适合考虑自定义内存管理的情况:

  1. 性能关键型应用:如游戏、实时系统、高频交易系统等,对延迟和吞吐量有严格要求。
  2. 资源受限环境:如嵌入式系统,内存资源有限,需要精确控制内存使用。
  3. 特定分配模式:应用有可预测的内存分配模式,如固定大小对象的频繁分配和释放。
  4. 内存碎片问题:长时间运行的应用程序容易出现内存碎片,需要专门的管理策略。
  5. 特殊内存布局要求:如SIMD编程需要对齐的内存,或特定的缓存友好数据布局。

避免常见陷阱

实现自定义内存管理时,应注意避免以下陷阱:

  1. 过度优化:不要仅为微小的性能改进而引入复杂的自定义内存管理。
  2. 线程安全问题:多线程环境中,确保内存管理是线程安全的,但不引入过多的锁竞争。
  3. 内存碎片:设计策略避免或减轻内存碎片,如合并相邻空闲块。
  4. 边界检查:实现适当的边界检查和错误处理,避免缓冲区溢出和内存损坏。
  5. 内存泄漏:确保所有分配的内存都有明确的释放路径。

性能测试与分析

自定义内存管理的目标之一是提高性能,因此必须进行彻底的测试和分析:

#include <iostream>
#include <chrono>
#include <vector>
#include <algorithm>
#include <random>
#include <numeric>

// 性能测试辅助函数
template <typename Allocator>
void performanceTest(const std::string& name) {
    std::cout << "=== Performance Test: " << name << " ===" << std::endl;
    
    constexpr size_t numOperations = 1000000;  // 100万次操作
    constexpr size_t maxAllocSize = 256;       // 最大分配大小
    
    // 随机数生成
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<size_t> sizeDist(8, maxAllocSize);
    
    // 准备工作:预生成随机大小
    std::vector<size_t> sizes(numOperations);
    std::generate(sizes.begin(), sizes.end(), [&]() { return sizeDist(gen); });
    
    // 存储指针和大小
    std::vector<void*> pointers;
    pointers.reserve(numOperations / 2);  // 假设平均有一半的内存被保留
    
    Allocator allocator;
    
    // 测试1:纯分配
    auto start = std::chrono::high_resolution_clock::now();
    
    for (size_t i = 0; i < numOperations; ++i) {
        void* ptr = allocator.allocate(sizes[i]);
        pointers.push_back(ptr);
    }
    
    auto end = std::chrono::high_resolution_clock::now();
    auto allocTime = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
    
    std::cout << "Pure allocation time: " << allocTime << " ms" << std::endl;
    std::cout << "Allocations: " << pointers.size() << std::endl;
    
    // 测试2:随机分配和释放
    pointers.clear();
    start = std::chrono::high_resolution_clock::now();
    
    for (size_t i = 0; i < numOperations; ++i) {
        // 75%的概率分配,25%的概率释放
        if (pointers.empty() || (gen() % 4 != 0)) {
            // 分配
            void* ptr = allocator.allocate(sizes[i]);
            pointers.push_back(ptr);
        } else {
            // 释放
            size_t index = gen() % pointers.size();
            void* ptr = pointers[index];
            
            // 从列表中移除
            std::swap(pointers[index], pointers.back());
            pointers.pop_back();
            
            allocator.deallocate(ptr, sizes[i % sizes.size()]);  // 使用循环大小
        }
    }
    
    end = std::chrono::high_resolution_clock::now();
    auto mixedTime = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
    
    std::cout << "Mixed allocation/deallocation time: " << mixedTime << " ms" << std::endl;
    std::cout << "Final allocations: " << pointers.size() << std::endl;
    
    // 释放所有剩余内存
    for (size_t i = 0; i < pointers.size(); ++i) {
        allocator.deallocate(pointers[i], sizes[i % sizes.size()]);
    }
}

// 标准分配器包装
struct StandardAllocator {
    void* allocate(size_t size) {
        return ::operator new(size);
    }
    
    void deallocate(void* ptr, size_t) {
        ::operator delete(ptr);
    }
};

// 基于内存池的分配器包装
struct PooledAllocator {
    ThreadSafeMemoryPool<65536> pool;  // 64KB块
    
    PooledAllocator() : pool(64) {}  // 64字节chunk
    
    void* allocate(size_t size) {
        return pool.allocate(size);
    }
    
    void deallocate(void* ptr, size_t size) {
        pool.deallocate(ptr, size);
    }
};

// 对齐分配器包装
struct AlignedAllocatorWrapper {
    void* allocate(size_t size) {
        void* ptr = nullptr;
#ifdef _WIN32
        ptr = _aligned_malloc(size, 32);  // 32字节对齐
#else
        posix_memalign(&ptr, 32, size);
#endif
        return ptr;
    }
    
    void deallocate(void* ptr, size_t) {
#ifdef _WIN32
        _aligned_free(ptr);
#else
        free(ptr);
#endif
    }
};

void allAllocatorTests() {
    // 测试标准分配器
    performanceTest<StandardAllocator>("Standard Allocator");
    
    // 测试内存池分配器
    performanceTest<PooledAllocator>("Pooled Allocator");
    
    // 测试对齐分配器
    performanceTest<AlignedAllocatorWrapper>("Aligned Allocator");
}

性能测试应该涵盖各种场景,包括纯分配、混合分配和释放、不同大小的分配等,并与标准分配器进行比较,以确保自定义实现确实提供了性能优势。

总结

自定义内存管理是C++中的一个强大工具,让我们能够控制内存分配和释放的细节,从而优化性能、减少内存碎片或实现特殊的内存布局。在本文中,我们探讨了:

  1. 针对STL容器的自定义分配器,包括跟踪分配器和基于内存池的分配器
  2. 特定应用场景中的自定义内存管理技术,包括游戏开发中的对象池、嵌入式系统中的静态分配器和高性能计算中的对齐分配器
  3. 多线程环境中的线程安全内存管理
  4. 自定义内存管理的最佳实践、常见陷阱以及性能测试方法

选择是否使用自定义内存管理应该基于具体的应用需求和性能目标。在许多情况下,标准库提供的内存管理工具已经足够好,但在性能关键或资源受限的环境中,合理的自定义内存管理策略可以带来显著的改进。

需要注意的是,现代C++提供了越来越多的工具来简化内存管理,如智能指针、标准容器和算法。在实现自定义内存管理之前,应该充分考虑是否可以通过这些标准工具解决问题。自定义内存管理应该是一种明智的选择,而不是默认的解决方案。


这是我C++学习之旅系列的第二十二篇技术文章。查看完整系列目录了解更多内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

superior tigre

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值