C++学习:六个月从基础到就业——内存管理:自定义内存管理(下篇)
本文是我C++学习之旅系列的第二十二篇技术文章,也是第二阶段"C++进阶特性"的第七篇,主要介绍C++中的自定义内存管理技术(下篇)。查看完整系列目录了解更多内容。
引言
在上篇文章中,我们介绍了C++自定义内存管理的基础知识、核心技术和简单实现,包括重载new
/delete
运算符、使用placement new以及实现简单的内存池。本文将继续深入探讨更高级的内存管理技术,包括针对STL容器的自定义分配器、自定义内存管理在特定应用场景中的应用,以及最佳实践和性能考量。
针对STL容器的自定义分配器
C++标准库容器允许我们通过自定义分配器来控制内存分配行为,这是一种将自定义内存管理与标准容器结合的强大方式。
分配器的基本要求
STL分配器必须满足以下要求:
- 定义各种类型别名(如
value_type
、pointer
等) - 提供
allocate
和deallocate
方法 - 实现复制构造函数,特别是从其他类型的相同分配器构造
- 提供
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();
}
这个线程安全的内存池使用互斥锁保护共享数据结构,同时使用原子计数器跟踪分配统计,以最小化锁的开销。
自定义内存管理的最佳实践
何时使用自定义内存管理
并非所有场景都适合使用自定义内存管理。以下是一些适合考虑自定义内存管理的情况:
- 性能关键型应用:如游戏、实时系统、高频交易系统等,对延迟和吞吐量有严格要求。
- 资源受限环境:如嵌入式系统,内存资源有限,需要精确控制内存使用。
- 特定分配模式:应用有可预测的内存分配模式,如固定大小对象的频繁分配和释放。
- 内存碎片问题:长时间运行的应用程序容易出现内存碎片,需要专门的管理策略。
- 特殊内存布局要求:如SIMD编程需要对齐的内存,或特定的缓存友好数据布局。
避免常见陷阱
实现自定义内存管理时,应注意避免以下陷阱:
- 过度优化:不要仅为微小的性能改进而引入复杂的自定义内存管理。
- 线程安全问题:多线程环境中,确保内存管理是线程安全的,但不引入过多的锁竞争。
- 内存碎片:设计策略避免或减轻内存碎片,如合并相邻空闲块。
- 边界检查:实现适当的边界检查和错误处理,避免缓冲区溢出和内存损坏。
- 内存泄漏:确保所有分配的内存都有明确的释放路径。
性能测试与分析
自定义内存管理的目标之一是提高性能,因此必须进行彻底的测试和分析:
#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++中的一个强大工具,让我们能够控制内存分配和释放的细节,从而优化性能、减少内存碎片或实现特殊的内存布局。在本文中,我们探讨了:
- 针对STL容器的自定义分配器,包括跟踪分配器和基于内存池的分配器
- 特定应用场景中的自定义内存管理技术,包括游戏开发中的对象池、嵌入式系统中的静态分配器和高性能计算中的对齐分配器
- 多线程环境中的线程安全内存管理
- 自定义内存管理的最佳实践、常见陷阱以及性能测试方法
选择是否使用自定义内存管理应该基于具体的应用需求和性能目标。在许多情况下,标准库提供的内存管理工具已经足够好,但在性能关键或资源受限的环境中,合理的自定义内存管理策略可以带来显著的改进。
需要注意的是,现代C++提供了越来越多的工具来简化内存管理,如智能指针、标准容器和算法。在实现自定义内存管理之前,应该充分考虑是否可以通过这些标准工具解决问题。自定义内存管理应该是一种明智的选择,而不是默认的解决方案。
这是我C++学习之旅系列的第二十二篇技术文章。查看完整系列目录了解更多内容。