Memory Pool

43
Memory Pool ACM 2012 5120379038 Yanqing Peng

description

Memory Pool. ACM 2012 5120379038 Yanqing Peng. Dynamic memory allocation in C. #define N 10000 int main () { int * p = malloc ( sizeof ( int )); //allocate a pointer to an integer int * pa = malloc ( sizeof ( int ) * N ); //allocate an array with N integers /*statement*/ - PowerPoint PPT Presentation

Transcript of Memory Pool

Page 1: Memory Pool

Memory PoolACM 20125120379038Yanqing Peng

Page 2: Memory Pool

Dynamic memory allocation in C

#define N 10000int main(){

int* p = malloc(sizeof(int)); //allocate a pointer to an integer

int* pa = malloc(sizeof(int) * N); //allocate an array with N integers

/*statement*/free(p); free(pa);return 0;

}

Page 3: Memory Pool

const int N = 10000;int main(){

int* p = new int; //allocate a pointer to an integer

int* pa = new int[N]; //allocate an array with N integers

/*statement*/delete p; delete [] pa;

return 0;}

Dynamic memory allocation in C++

Page 4: Memory Pool

Some Problems…

Memory Leak Mismatching Operators Fragmentation Long Execution Time ……

Page 5: Memory Pool

Memory Leakvoid foo() throw(std::runtime_error){

int* p = new int; /*statement*/if ( condition ) throw std::runtime_error(“This is a Memory Leak”);/*statement*/delete p; //May cause a memory leak

}

Page 6: Memory Pool

Some Problems…

Memory Leak Mismatching Operators Fragmentation Long execution time ……

Page 7: Memory Pool

Mismatching Operators

const int N = 10000;int main(){

int* p = static_cast<int*>(malloc(sizeof(int)); //allocate by malloc

int* pa = new int[N]; //allocate by new[]

/*statement*/delete p; //de-

allocate by delete delete pa; //de-

allocate by delete return 0;}

Page 8: Memory Pool

Some Problems…

Memory Leak Mismatching Operators Fragmentation Long execution time ……

Page 9: Memory Pool

Fragmentation Memory = 8 bytes

Allocate the whole memory

De-allocate odd bytes

Allocate a 4-byte block : std::bad_alloc

0 1 2 3 4 5 6 7

0 1 2 3 4 5 6 7

0 1 2 3 4 5 6 7

Page 10: Memory Pool

Some Problems…

Memory Leak Mismatching Operators Fragmentation Long execution time  ……

Page 11: Memory Pool

Long execution timeint main(){

for (int n = ~0U >> 1; n; --n) //delete (new int());

return 0; }

Time = 4235 ms

Page 12: Memory Pool

Long execution timeint main(){

for (int n = ~0U >> 1; n; --n) delete (new int());

return 0; }

Time = 1038020 ms

Page 13: Memory Pool

Placement New and Memory Pool 使用 Placement New 可以在指定的内存上构造对象。指定的内存需要事先申请,并且保证没有其它变量正在使用这块内存。对于非 POD 对象,需要显式调用其析构函数。#include <new>struct obj {};char* buffer = new char[1<<8];void foo(){

obj* p = new (buffer) obj; //placement newp -> ~obj(); //invoke the destructor explicitlydelete p;

}

如果预先申请好一大块 buffer 进行之后的分配……

Page 14: Memory Pool

Placement New and Memory Pool 使用内存池来管理这个 buffer !#include <new>struct obj {};class MemoryPool;void foo(){

obj* p = new (MemoryPool::allocate<obj>()) obj; p -> ~obj();MemoryPool::deallocate<obj>(p);

}

Page 15: Memory Pool

Fixed-size Memory Pool 预先申请好一块内存区域,并把它分成定长的块。 每次需要内存申请时,将一块尚未分配的块返回。每次释放时,将传入的内存块重新标记为未分配。 定长内存池在工程中应用非常广泛。代码量少、效率高,非常适合用作大量同类型小数据的内存管理器。

Page 16: Memory Pool

template <typename Type>class MemoryPool{ char *memStart;

void *head;Type *left, *right, *memEnd;

public: MemoryPool(size_t); ~MemoryPool(); inline Type* allocate(); inline bool deallocate(Type*);};

Prototype

Page 17: Memory Pool

Constructortemplate <typename Type>MemoryPool<Type>::MemoryPool(size_t _capacity) : memStart(new char[std::max(sizeof(Type), sizeof(void*)) * _capacity]), head(0), left(reinterpret_cast<Type*>(memStart)), right(left), memEnd(left + _capacity) {}

未初始化的内存内存的开始地址 memStart=1

内存的结束地址的后一位 memEnd=7

初始化内存的开始地址 left=1未初始化内存的开始地址 right=1[left, right) 为已初始化的内存地址区间

第一个初始化内存head = NULL

Page 18: Memory Pool

未初始化的内存memStart=1 memEnd=7

left=1right=1

Allocation 1• 若没有空余的已初始化内存,则从未初始化的内存头部取出一块作为返回值 if (!head)

return right++;

head = NULL

Page 19: Memory Pool

Allocation 1

未初始化的内存memStart=1 memEnd=7

left=1

分配的内存right=2

已初始化内存

• 若没有空余的已初始化内存,则从未初始化的内存头部取出一块作为返回值 if (!head)

return right++;

head = NULL

Page 20: Memory Pool

未初始化的内存memStart=1 memEnd=7

left=1

分配的内存right=3

已初始化内存分配的内存

Allocation 1• 若没有空余的已初始化内存,则从未初始化的内存头部取出一块作为返回值 if (!head)

return right++;

head = NULL

Page 21: Memory Pool

Allocation 1• 若没有空余的已初始化内存,则从未初始化的内存头部取出一块作为返回值 if (!head)

return right++;

head = NULL

未初始化的内存memStart=1 memEnd=7

left=1

分配的内存right=4

已初始化内存分配的内存 分配的内存

Page 22: Memory Pool

未初始化的内存memStart=1 head = NULL memEnd=7

left=1

分配的内存right=4

已初始化内存分配的内存 分配的内存

De-allocation 将返回的内存块( p )插入已初始化内存链表头部,内存块中存放下一个已初始化的内存void** newHead = reinterpret_cast<void**>(p);*newHead = head;head = newHead;

Page 23: Memory Pool

未初始化的内存memStart=1 head=1

memEnd=7

left=1

NULL

right=4已初始化内存分配的内存 分配的内存

De-allocation 将返回的内存块( p )插入已初始化内存链表头部,内存块中存放下一个已初始化的内存void** newHead = reinterpret_cast<void**>(p);*newHead = head;head = newHead;

Page 24: Memory Pool

De-allocation 将返回的内存块( p )插入已初始化内存链表头部,内存块中存放下一个已初始化的内存void** newHead = reinterpret_cast<void**>(p);*newHead = head;head = newHead;

未初始化的内存memStart=1 memEnd=7

left=1

NULL

right=4已初始化内存分配的内存 1

head=3

Page 25: Memory Pool

De-allocation 将返回的内存块( p )插入已初始化内存链表头部,内存块中存放下一个已初始化的内存void** newHead = reinterpret_cast<void**>(p);*newHead = head;head = newHead;

未初始化的内存memStart=1 memEnd=7

left=1

NULL

right=4已初始化内存

3 1

head=2

Page 26: Memory Pool

Allocation2 若已初始化内存块链表非空,则删除表首并返回该地址Type* ret = reinterpret_cast<Type*>(head);head = *(reinterpret_cast<void**>(head));return ret;

未初始化的内存memStart=1 memEnd=7

left=1

NULL

right=4已初始化内存

3 1

head=2

Page 27: Memory Pool

Allocation2 若已初始化内存块链表非空,则删除表首并返回该地址Type* ret = reinterpret_cast<Type*>(head);head = *(reinterpret_cast<void**>(head));return ret;

未初始化的内存memStart=1 memEnd=7

left=1

NULL

right=4已初始化内存分配的内存 1

head=3

Page 28: Memory Pool

Allocation2 若已初始化内存块链表非空,则删除表首并返回该地址Type* ret = reinterpret_cast<Type*>(head);head = *(reinterpret_cast<void**>(head));return ret;

未初始化的内存memStart=1 head=1

memEnd=7

left=1

NULL

right=4已初始化内存分配的内存 分配的内存

Page 29: Memory Pool

Allocation2 若已初始化内存块链表非空,则删除表首并返回该地址Type* ret = reinterpret_cast<Type*>(head);head = *(reinterpret_cast<void**>(head));return ret;

未初始化的内存memStart=1 head = NULL memEnd=7

left=1

分配的内存right=4

已初始化内存分配的内存 分配的内存

Page 30: Memory Pool

Destructor

这个就不画图了……

delete [] memStart;

Page 31: Memory Pool

Pseudo-code Initialization1. Store the start address, number of blocks and the number of uninitialized unused

block Allocation1. Check if there any free blocks2. If necessary - initialize and append unused memory block to the list3. Go to the head of the unused block list4. Extract the block number from the head of the unused block in the list and set it as

the new head5. Return the address for the old block head De-allocation1. Check the memory address is valid2. Calculate the memory addresses index id3. Set its contents to the index id of the current head of unused blocks and set itself as

the head

Page 32: Memory Pool

Benchmark#include “memorypool.h”MemoryPool<int> pool(10);int main(){

for (int n = ~0U >> 1; n; --n)pool.deallocate(pool.allocate());

return 0; }

Time = 17904 ms

Page 33: Memory Pool

Resizing

使用类似 vector 的扩张方法 每次内存不够时,重新申请一块长度为已有内存两倍的内存块 使用一个类去维护多个相同长度的定长内存池

Page 34: Memory Pool

Thread-safety 线程安全是编程中的术语,指某个函数 ( 计算机科学 ) 、函数库在多线程环境中被调用时,能够正确地处理各个线程的局部变量,使程序功能正确完成。 一般来说,线程安全的函数应该为每个调用它的线程分配专门的空间,来储存需要单独保存的状态(如果需要的话),不依赖于“线程惯性”,把多个线程共享的变量正确对待(如,通知编译器该变量为“易失( volatile )”型,阻止其进行一些不恰当的优化),而且,线程安全的函数一般不应该修改全局对象。 使用互斥锁保证线程安全。

Page 35: Memory Pool

Thread-safety (Using pthread)#include <pthread.h>template <typename Type>class mt_MemoryPool{

MemoryPool<Type> pool;pthread_mutex_t lock;

public:mt_MemoryPool(size_t capacity) : pool(capacity){pthread_mutex_init(&lock, 0);}inline Type* allocate();inline bool deallocate(Type*);

};

Page 36: Memory Pool

Thread-safety (Using pthread)

template <typename Type>inline Type* mt_MemoryPool::allocate();{

pthread_mutex_lock(&lock);Type* ret = pool.allocate();pthread_mutex_unlock(&lock);return ret;

}

Page 37: Memory Pool

Thread-safety (Using pthread)

template <typename Type>inline bool mt_MemoryPool::deallocate(Type* p);{

pthread_mutex_lock(&lock);bool ret = pool.deallocate(p);pthread_mutex_unlock(&lock);return ret;

}

Page 38: Memory Pool

Smart Pointer

用类似智能指针的思想实现内存池分配空间的自动归还 引用计数 在智能指针的析构中自动析构对象并归还

Page 39: Memory Pool

Smart Pointervoid foo() throw(std::runtime_error){

MemoryPool<int>::pointer p = pool.allocate(); /*statement*/if ( condition ) throw std::runtime_error(“No memory leak.”);/*statement*/

} // p will be executed when leaving this scope

Page 40: Memory Pool

Variable-size Memory Pool 不同于定长内存池,不定长内存池可以在构造后申请各种不同大小的内存块。

p = pool.allocate<int>(); 实现方法:内部维护多个定长内存池,每次找到最小的能满足条件的内存池进行内存分配。如果申请的内存大于所有的内存池大小,则直接调用 malloc() 如:分别维护 1,2,4,8,16,32,64,128,256,512,1024bytes的定长内存池,对于每一个内存申请向上对齐到 2 的整次幂后分配对应大小的内存块,此时空间最多浪费一倍 效率低于定长内存池

Page 41: Memory Pool

Singleton / Static Class

强制使类只生成一个实例。 使用单例模式:将构造函数声明为 private ,通过 public 函数 GetInstance 函数中的 static class reference获取唯一的实例

MemoryPool& pool = MemoryPool::GetInstance();

静态类:将类的所有函数声明为 static ,全局静态实例保证唯一性。p = MemoryPool::allocate<int>();MemoryPool::deallocate<int>(p);

Page 42: Memory Pool

Allocator in SGI-STL 默认使用两级配置器 第一级配置器直接调用 C 的内存分配函数 malloc()及 realloc() ,如果调用失败则执行 oom_malloc() 第二级配置器的做法是,如果区块够大,超过 128bytes 时,就移交第一级配置器处理。当区块小于 128bytes 时,则以不定长内存池管理。 不定长内存池中维护 16 个定长内存池,大小分别为

8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128bytes ,将所需的内存向上对齐至 8 的倍数后交由对应的定长内存池分配 所有 stl容器默认使用上述 stl::alloc 进行内存管理

Page 43: Memory Pool

Reference

Stanley B.Lippman, Josee Lajoie, Barbara E. Moo C++ Primer[M]Addison-Wesley Educational Publishers Inc, 2012.08

候捷 STL源码剖析 [M] 华中科技大学出版社 , 2002.06 SGI SGI STL Allocator Design [G/OL] from:

http://www.sgi.com/tech/stl/alloc.html Wikipedia Memory Pool [G/OL], 2013.04 from:

http://en.wikipedia.org/wiki/Memory_pool