最近在讀侯捷寫的《STL源碼剖析》。 看完STL的內存空間分配器這章。在這章裏,做者開玩笑的說,你甚至能夠寫一個直接從硬盤上取空間的配置器。我想,我確實能夠寫這樣的分配器。而後今天就動手寫了一個。不過這個分配器只是寫着玩玩,不只效率奇低,還有不少BUG。因此你們能夠看着玩玩,可千萬別使用啊。 ios
#ifndef __ALLOCATOR_H__ #define __ALLOCATOR_H__ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <string.h> #include <new> #include <cstddef> #include <cstdlib> #include <climits> #include <iostream> using namespace std; namespace Costaxu { #define BUFFER_FILE_NAME "/tmp/costaxu_buffer" #define BUFFER_SIZE 1024*1024*1024 template <class T> inline T* _hd_allocate(ptrdiff_t size, T*, char** ppBufferCurrent) { size_t totalsize=sizeof(T)*size; std::cerr<<"costaxu debug:: _hd_allocate "<<totalsize<<" bytes"<<std::endl; T* p = (T*)*ppBufferCurrent; *ppBufferCurrent += totalsize; //std::cerr<<"end _hd_allocate"<<std::endl; return p; } template <class T> inline void _hd_deallocate(T* buffer) { std::cerr<<"costaxu debug:: _hd_deallocate"<<std::endl; //do nothing } template <class T1, class T2> inline void _hd_construct(T1* p, const T2& value, char** pBufferCurrent) { std::cerr<<"costaxu debug:: _hd_construct"<<std::endl; //p = (T1*)*pBufferCurrent; //*pBufferCurrent += sizeof(T2); //memcpy(p,&value,sizeof(T2)); cout<<"costaxu debug:: construct value is:"<<value<<endl; new(p) T1(value); cout<<"costaxu debug:: after construct p is: "<<*p<<endl; //std::cerr<<"end _hd_construct"<<std::endl; } template <class T> inline void _hd_destroy(T* ptr) { std::cerr<<"costaxu debug:: _hd_destroy"<<std::endl; ptr->~T(); } template <class T> class allocator{ private: static char* m_pBuffer ; static char* m_pBufferCurrent; int m_fd; public: typedef T value_type; typedef T* pointer; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; typedef size_t size_type; typedef ptrdiff_t difference_type; template<class U> struct rebind{ typedef allocator<U> other; }; void init_allocator() { m_fd = open(BUFFER_FILE_NAME, O_RDWR|O_CREAT|O_TRUNC, 0644); if(m_fd< 0 ) { std::cerr<<"costaxu debug:: open "<<BUFFER_FILE_NAME<<"fail" <<std::endl; exit(1); } lseek(m_fd, BUFFER_SIZE, SEEK_SET); write(m_fd ,"0",1); m_pBuffer = (char*)mmap(0, BUFFER_SIZE, PROT_WRITE, MAP_SHARED, m_fd ,0); m_pBufferCurrent = m_pBuffer; //std::cerr<<"mmap "<<m_pBufferCurrent<<std::endl; } allocator() { if(m_pBuffer ==0) { init_allocator(); } } pointer allocate(size_type n, const void* hint=0) { cerr<<"costaxu debug:: allocate "<<n<<" structs"<<endl; return _hd_allocate((difference_type) n,(pointer)0, &m_pBufferCurrent); } void deallocate(pointer p,size_type n) { _hd_deallocate(p); } void construct(pointer p,const T& value) { _hd_construct(p,value,&m_pBufferCurrent); } void destroy(pointer p) { _hd_destroy(p); } 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)); } } ; template<class T> char* allocator<T>::m_pBuffer =0; template<class T> char* allocator<T>::m_pBufferCurrent =0; };//end of namespace Costaxu #endif
原理是這樣,我在磁盤上建立了一個'/tmp/costaxu_buffer' 這個1G大小的文件。 我在init_allocator中mmap把這個文件映射到進程內存鏡像中,而後拿這個文件看成‘內存池’用。每次須要分配‘內存’的時候我就從這個文件中分配。具體的分配方式我實現的很簡單,Costaxu::allocator裏面的有兩個靜態成員變量: 函數
static char* m_pBuffer ; static char* m_pBufferCurrent;這兩個指針分別指向磁盤上文件'/tmp/costaxu_buffer' 的開始位置和當前已經用到的位置。每次分配一點內存,就只向後移動一下m_pBufferCurrent 這個指針。釋放內存的時候我沒有作任何動做。反正磁盤空間大就隨便用吧。:)
使用STL容器類的時候能夠用Costaxu::allocator這個模板類來分配空間,用法是這樣的: 學習
std::vector<string, Costaxu::allocator<string> > vecString; std::vector<int,Costaxu::allocator<int> > vecInt;
這個allocator模板類主要用到4個函數allocate deallocate construct destroy , 從字面上也能夠看得出就是分配空間、釋放空間、構造對象和釋放對象的意思。 測試
寫了個簡單的程序試了一下,能夠在vector這個容器裏使用這個allocator。 代碼以下: spa
#include <vector> #include "allocator.h" #include <iostream> #include <string> using namespace std; int main() { //std::vector<int, Costaxu::allocator<int> > vecInt; std::vector<string, Costaxu::allocator<string> > vecString; std::vector<int,Costaxu::allocator<int> > vecInt; vecString.push_back(string("1234567890")); cout<<"vecString.push_back"<<endl; vecString.push_back("4567890"); cout<<"vecString.push_back"<<endl; vecString.push_back("abcdefg"); cout<<"vecString.push_back"<<endl; vecInt.push_back(10000); cout<<"vecInt.push_back"<<endl; vecInt.push_back(12345); cout<<"vecInt.push_back"<<endl; int i=0; cout<<vecString[0]<<endl; cout<<vecString[1]<<endl; cout<<vecString[2]<<endl; cout<<vecInt[0]<<endl; cout<<vecInt[1]<<endl; /* for(;i<;i++) { vecInt.push_back(i); } for(i=0;i<10;i++) { std::cout<<i<<std::endl; } */ return 0; }
而後,我測試了一下這個硬盤分配器的效率。 測試的方法是用STL默認內存分配器和我寫的硬盤分配器的分別建立一個vector容器,而後向vector中插入1000萬個整數。 debug
代碼以下: 指針
int main() { std::vector<int,Costaxu::allocator<int> > vecInt; //std::vector<int > vecInt; int i=0; for(;i<10000000;i++) { vecInt.push_back(i); } return 0; }在個人虛擬機(intel core duo 2.26HZ)上,
STL默認的內存分配器的vector插入1000萬整數,所須要的時間是2.3秒, code
而磁盤分配器運行的時間是,16.7秒。 比STL的默認內存分配器差了一個數量級啊。並且這仍是在磁盤空間順序寫的狀況下,若是是隨機讀寫效率和內存差的更大了。不過relax了, 咱們追求的不是效率,嗯,一方面能夠學習一下STL原理,另外一方面真的就是好玩了。畢竟寫代碼這件事情,仍是有趣比較重要。 對象