內存池:
爲了節省申請小塊內存與釋放小塊內存時的開銷,可以使用內存池,開始時根據需要申請一片較大的內存,在申請小塊內存的時候從內存池中獲取,在釋放小塊內存時,將內存放回內存池
內存池中的內存塊:
塊的大小可以由內存池的編寫者確定,一般爲8的倍數:8,16,24,32,.....,128;單位爲字節,相同大小的塊通過指針串成一串(和單鏈表一樣)
內存池的兩個部分:
1.未區塊化的內存:這是一片連續的內存,可以通過兩個指針 begin和end 表示它
2.區塊化的內存:區塊化的內存通過鏈表的形式保存着
在申請小塊內存的時候,是從區塊內存鏈中取,而不是從未區塊化的內存中取
內存池中的區塊化:
將大片的內存轉換成許多內存塊,如:將80個字節連續的內存,轉換成10個大小爲8字節的內存,同時需要將這10個內存塊像鏈表一樣串起來
爲什麼需要塊?
因爲如果沒有塊,在回收內存的時候,回收的內存可能與內存池中未區塊化的內存是不連着的,所有沒辦法通過簡單的兩個指針來表示他們。
實現:
#include <cstddef>
#include <cstdlib>
#include <climits>
#include <iostream>
#define ALIGN 8 //最小的內存塊及內存塊增加的梯度
#define MAX_BYTES 128 //最大的內存塊
template<int inst>
class memory_pool{
private:
union block{
union block* next; //指向下一個區塊
char d[1];
};
static block * volatile free_list[MAX_BYTES/ALIGN]; //數組元素指向區塊化的鏈表
static char *begin; //未區塊化的內存池的頭
static char *end; //未區塊化的內存池的尾
static size_t heap_number; //內存池向堆申請內存的次數
private:
//將n上升到ALIGN的倍數
static size_t ALIGN_BYTES(size_t n){
return ( (n+ALIGN - 1)/ALIGN) * ALIGN;
}
//根據n的大小,獲取free_list的下標
static size_t FREE_LIST_INDEX(size_t n){
return (n - 1)/ALIGN;
}
//從未區塊化的內存中獲取內存,或者向堆申請
static void *getMemory(size_t bytes, size_t& blocks){
size_t bytes_remain = end - begin; //現在還擁有的未區塊化的內存,單位爲字節
size_t bytes_need = bytes * blocks; //需要獲取的內存,單位爲字節
void *result;
//(1).可申請到所有需要的區塊
if(bytes_remain >= bytes_need){
result = begin;
begin += bytes_need;
return result;
}
//無法滿足(1),但是可以申請1個或以上的區塊
if(bytes_remain >= bytes){
blocks = bytes_remain / bytes; //可以獲取的塊數
result = begin;
begin += blocks * bytes; //調整爲區塊化的內存的範圍
return result;
}
if(bytes_remain > 0){
//連1個區塊都無法申請,則將剩餘的未區塊化的內存區塊化
block * volatile * free_list_now = free_list + FREE_LIST_INDEX(bytes_remain);
block* new_block = (block *)begin; //剩餘的內存區塊化
block* old_block = *free_list_now; //鏈表拼接
new_block->next = old_block; //鏈表拼接
*free_list_now = new_block; //鏈表拼接
begin += bytes_remain; //調整爲區塊化的內存的範圍,該語句執行後表示已無未區塊化內存
}
//從堆中申請內存
void * alloc_storage = 0;
size_t alloc_number = bytes_need * 2 + ALIGN_BYTES(heap_number << 1); //申請字節數,隨着申請次數增加而增加
alloc_storage = malloc(alloc_number);
//申請alloc_number個字節的內存成功,調整begin以及end後,返回需要的內存:bytes_need個字節
if(alloc_storage != 0){
begin = (char*)alloc_storage;
end = begin + alloc_number;
result = begin;
begin += bytes_need;
return result;
}
//內存申請失敗,調用oom_malloc,申請bytes個字節的內存
result = first_alloc<1>::oom_malloc(bytes);
if(result == 0){
blocks = 0;
}else{
blocks = 1;
}
return result;
}
//內存池第二級空間配置,請求將未區塊化的內存區塊化
static void *blocking(size_t n){
size_t blocks = 10; //請求10個區塊
char * alloc_storage = (char*)getMemory(n, blocks); //申請空間,blocks傳遞的是引用類型
void * result = alloc_storage;
if(blocks == 1){
return result;
}else if(blocks == 0){
return 0;
}
//申請到大於1個區塊,第一個用於返回給用戶,之後的區塊加入區塊鏈
block* volatile * free_list_now = free_list + FREE_LIST_INDEX(n); //數組的頭部
block* itr_storage = (block*)(alloc_storage + n); //每次以n的偏移遍歷申請到的內存
block* block_line_head = *free_list_now; //指向區塊大小爲n的區塊鏈的頭部
//連接申請到的內存
for(int i = 1; i < blocks; ++i){
itr_storage->next = block_line_head;
block_line_head = itr_storage;
itr_storage = (block*)((char*)itr_storage + n); //這裏注意,需要先將指針轉換爲char*,以讓其偏移的單位爲字節
}
*free_list_now = block_line_head;
return result;
}
public:
/*
//僅用於查看區塊鏈,無其他作用
static void print(char x[100] = "defult", int a = -1){
std::cout << x << " " << a <<std::endl;
for(int i = 0; i < 16; ++i){
if(free_list[i] != 0){
std::cout << (i+1)*ALIGN << " : " << free_list[i] << " ";
block* j= free_list[i];
int k = 0;
while(j){
j = j->next;
k++;
}
std::cout << k << std::endl;
}
}
}
*/
//內存池第一級空間配置,從區塊中獲取內存
static void *allocate(size_t n){
//當大於最大內存塊時,調用 malloc獲取內存
if(n > MAX_BYTES)
return malloc(n);
block* volatile * free_list_now = free_list + FREE_LIST_INDEX(n); //獲取相應的鏈表
block* p = *free_list_now;
void* result = 0;
if(p != 0){ //鏈表中有內存塊
*free_list_now = p->next;
result = p;
}else{ //鏈表中無內存塊,申請內存塊
result = blocking(ALIGN_BYTES(n));
}
//---------查看各個區塊鏈,可以刪除
//print((char*)"申請內存/字節:",n);
return result;
}
static void deallocate(void *p, size_t n){
/*
//---------------檢查釋放的內存,看看釋放了什麼,可以刪除
for(int i = 0; i < ALIGN_BYTES(n)/4; ++i){
std::cout << (int*)((int*)p)[i] << ",";
}
std::cout << "\n";
*/
//當釋放的內存大於最大內存塊時,通過free回收
if(n > MAX_BYTES)
free(p);
else{
block* volatile * free_list_now = free_list + FREE_LIST_INDEX(n);
block* link_head = *free_list_now;
block* temp = (block*)p;
temp->next = link_head;
*free_list_now = temp;
//print((char*)"釋放內存/字節:", n);
}
/*
//---------------在將內存拼接到鏈表後再檢查一次
for(int i = 0; i < ALIGN_BYTES(n)/4; ++i){
std::cout << (int*)((int*)p)[i] << ",";
}
std::cout << "\n";
*/
}
};
template<int inst> char* memory_pool<inst>::begin = 0;
template<int inst> char* memory_pool<inst>::end = 0;
template<int inst> size_t memory_pool<inst>::heap_number = 1;
template<int inst> typename memory_pool<inst>::block* volatile memory_pool<inst>::free_list[MAX_BYTES/ALIGN] = {0};
此處,不知道可不可以將template<int inst>中的inst變爲ALIGN,從而更簡單的創建不同的memory_pool,這取決於inst是否有什麼實際作用
成員變量:
free_list | 一個數組,數組的元素爲指針,指向每個區塊鏈的頭 |
begin | 一個指針,指向未區塊化內存的頭部,值僅在getMemory中改變 |
end | 一個指針,指向未區塊化內存的尾部+1,值盡再getMemory中改變;採用半開半閉表示法[begin,end) |
heap_number | 一個整數,隨着調用malloc(僅再未區塊化內存不足的時候調用)的次數增加而增加 |
私有成員方法:
方法名 | 參數 | 返回類型 | 功能 |
ALIGN_BYTES | size_t n 表示要對齊的字節 | size_t 表示對齊後的字節 | 將字節對齊,以符合內存塊的大小 |
FREE_LIST_INDEX | size_t n 表示字節 | size_t 表示free_list的下標 | 根據n獲取free_list的下標 |
getMemory |
size_t bytes 內存塊的大小 size_t& blocks 申請的塊的數量 |
void * 指向獲得的內存的首字節 | 申請一片連續的,大小爲bytes*blocks的內存,返回指向內存的指針,注意blocks爲引用,因此實際申請到的內存爲該函數調用結束後的blocks*bytes |
blocking | size_t n 需要獲取的內存塊的大小 | void * 指向獲取的內存塊 | 當該函數被調用時,表示區塊鏈中已無內存塊,需要調用getMemory申請內存,同時將得到的內存區塊化 |
公有成員方法:
方法名 | 參數 | 返回類型 | 功能 |
allocate | size_t n 需要申請的字節數 | void * 指向獲得的內存 | 從區塊鏈中,獲取1個區塊,區塊的大小未ALIGN_BYTES(n) |
deallocate |
void *p 指向需要釋放的內存 size_t n 需要釋放的內存的大小 |
void | 釋放內存,將需要釋放的內存拼接回區塊鏈 |
測試:
內存配置器可參考https://blog.csdn.net/A_littleK/article/details/102639016
//allocator.h
//內存配置器,可參考https://blog.csdn.net/A_littleK/article/details/102639016
template<class T, class Alloc = memory_pool<1> >
class myalloc{
public:
//以下的 typedef 是必須的,因爲在容器中有類似 _alloc::pointer 的語法
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef const T& const_reference;
typedef T& reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
template<class U>
struct rebind{
typedef myalloc<U> other;
};
pointer allocate(size_type size, const void* hint = 0){
return (pointer)Alloc::allocate(size * sizeof(T) );
};
pointer allocate(){
return (pointer)Alloc::allocate( sizeof(T) );
};
void deallocate(pointer p, size_type n){
Alloc::deallocate(p, n * sizeof(T));
}
void deallocate(pointer p){
Alloc::deallocate(p, sizeof(T));
}
void construct(pointer p, const_reference x){
new(p) T(x);
}
void destory(pointer p){
p->~T();
}
pointer address(reference x){
return (pointer)&x;
}
const_pointer const_address(const_reference x){
return (const_pointer)&x;
}
size_type max_size() const {
return size_type(UINT_MAX/sizeof(T));
}
};
main:
#include "allocator.h"
#include <memory>
#include <cstdlib>
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
int main(){
int a[5] = {0,1,2,3,4};
vector<int, myalloc<int> > v(a,a+5);
v.push_back(5);
v.clear();
v.push_back(9);
return 0;
}
運行結果1(ALIGN爲8):
運行結果解析:
vector<int, myalloc<int> > v(a,a+5); //申請20個字節的內存,32位程序int佔4字節,因此有:
申請20字節,但是提升到8的倍數,所有申請到24個字節,但是對於vector,它會認爲是20字節;9代表當前24字節區塊鏈中還有9個24字節的塊:
v.push_back(5); //當前vector中含有5個int,佔用24字節,而原本有20字節,因此申請原來兩倍的空間20*2 = 40字節:
執行完v.push_back(5); 之後,申請了更大的空間,原來的空間被回收,可以看到回收的內容爲(0,1,2,3,4,沒有用到的內存)
當加入24字節區塊鏈後,block*next指針指向了原來的頭部0x1e0fe0(看上圖),24字節區塊鏈的長度也因此變成了10:
執行v.clear(); 後,vector的內容被清除或者說vector的end指針的指向和begin相同,但是vector掌管的動態內存並沒有被回收。
執行v.push_back(9);後,相當於v[0] = 9;
程序運行結束後,釋放了vector所掌管的動態內存: