1.内存池简介
内存池是池化技术中的一种形式,通常我们在编写程序的时候使用new和delete关键字向操作系统申请内存。但是每一个申请内存和释放内存的时候,都需要和操作系统的系统调用进行交互,并在堆中进行内存分配。如果操作过于频繁,就会出现大量的内存碎片进而降低内存的分配性能。从而导致内存分配失败的情况。对于内存申请过程而言,其实就是一次申请指针的过程,对于每一次的内存分配,都会消耗一次分配内存的时间,如果在程序开始的时候就分配好一块合理的内存区域,当我们下次使用的时候,就可以直接从分配的内存中进行使用,降低申请内存分配时操作系统进行复杂调度过程的时间。
优势:
1.从原理上比new
和malloc
快
2.分配内存的时候overhead
3.基本不会出现内存碎片
4.无需一个一个释放内存,只需要释放内存池就可以
2.函数实现
2.1 主函数
#include <iostream>
#include <ctime>
#include <vector>
#include <cassert>
#include "StackAlloc.h"
#include "MemoryPool.h"
using namespace std;
const int REPS = 100;
const int ELEMS = 1e8;
int main() {
clock_t start;
// Use standard allocator
StackAlloc<int, std::allocator<int> > stackDefault;
start = clock();
for(int reps=0; reps < REPS; ++reps) {
assert(stackDefault.empty());
for(int elems=0; elems < ELEMS; ++elems) {
stackDefault.push(elems);
}
for(int elems=0; elems < ELEMS; ++elems) {
stackDefault.pop();
}
}
std::cout << "Default Allocator Time: ";
std::cout << (double)clock() - start / CLOCKS_PER_SEC << "\n\n";
// Use memory pool
StackAlloc<int, MemoryPool<int > > stackPool;
start = clock();
for(int reps=0; reps < REPS; ++reps) {
assert(stackPool.empty());
for(int elems=0; elems < ELEMS; ++elems) {
stackPool.push(elems);
}
for(int elems=0; elems < ELEMS; ++elems) {
stackPool.pop();
}
}
std::cout << "Memory Allocator Time: ";
std::cout << (double)clock() - start / CLOCKS_PER_SEC << "\n\n";
return 0;
}
多次执行链式栈的入栈出栈过程,对时间性能进行比较。分别使用标准库中的allocator和自己实现的内存池进行内存测试。对于new
和delete
,在变量分配内存给空间的同时对变量进行初始化,std::allocator
将变量初始化和内存分配隔离开。
2.2 StackAlloc.h
#ifndef OPTIMIZED_STACKALLOC_H
#define OPTIMIZED_STACKALLOC_H
#include <memory>
template <typename T>
struct StackNode_{
T data;
StackNode_* prev;
};
template <typename T, typename Alloc=std::allocator<T > >
class StackAlloc {
public:
typedef StackNode_<T> node;
typedef typename Alloc::template rebind<node>::other allocator;
StackAlloc();
~StackAlloc();
bool empty();
void clear();
void push(T element);
void pop();
void top();
private:
allocator allocator_;
Node* head_;
};
#endif //OPTIMIZED_STACKALLOC_H
1.结构体为数据值和指向上一个结点的指针。
2.template <typename T, typename Alloc=std::allocator<T > >
定义一个模板类,默认内存分配器是std::allocator
。
3.typedef typename Alloc::template rebind<node>::other allocator;
(1)typedef ... allocator
给内存分配器重新设置一个别名。
(2)typename
关键字是进行类别限定, 对于后面的部分在标准库中可以被认为是静态数据成员、静态成员函数以及嵌套类型。使用该关键字说明后面的是一个类型而不是一个成员变量。
(3)rebind
如果已经为类型T创建了一个内存分配器allocator<T>
,如果需要按照相同的策略对另外一个类型U构建一个分配器。那么可以使用allocator<U> = allocator<T>::rebind<U>::other
,这样如果是我们自己定义的分配器模板allocator,就可以使用该方式,快速地实现多类型的构建过程(同族)。因为在构建T的分配器的的时候,是一个模板方式进行定义的,且rebind依赖于allocator,所以也要声明为模板。
2.3 StackAlloc.cpp
2.4 禁用拷贝赋值
为降低程序代价,禁用拷贝赋值,仅使用移动赋值,因为内存池拷贝的代价巨大。
MemoryPool& operator=(const MemoryPool& mp) = delete;
MemoryPool& operator=(const MemoryPool& mp) noexcept;
C++11中的delect
表示对前面的函数声明进行禁用。
2.5 静态断言与断言
断言方式:assert
是在运行时进行断言,而static_assert
是在编译阶段进行断言。
2.6 使用reinterpret_cast
在对内存池进行删除的时候,通过对delete进行重载,并使用reinterpret_cast
将指针强制转换为void *
类型。