再探小型对象分配技巧

再探小型对象分配技巧(Small-Object Allocation)

/*

 * References, 参考文献:

 * [1]. Alexandrescu, Andrei. "Modern C++ Design: Generic Programming and Design 

 * Patterns Applied". Copyright (c) 2001. Addison-Wesley.

 *

 * 本文研究的源代码取自loki库SmallObj.h & SmallObj.cpp 

 */

 

#include <boost/progress.hpp>
#include <vector>

using namespace boost;
using namespace std;

struct SmallObj {
    char c;
};

int main()
{
    progress_timer t;
    
    SmallObj *s = new SmallObj();   //case 1.
    vector<char> vc;  //case 2.
    vector<string>vs; //case 3.
    vector<int> *vi = new vector<int>(1); //case 4.
    
    std::cout << t.elapsed() << std::endl;
}

利用boost的progress_timer库编了一个小测试,看看c++默认的new操作大概需要多长时间.

机器配置:2.26 GHz Intel Core 2 Duo, 4GB 1067 MHz DDR3, Mac OS 10.7.3.


1. 单独执行case 1时,耗时平均3微妙,小对象内存的动态内存分配时间;

2. 同时执行2,3时,耗时最低3微妙,最多4微妙,通过std::alloc分配小对象内存;这种情况下,通常case 3是从case 2 已经分配的segregated freelist中得到分配的内存快,耗时应该很低。

3. 只执行case 4时,最低耗时4微妙,最多6微妙。


按照[1]的说法,系统缺省的free store分配器速度很慢,并且跟loki编写小型对象分配器比较,执行速度慢一个等量级。下面就看看loki的Small-Object Allocator是怎么实现的,有哪些优化技巧在实现中。


loki的小型对象分配器一共分为四层,处于最底层的是chunk struct。每一个chunk对象包含并管理一大块内存,这一大块内存本身包含整数个固定大小的区块(block)。chunk内包含逻辑信息,使用者可根据这些逻辑信息分配和归还区块,当chunk中不再剩余blocks时,分配失败并传回零。


第二层是FixedAllocator,其对象以chunk为构件。FixedAllocator主要用来满足那些“累计总量超过chunk容量”的内存分配请求。FixedAllocator会通过一个vector将chunks组合起来以达到目的。如果新的内存请求出现,但vector中的chunk都被占用了,此时FixedAllocator会产生一块新的chunk,并将它添加到vector中,再由chunk满足需求。


第三层SmallObjAllocator提供的通用性的分配归还函数,此对象拥有数个FixedAllocator对象,每一个负责分配某特定大小的对象。根据申请的bytes个数不同,SmallObjAllocator对象会将内存分配申请分发给辖内某个FixedAllocator。如果请求数量过大,会转发给系统提供的::operator new。


最后一层是SmallObject,它包装了FixedAllocator,以便向C++ Classes提供封装良好的分配服务。SmallObject重载了operator new 和 operator delete,将任务转给SmallObjAllocator对象去完成。

 

Chunk

Chunk的定义如下

 struct Chunk
        {
            void Init(std::size_t blockSize, unsigned char blocks);
            void* Allocate(std::size_t blockSize);
            void Deallocate(void* p, std::size_t blockSize);
            void Reset(std::size_t blockSize, unsigned char blocks);
            void Release();
            unsigned char* pData_;
            unsigned char
                firstAvailableBlock_,
                blocksAvailable_;
        };

 pData指向被管理内存本身,除此以外chunk还保存一下整数值:

  • firstAvailableBlock_, chunk内的第一个可用区块的索引号
  • blocksAvailable_, chunk内的可用区块总数

因为firstAvailableBlock_, blocksAvailable_的类型都是unsigned char,因此一个chunk在一部8-bit char机器上无法拥有255个以上的区块。如果block未被使用,就拿第一个字节放置“下一个未被使用的区块”的索引号。由于firstAvailableBlock_已经持有第一个block的索引号,因此我们便有了一个由“可用区块”组成的freelist,无须占用额外内存。

void FixedAllocator::Chunk::Init(std::size_t blockSize, unsigned char blocks)
{
    assert(blockSize > 0);
    assert(blocks > 0);
    // Overflow check
    assert((blockSize * blocks) / blockSize == blocks);
    
    pData_ = new unsigned char[blockSize * blocks];
    Reset(blockSize, blocks);
}

void FixedAllocator::Chunk::Reset(std::size_t blockSize, unsigned char blocks)
{
    assert(blockSize > 0);
    assert(blocks > 0);
    // Overflow check
    assert((blockSize * blocks) / blockSize == blocks);

    firstAvailableBlock_ = 0;
    blocksAvailable_ = blocks;

    unsigned char i = 0;
    unsigned char* p = pData_;
    for (; i != blocks; p += blockSize)
    {
        *p = ++i;
    }
}

 其中里面有两个技巧特别需要思考一下:

1. 将可用block的第一个字节记录索引号,加firstAvailableBlock_,形成freelist的好处是什么?

2. 为什么要将chunk管理的block数量用unsigned char类型的数值上限(255)加以限制。

 

回答:

1. 这种freelist的设计技巧在于,融于数据结构本身,无须占用额外内存,且提供了一种高效的方法chunk中可用block。

2. 假定chunk改为class template,如下

template<typename T>
struct Chunk {
   void Init(std::size_t blockSize, T blocks);
   void Release();
   void * Allocate(std::size_t blockSize);
   void Deallocate(void *p, std::size_t blockSize);
   unsigned char *pData_;
   T firstAvailableBlock_, blocksAvailable_;
};

template<typename T>
void Chunk<T>::Init(std::size_t blockSize, T blocks){
   pData_ = new unsigned char[blockSize * blocks];
   firstAvailableBlock_ = 0;
   blocksAvailable_ = blocks;
   T i = 0;
   for (; i != blocks; p+=blockSize)
      *p = ++i;
}

按此修改后,如果T被unsigned short替换的话,最高值是65535, 但是我们无法分配比sizeof(unsigned short)小的内存,但是这不是十分大的问题,可以将比之小的内存对齐成sizeof(unsigned short)大小再分配;当然这会引起Internal fragmentation。

 

另一个问题是齐位问题,假如分配block大小是5个字节,那么区块的索引该用什么类型呢?unsigned short,还是unsigned int,在将指向这样一个5字节block的指针转换为unsigned int,会引发不确定的行为。即使按照特定的字节序进行读取这5个字节中的4个字节转换为unsigned int,也要费一番周折,付出的开销或许已经抵消了由此设计而带来的效率提升。

 

所以限定一下blocks的数量为255,保持在类型unsigned char的数值上限,不失为一个明智之举。一来,chunks不是那么大,二来char的大小是1个字节,无齐位问题,即使原始内存的指针也是指向unsigned char的。

 

分配函数Allocate()的动作就是取出firstAvailableBlock_所代表的区块,然后调整firstAvailableBlock_,使其指向下一个可用区块。

void* FixedAllocator::Chunk::Allocate(std::size_t blockSize)
{
    if (!blocksAvailable_) return 0;
    
    assert((firstAvailableBlock_ * blockSize) / blockSize == 
        firstAvailableBlock_);

    unsigned char* pResult =
        pData_ + (firstAvailableBlock_ * blockSize);
    firstAvailableBlock_ = *pResult;
    --blocksAvailable_;
    
    return pResult;
}

注意这个allocate的成本很小,因为不需要查找。

 

归还函数Deallocate:

void FixedAllocator::Chunk::Deallocate(void* p, std::size_t blockSize)
{
    assert(p >= pData_);

    unsigned char* toRelease = static_cast<unsigned char*>(p);
    // Alignment check
    assert((toRelease - pData_) % blockSize == 0);

    *toRelease = firstAvailableBlock_;
    firstAvailableBlock_ = static_cast<unsigned char>(
        (toRelease - pData_) / blockSize);
    // Truncation check
    assert(firstAvailableBlock_ == (toRelease - pData_) / blockSize);

    ++blocksAvailable_;
}

 配合看Allocate和Deallocate函数实现时,需要注意几点:

1. pData永远都是指向分配chunks的起始地址。

2. firstAvailableBlock_永远记录的是第一个可用block的索引号

3. 也是最重要的一点,(经过多次allocation和deallocation之后)每个block的第一个字节保存的是下一个可用block的索引号。

4. 一次内存请求只是一个block size,不能一次分配多个block。

 

FixedAllocator

小型对象分配器的第二层由FixedAllocator构成。FixedAllocator负责分配和归还“特定大小的block”,其大小不受限于chunk,因为FixedAllocator将新建的chunk对象放在一个vector中。任何一个内存请求,会找出一个适当的chunk满足,如果都被占用了,就新建一个chunk加入到vector中。下面是FixedAllocator的定义:

class FixedAllocator{
private:
   void DoDeallocate(void *p);
   bool MakeNewChunk(void);
   Chunk * VicinityFind(void *p) const;
   FixedAllocator(const FixedAllocator&); //copy ctor, but not implemented;
   FixedAllocator& operator=(const FixedAllocator&);// copy assignment, not implemented;
   
   typedef std::vector<Chunk> Chunks;
   typedef Chunks::iterator ChunkIter;
   typedef Chunks::const_iterator ChunkCIter;

   static unsigned char MinObjectsPerChunk_;
   // Fewest # of objects managed by a chunk;
   static unsigned char MaxObjectsPerChunk_;
   // maximum # of objects manged by a chunk;

   std::size_t blockSize_;  //FixedAllocator manages chunks owning blockSize_ specific blocks only.
   unsigned char numBlocks_;

   Chunks chunks;  // Container of Chunks
   Chunk *allocChunk_; //ptr to chunk used for last/next allocation.
   Chunk *deallocChunk_; // ptr to chunk used for last/next deallocation.
   Chunk * emptyChunk; // ptr to the only empty chunk if there is one, else null...

public:
    FixedAllocator();
   ~FixedAllocator();
    void Initialize(std::size_t blockSize, std::size_t pageSize);
    void *Allocate(void);
    bool Deallocate(void *p, Chunk *hint);
    inline std::size_t BlockSize() const { return blockSize_; }
    bool TrimEmptyChunk(void); // releases the memory used by empty chunk.
    bool TrimChunkList(void); // Returns count of empty Chunks held by this allocator.
    std::size_t CountEmptyChunks(void) const;
    bool IsCorrupt(void) const;
    const Chunk * HasBlock(void *p) const;
    inline Chunk * HasBlock(void *p){
        return const_cast<Chunk *>(
             const_cast< const FixedAllocator *>( this )->HasBlock(p) );
    }
};

 

FixedAllocator::allocate()

其中,allocChunk_指针指向“最近一次分配所使用的chunk”。每次分配请求都会先查询该指针所指的chunk,如果尚有空闲空间,分配请求将由此chunk分配获得。否则会触发一次线性查找。无论上述哪种情况,allocChunk_都会更新,指向刚找到的或新添加的chunk。这样可以提高下次分配速度。以下是loki_0.1.7的FixedAlloctor::Allocate(void)的实现:

void * FixedAllocator::Allocate( void )
{

    if ( ( NULL == allocChunk_ ) || allocChunk_->IsFilled() ) 
    //初始状态或者最近一次分配所用的chunk已被占用
    {
        if ( NULL != emptyChunk_ ) //有可用的chunk,并且是空的,直接从这个chunk分配内存
        {
            allocChunk_ = emptyChunk_;
            emptyChunk_ = NULL;
        }
        else //只能遍历chunks,看是否有空闲chunk可用
        {
            for ( ChunkIter i( chunks_.begin() ); ; ++i )
            {
                if ( chunks_.end() == i ) 
             // 如果没有,只能创建一个新的chunk,并加入到chunks,
             // 同时修改allocChunk_,deallocChunk_指针
                {
                    if ( !MakeNewChunk() )
                        return NULL;
                    break;
                }
                if ( !i->IsFilled() ) //如果chunks中某个chunk(first-fit)有空闲block,分配内存。
                {
                    allocChunk_ = &*i;
                    break;
                }
            }
        }
    }
    else if ( allocChunk_ == emptyChunk_) 
    //无空emptyChunk时,修改指针emptyChunk_,置空。
        emptyChunk_ = NULL;

    void * place = allocChunk_->Allocate( blockSize_ );

    return place;
}

 

FixedAllocator::deallocated(void *p, chunk *hint);

内存归还(deallocate)比较麻烦,因为不知道待还block属于哪个chunk的。是的,我们可以遍历chunks, 检查指针是否落在pData_和pData_+blockSize_ * numBlocks_之间。找到对应的chunk后,就在这个chunk内做deallocated动作。不过这么做,需要耗费线性时间归还内存。因此loki做了一些优化,设一个成员变量deallocChunk_指针,指向归还动作所用的最后那个chunk对象。任何归还动作都必须先检查这个deallocChunk_所指向的chunk,如果是错误的chunk,再进行线性搜索,搜到以后做deallocate动作,最后修改deallocChunk_指针。

 

bool FixedAllocator::Deallocate( void * p, Chunk * hint )
{
    Chunk * foundChunk = ( NULL == hint ) ? VicinityFind( p ) : hint;
    if ( NULL == foundChunk )
        return false;

    deallocChunk_ = foundChunk;
    DoDeallocate(p);

    return true;
}

Chunk * FixedAllocator::VicinityFind( void * p ) const
{
    if ( chunks_.empty() ) return NULL;
    assert(deallocChunk_);

    const std::size_t chunkLength = numBlocks_ * blockSize_;
    Chunk * lo = deallocChunk_;
    Chunk * hi = deallocChunk_ + 1;
    const Chunk * loBound = &chunks_.front();
    const Chunk * hiBound = &chunks_.back() + 1;

    // Special case: deallocChunk_ is the last in the array
    if (hi == hiBound) hi = NULL;  //边界条件

    for (;;)
    {
        if (lo)
        {
            if ( lo->HasBlock( p, chunkLength ) ) return lo;
            if ( lo == loBound )
            {
                lo = NULL;
                if ( NULL == hi ) break;
            }
            else --lo;
        }

        if (hi)
        {
            if ( hi->HasBlock( p, chunkLength ) ) return hi;
            if ( ++hi == hiBound )
            {
                hi = NULL;
                if ( NULL == lo ) break;
            }
        }
    }

    return NULL;
}

void FixedAllocator::DoDeallocate(void* p)
{
    // Show that deallocChunk_ really owns the block at address p.
    assert( deallocChunk_->HasBlock( p, numBlocks_ * blockSize_ ) );
    // Either of the next two assertions may fail if somebody tries to
    // delete the same block twice.
    assert( emptyChunk_ != deallocChunk_ );
    assert( !deallocChunk_->HasAvailable( numBlocks_ ) );
    // prove either emptyChunk_ points nowhere, or points to a truly empty Chunk.
    assert( ( NULL == emptyChunk_ ) || ( emptyChunk_->HasAvailable( numBlocks_ ) ) );

    // call into the chunk, will adjust the inner list but won't release memory
    deallocChunk_->Deallocate(p, blockSize_);

    if ( deallocChunk_->HasAvailable( numBlocks_ ) ) //如果deallocChunk是个空chunk
    {
        assert( emptyChunk_ != deallocChunk_ );
        // deallocChunk_ is empty, but a Chunk is only released if there are 2
        // empty chunks.  Since emptyChunk_ may only point to a previously
        // cleared Chunk, if it points to something else besides deallocChunk_,
        // then FixedAllocator currently has 2 empty Chunks.
        if ( NULL != emptyChunk_ )
        {
            // If last Chunk is empty, just change what deallocChunk_
            // points to, and release the last.  Otherwise, swap an empty
            // Chunk with the last, and then release it.
            Chunk * lastChunk = &chunks_.back();
            if ( lastChunk == deallocChunk_ )
                deallocChunk_ = emptyChunk_;
            else if ( lastChunk != emptyChunk_ )
                std::swap( *emptyChunk_, *lastChunk );
            assert( lastChunk->HasAvailable( numBlocks_ ) );
            lastChunk->Release();
            chunks_.pop_back();
            if ( ( allocChunk_ == lastChunk ) || allocChunk_->IsFilled() ) 
                allocChunk_ = deallocChunk_;
        }
        emptyChunk_ = deallocChunk_;
    }

    // prove either emptyChunk_ points nowhere, or points to a truly empty Chunk.
    assert( ( NULL == emptyChunk_ ) || ( emptyChunk_->HasAvailable( numBlocks_ ) ) );
}
 

注意VicinityFind函数,里面有两个迭代器,lo和hi,(不明白为什么不用std::iterator包装一下)每次迭代分别向上和向下进行。(这样实现,有点divide-and-conquer的感觉哈)。

 

在DoDeallocate函数实现中,当deallocChunk_是空的时候,并且emptyChunk_也存在时,说明FixedAllocator此时很有同时存在两个emptyChunk了(除了deallcoChunk_与emptyChunk_都指向同一个chunk时之外),这种情况势必要释放一个emptyChunk。根据语句 lastChunk->Release(); chunks_.pop_back();可以判断出释放动作发生在chunks vector的最后一个chunk对象上(lastChunk_)。因此,在此之前需要根据以下两种具体情况修改deallocChunk_和emptyChunk_指针:

1. 如果deallocChunk指向最后一个chunk,那么将deallocChunk指向原emptyChunk。

2. 如果lastChunk不是emptyChunk指向的那个chunk,那么交换两个chunk(不是指针交换)。

 

这么做的原因是为了避免一种边界条件(allocChunk_和deallocChunk_均指向同一个chunk,且该chunk已无空闲空间)。如果按照常规的allocate()的逻辑,会新建一个chunk,并加入到chunks中,并在新建的chunk上完成内存分配。可以恰好,这个刚分配完的内存又要归还。这时deallocChunk_找到新建的chunk,完成归还动作后,发现是空的chunk,就释放掉这个新建的chunk。如果这个逻辑如此反复,性能损耗不可小觑。所以只有在发现两个空的chunk时,才会归还其中之一。

 

SmallObjAllocator

loki的allocator的第三层便是这个SmallObjAllocator,它能够分配任意大小的对象。SmallObjAllocator聚集了N个FixedAllocator。当SmallObjAllocator接收到一个分配请求时,会将请求分配给最佳匹配的FixedAllocator,否则就转给缺省的::operator new. 所以SmallObjAllocator的声明如下:

class LOKI_EXPORT SmallObjAllocator
    {
    protected:
        SmallObjAllocator( std::size_t pageSize, std::size_t maxObjectSize,
            std::size_t objectAlignSize );

        ~SmallObjAllocator( void );

    public:       
        void * Allocate( std::size_t size, bool doThrow );
        void Deallocate( void * p, std::size_t size );
                //多了一个参数,表明待还内存大小
        void Deallocate( void * p );
        inline std::size_t GetMaxObjectSize() const
        { return maxSmallObjectSize_; }

        /// Returns # of bytes between allocation boundaries.
        inline std::size_t GetAlignment() const { return objectAlignSize_; }

        bool TrimExcessMemory( void );
        bool IsCorrupt( void ) const;

    private:
        SmallObjAllocator( void );
        SmallObjAllocator( const SmallObjAllocator & );
        SmallObjAllocator & operator = ( const SmallObjAllocator & );
        Loki::FixedAllocator * pool_; //array instead of vector. (modification)

        /// Largest object size supported by allocators. 超过这值就转发到默认的::operator new
        const std::size_t maxSmallObjectSize_;

        /// Size of alignment boundaries.
        const std::size_t objectAlignSize_;
    };

  void * SmallObjAllocator::Allocate( std::size_t numBytes, bool doThrow )

void * SmallObjAllocator::Allocate( std::size_t numBytes, bool doThrow )
{
    if ( numBytes > GetMaxObjectSize() )
        return DefaultAllocator( numBytes, doThrow );

    assert( NULL != pool_ );
    if ( 0 == numBytes ) numBytes = 1;
    const std::size_t index = GetOffset( numBytes, GetAlignment() ) - 1;
    const std::size_t allocCount = GetOffset( GetMaxObjectSize(), GetAlignment() );
    (void) allocCount;
    assert( index < allocCount );

    FixedAllocator & allocator = pool_[ index ];
    assert( allocator.BlockSize() >= numBytes );
    assert( allocator.BlockSize() < numBytes + GetAlignment() );
    void * place = allocator.Allocate();

    if ( ( NULL == place ) && TrimExcessMemory() )
        place = allocator.Allocate();

    if ( ( NULL == place ) && doThrow )
    {
#ifdef _MSC_VER
        throw std::bad_alloc( "could not allocate small object" );
#else
        // GCC did not like a literal string passed to std::bad_alloc.
        // so just throw the default-constructed exception.
        throw std::bad_alloc();
#endif
    }
    return place;
}

 pool_是个FixedAllocator数组,为了能够简单而且高效地查找到哪个FixedAllocator负责哪个大小的内存块,所以数组下标index处理index大小的内存块。但是事情总是没有那么完美的,不是吗?也许某个应用程序只需产生4字节和32字节的大小的两种对象,再没其他的了。但是pool_还是要分配负责除此两个大小以外的FixedAllocator。

void SmallObjAllocator::Deallocate( void * p, std::size_t numBytes )

void SmallObjAllocator::Deallocate( void * p, std::size_t numBytes )
{
    if ( NULL == p ) return;
    if ( numBytes > GetMaxObjectSize() )
    {
        DefaultDeallocator( p );
        return;
    }
    assert( NULL != pool_ );
    if ( 0 == numBytes ) numBytes = 1;
    const std::size_t index = GetOffset( numBytes, GetAlignment() ) - 1;
    const std::size_t allocCount = GetOffset( GetMaxObjectSize(), GetAlignment() );
    (void) allocCount;
    assert( index < allocCount );
    FixedAllocator & allocator = pool_[ index ];
    assert( allocator.BlockSize() >= numBytes );
    assert( allocator.BlockSize() < numBytes + GetAlignment() );
    const bool found = allocator.Deallocate( p, NULL );
    (void) found;
    assert( found );
}

void SmallObjAllocator::Deallocate( void * p )
//这个函数因为只有一个待还区块的指针,所以不得已需要遍历FixedAllocator,找到对应负责的FixedAllocator。
{
    if ( NULL == p ) return;
    assert( NULL != pool_ );
    FixedAllocator * pAllocator = NULL;
    const std::size_t allocCount = GetOffset( GetMaxObjectSize(), GetAlignment() );
    Chunk * chunk = NULL;

    for ( std::size_t ii = 0; ii < allocCount; ++ii )
    {
        chunk = pool_[ ii ].HasBlock( p );
        if ( NULL != chunk )
        {
            pAllocator = &pool_[ ii ];
            break;
        }
    }
    if ( NULL == pAllocator )
    {
        DefaultDeallocator( p );
        return;
    }

    assert( NULL != chunk );
    const bool found = pAllocator->Deallocate( p, chunk );
    (void) found;
    assert( found );
}

 SmallObjAllocator只是FixedAllocator的简单包装。

SmallObject

第四层,它将第三层提供的功能做更便捷于是用的包装。SmallObject重载系统默认的::operator new 和 ::operator delete。 这样,每生成一个SmallObject对象,重载后的行为便会将分配请求发送给底层的FixedAllocator。所以定义如下

template
    <
        template <class, class> class ThreadingModel = LOKI_DEFAULT_THREADING_NO_OBJ_LEVEL,
        std::size_t chunkSize = LOKI_DEFAULT_CHUNK_SIZE,
        std::size_t maxSmallObjectSize = LOKI_MAX_SMALL_OBJECT_SIZE,
        std::size_t objectAlignSize = LOKI_DEFAULT_OBJECT_ALIGNMENT,
        template <class> class LifetimePolicy = LOKI_DEFAULT_SMALLOBJ_LIFETIME,
        class MutexPolicy = LOKI_DEFAULT_MUTEX
    >
    class SmallObject : public SmallObjectBase< ThreadingModel, chunkSize,
            maxSmallObjectSize, objectAlignSize, LifetimePolicy, MutexPolicy >
    {

    public:
        virtual ~SmallObject() {}
    protected:
        inline SmallObject( void ) {}

    private:
        /// Copy-constructor is not implemented.
        SmallObject( const SmallObject & );
        /// Copy-assignment operator is not implemented.
        SmallObject & operator = ( const SmallObject & );
    }; // end class SmallObject

template
    <
        template <class, class> class ThreadingModel,
        std::size_t chunkSize,
        std::size_t maxSmallObjectSize,
        std::size_t objectAlignSize,
        template <class> class LifetimePolicy,
        class MutexPolicy
    >
    class SmallObjectBase
    {

    public:        
     
        typedef AllocatorSingleton< ThreadingModel, chunkSize,
            maxSmallObjectSize, objectAlignSize, LifetimePolicy > ObjAllocatorSingleton;
    
    private:
        typedef ThreadingModel< ObjAllocatorSingleton, MutexPolicy > MyThreadingModel;
        typedef typename ObjAllocatorSingleton::MyAllocatorSingleton MyAllocatorSingleton;
        
    public:

        static void * operator new ( std::size_t size, const std::nothrow_t & ) throw ()
        {
            typename MyThreadingModel::Lock lock;
            (void)lock; // get rid of warning
            return MyAllocatorSingleton::Instance().Allocate( size, false );
        }

        /// Placement single-object new merely calls global placement new.
        inline static void * operator new ( std::size_t size, void * place )
        {
            return ::operator new( size, place );
        }

        static void operator delete ( void * p, std::size_t size ) throw ()
        {
            typename MyThreadingModel::Lock lock;
            (void)lock; // get rid of warning
            MyAllocatorSingleton::Instance().Deallocate( p, size );
        }

        static void operator delete ( void * p, const std::nothrow_t & ) throw()
        {
            typename MyThreadingModel::Lock lock;
            (void)lock; // get rid of warning
            MyAllocatorSingleton::Instance().Deallocate( p );
        }

        /// Placement single-object delete merely calls global placement delete.
        inline static void operator delete ( void * p, void * place )
        {
            ::operator delete ( p, place );
        }

    protected:
        inline SmallObjectBase( void ) {}
        inline SmallObjectBase( const SmallObjectBase & ) {}
        inline SmallObjectBase & operator = ( const SmallObjectBase & )
        { return *this; }
        inline ~SmallObjectBase() {}
    }; // end class SmallObjectBase

 在上述实现过程中,作者Andrei又运用了一个C++编译器的技巧。在重载::operator delete(void *p, size_t size)时,实际上是需要SmallObject提供被析构对象的大小(运用了C++编译器删除对象前,即时产生一些代码计算被删除对象的大小),提供给SmallObject重载delete函数的“大小”参数来源于此。因此SmallObject提供了一个虚析构函数。因此,从SmallObject派生的任何类都会继承这个虚析构函数。

 

另一个问题就是对于整个程序而言,只需要一个唯一的SmallObjAllocator,因此SmallObject在包装第三层时,使用了Singleton模式。(Singleton不在本篇讨论范围内,在此不加描述)。

 

小结:

       正在写测试程序,对比性能;待续。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值