一个从硬盘上取空间的STL内存空间分配器

最近在读侯捷写的《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原理,另外一方面真的就是好玩了。毕竟写代码这件事情,仍是有趣比较重要。 对象

相关文章
相关标签/搜索