頻繁的分配和回收內(nèi)存會嚴重降低程序的性能。原因在于默認的內(nèi)存管理器是通用的、應用程序可能會以某種特定的方式使用內(nèi)存,并且為不需要的功能付出性能上的代價。通過開發(fā)專用的內(nèi)存管理器可以解決這個問題。
兩個方面考慮內(nèi)存管理器的設計:大小和并發(fā)
固定大小、可變大小
單線程:內(nèi)存管理器局限在一個線程內(nèi),內(nèi)存被一個線程使用
多線程:內(nèi)存管理器被多個線程并發(fā)的使用,需包含互斥執(zhí)行的代碼段。無論什么時候,只有一個線程在執(zhí)行一個代碼段
- 全局內(nèi)存管理器(由new()和delete()執(zhí)行)是通用的。因此代價較高
- 專用內(nèi)存管理器,如單線程內(nèi)存管理器,其可以省去new和delete的并發(fā)問題
(指的是對于一個類重載實現(xiàn)他的new和delete方面。分配的大小根據(jù)sizeof,然后用new char[]來分配,delete[] 來刪除)
示例:
- 全局的new和delete
class Rational{
public:
Rational (int a = 0, int b = 1) : n(a), d(b) {}
private:
int n; //分子
int d; //分母
};
Rational *array = new Rational(i);
delete array; // 循環(huán)執(zhí)行100萬次,1500ms
版本1:專用的Rational內(nèi)存管理器
避免頻繁使用默認內(nèi)存管理器,Rational需要維護一個預先分配的Rational對象的靜態(tài)鏈接鏈表,列出空閑的可用對象,當需要Rational對象時,從空閑列表取出一個即可,使用后再把它放回空閑列表以便今后分配。
class NextOnFreeList{
public:
NextOnFreeList *next;
};
空閑列表被聲明為一個有NextOnFreeList元素組成的列表,這樣空閑列表的每個元素都聲明為NextOnFreeList結(jié)構(gòu),但他們也是Rational對象。
為了遍歷對象,每個Rational對象的前幾個字節(jié)用于指向空閑列表的下一個對象(next)??梢酝ㄟ^將Rational對象轉(zhuǎn)換成指向NextOnFreeList類型的指針來實現(xiàn)。這樣空閑列表雙重身份:Rational對象的序列andNextOnFreeList元素的序列
class Rational{
...
static NextOnFreeList *freelist;
};
空閑列表聲明為Rational靜態(tài)成員,Rational的New和delete操作符可以管理該靜態(tài)列表,重載全局的new和delete。
class Rational{
public:
inline void *operator new (size_t size);
inline void operator delete (void *doomed, size_t size);
static void newMemPool() { expandTheFreeList(); }
static void deleteMemPool();
private:
static NextOnFreeList *freelist;
static void expandTheFreeList();
enum { EXPANSION_SIZE = 32};
int n;
int d;
};
new()是在空閑列表中分配一個Rational對象,列表為空就擴展列表。先去掉空閑列表的頭部,調(diào)整完指針后再返回head。
inline void* Rational::operator new(szie_t size) {
if(0 == freelist) {
expandTheFreeList();
}
NextOnFreeList *head = freelist;
freelist = head->next;
return head;
}
delete()把Rational對象直接添加到空閑列表的頭部,以返回一個Rational對象。這樣每次delete都會使得freelist變?yōu)閐elete調(diào)的空間,而其next則指向之前的freelist,因此可以繼續(xù)形成一條鏈
inline void Rational::operator delete(void *doomed, size_t size) {
NextOnFreeList *head = static_cast<NextOnFreeList *> doomed;
head->next = freelist;
freelist = head;
}
Rational和NextOnFreeList之間的類型轉(zhuǎn)換有危險。必須確??臻e列表的元素足夠大以支持任意一種類型。因此,當我們用Rational對象填充空閑列表時,需要比較兩個類型之間的大小,從而分配較大的一個。
void Rational::expandTheFreeList() {
size_t size = (sizeof(Rational) > sizeof(NextOnFreeList *)) ?
sizeof(Rational) : sizeof(NextOnFreeList *);
NextOnFreeList *runner = static_cast<NextOnFreeList *> new char[size];
freelist = runner;
for(int i = 0; i < EXPANSION_SIZE; i++) {
runner->next = static_cast<NextOnFreeList *> new char[size];
runner = runner->next;
}
runner->next = 0;
}
void Rational::deleteMemPool() {
NextOnFreeList *nextPtr;
for(nextPtr = freelist; nextPtr != NULL; nextPtr = freelist) {
freelist = freelist->next;
delete []nextPtr;
}
}
重復性能測試
for(500){
for(1000){
array[1-1000] = new Rational(i);
}
for(1000){
delete array[1-1000];
}
執(zhí)行時間從1500描述將至43ms。
版本2:固定大小對象的內(nèi)存池
版本1只可以管理Rational對象,想管理多個對象的話,用模板。因為Rational可以看出,內(nèi)存管理邏輯是獨立于Rational之外的。
template <class T>
class MemoryPool {
public:
MemoryPool (size_t size = EXPANSION_SIZE) ;
~MemoryPool ();
inline void* alloc (size_t size);
inline void free (void *someElement);
private:
MemoryPool<T> *next;
void expandTheFreeList (int howMany = EXPANSION_SIZE);
};
template <class T>
MemoryPool<T>:: MemoryPool (size_t size = EXPANSION_SIZE) {
expandTheFreeList(size);
}
template <class T>
MemoryPool<T>:: ~MemoryPool () {
MemoryPool<T> *nextPtr = next;
for(nextPtr =next; nextPtr != NULL; nextPtr = next) {
next = next->next;
delete []nextPtr;
}
}
template <class T>
inline void* MemoryPool<T>:: alloc (size_t size) {
if(!next) expandTheFreeList();
MemoryPool<T> *head = next;
next = head->next;
return head;
}
template <class T>
inline void MemoryPool<T>:: free (void *doomed) {
MemoryPool<T> *head = static_cast<MemryPool<T> *> doomed;
head->next = next;
next = head;
}
template <class T>
void MemoryPool<T>:: expandTheFreeList (int howMany = EXPANSION_SIZE) {
size_t size = (sizeof(T) > sizeof(MemoryPool<T> *)) ?
sizeof(T) > sizeof(MemoryPool<T> *);
MemoryPool<T> *runner = static_cast< MemoryPool<T> *> new char[size];
next = runner;
for(int i = 0; i < howmany; i++) {
runner->next = static_cast< MemoryPool<T> *> new char[size];
runner = runner->next;
}
runner->next = 0;
}
上述代碼解析:與版本1類似,alloc函數(shù)負責擴充,free函數(shù)負責把T元素放回空閑列表,以此來釋放它。
Rational就不需要再維護自己的空閑列表,因此Rational變?yōu)?/p>
class Rational{
public:
Rational (int a = 0, int b = 1) : n(a), d(b) {}
void* operator new(size_t size) { return memPool->alloc(szie); }
void operator delete (void *doomed, size_t size) { memPool->free(doomed); }
static void newMemPool() { memPool = new MemoryPool<Rational>(); }
static void deleteMemPool() { delete memPool; }
private:
static MemryPool<Rational> *memPool;
int n;
int d;
};
MemoryPool<Rational> *Rational::memPool = 0;
int main(){
Rational *array[1000];
Rational::newMemPool();
for(01-500)
for(1-1000)
array[i] = new Rational(i);
for(1-1000)
delete array[i];
Rational::deleteMemPool();
}
這里的耗時大約是63ms
注意:之前的static_cast的類型轉(zhuǎn)換在編譯時可能報錯,可以采用強制類型轉(zhuǎn)換
版本3:單線程可變大小的內(nèi)存管理器
版本2分配的是固定大小的內(nèi)存,但有些程序需要分配可變大小的內(nèi)存。
下面是Web服務器中常用的方式:
MemoryChunk類取代了之前版本中使用的NextOnFreeList類,他把不同大小的內(nèi)存塊連接起來形成塊序列。
完整代碼如下(#####可運行)
#include<iostream>
using namespace std;
#include <string>
class MemoryChunk{
public:
MemoryChunk(MemoryChunk *nextChunk, size_t chunkSize);
~MemoryChunk();
inline void *alloc(size_t size);
inline void free(void* someElement);
MemoryChunk *nextMemChunk(){ return next; }
size_t spaceAvailable(){ return chunkSize - bytesAlreadyAllocated; }
enum {DEFAULT_CHUNK_SIZE = 4096};
private:
MemoryChunk *next;
void *mem;
size_t chunkSize;
size_t bytesAlreadyAllocated;
};
// MemoryChunk是NextOnFreeList的一個更為簡潔的版本。將next指針從用于已分配對象的實際內(nèi)存中分離出來。MemoryChunk使用顯式的next和mem指針因此不需要轉(zhuǎn)換。
//MemoryChunk首先確定內(nèi)存塊大小(mem指向),然后next鏈接下一個MemoryChunk內(nèi)存塊
MemoryChunk::MemoryChunk(MemoryChunk *nextChunk, size_t reqSize){
chunkSize = (reqSize > DEFAULT_CHUNK_SIZE) ? reqSize : DEFAULT_CHUNK_SIZE;
next = nextChunk;
bytesAlreadyAllocated = 0;
mem = new char[chunkSize];
}
MemoryChunk::~MemoryChunk() {
delete[]mem;
}
void* MemoryChunk::alloc(size_t requestSize){
void *addr = (void*)((size_t)mem + bytesAlreadyAllocated);
bytesAlreadyAllocated += requestSize;
return addr;
}
inline void MemoryChunk::free(void *doomed){}//無需擔心空閑內(nèi)存段的釋放,對象被刪除,內(nèi)存塊也就被釋放了
//MemoryChunk只是輔助類,ByteMemoryPool類用它首先可變大小的內(nèi)存管理。
class ByteMemoryPool{
public:
ByteMemoryPool(size_t initSize = MemoryChunk::DEFAULT_CHUNK_SIZE);
~ByteMemoryPool();
inline void* alloc(size_t size);
inline void free(void* someElement);
private:
MemoryChunk *listOfMemChunks;
void expandStorage(size_t reqSize);
};
//內(nèi)存塊列表可能包含多個塊,但只有第一塊可用于分配的內(nèi)存,其他塊表示已分配的內(nèi)存。即列表的首個元素是唯一能夠分配可用內(nèi)存的塊。
ByteMemoryPool::ByteMemoryPool(size_t initSize){
expandStorage(initSize);
}
ByteMemoryPool::~ByteMemoryPool(){
MemoryChunk *memChunk = listOfMemChunks;
while (memChunk){
listOfMemChunks = memChunk->nextMemChunk();
delete memChunk;
memChunk = listOfMemChunks;
}
}
//alloc如果確保有足夠的可用空間,則會把分配任務托付給列表頭的MemoryChunk:
void* ByteMemoryPool::alloc(size_t requestSize){
size_t space = listOfMemChunks->spaceAvailable();
if (space < requestSize){
expandStorage(requestSize); //表示如果當前塊的大小不夠的話就需要另外擴充新的request大小的MemoryChunk,并把它放在列表頭部返回
}
return listOfMemChunks->alloc(requestSize);
}
inline void ByteMemoryPool::free(void *doomed){ listOfMemChunks->free(doomed); }
void ByteMemoryPool::expandStorage(size_t reqSize){
listOfMemChunks = new MemoryChunk(listOfMemChunks, reqSize);
}
//這里需要修改Rational類的實現(xiàn),用ByteMemoryPool代替MemoryPool<Rational>對象。
class Rational{
public:
Rational(int a = 0, int b = 1) : n(a), d(b){}
void *operator new(size_t size){ return memPool->alloc(size); }
void operator delete(void *doomed){ memPool->free(doomed); }
static void newMemPool(){
memPool = new ByteMemoryPool();
}
static void deleteMemPool(){ delete memPool; }
private:
static ByteMemoryPool *memPool;
int n, d;
};
ByteMemoryPool* Rational::memPool = nullptr;
int main(){
Rational *array[1000];
Rational::newMemPool();
for (int j = 0; j < 500; j++){
for (int i = 0; i < 1000; i++){
array[i] = new Rational(i);
}
for (int i = 0; i < 1000; i++){
delete array[i];
}
}
Rational::deleteMemPool();
}