1. 程式人生 > >【原始碼剖析】MemoryPool —— 簡單高效的記憶體池 allocator 實現

【原始碼剖析】MemoryPool —— 簡單高效的記憶體池 allocator 實現

      什麼是記憶體池?什麼是 C++ 的 allocator?

      記憶體池簡單說,是為了減少頻繁使用 malloc/free new/delete 等系統呼叫而造成的效能損耗而設計的。當我們的程式需要頻繁地申請和釋放記憶體時,頻繁地使用記憶體管理的系統呼叫可能會造成效能的瓶頸,嗯,是可能,畢竟作業系統的設計也不是蓋的(麻麻說把話說太滿會被打臉的(⊙v⊙))。記憶體池的思想是申請較大的一塊記憶體(不夠時繼續申請),之後把記憶體管理放在應用層執行,減少系統呼叫的開銷。

     那麼,allocator 呢?它默默的工作在 C++ 所有容器的記憶體分配上。默默貼幾個連結吧:

     http://www.cnblogs.com/wpcockroach/archive/2012/05/10/2493564.html

     http://blog.csdn.net/justaipanda/article/details/7790355

     http://www.cplusplus.com/reference/memory/allocator/

     http://www.cplusplus.com/reference/memory/allocator_traits/

     當你對 allocator 有基本的瞭解之後,再看這個專案應該會有恍然大悟的感覺,因為這個記憶體池是以一個 allocator 的標準來實現的。一開始不明白專案裡很多函式的定義是為了什麼,結果初步瞭解了 allocator 後才知道大部分是標準介面。這樣一個 memory pool allocator 可以與大多數 STL 容器相容,也可以應用於你自定義的類。像作者給出的例子 —— test.cpp, 是用一個基於自己寫的 stack 來做 memory pool allocator 和 std::allocator 效能的對比 —— 最後當然是 memory pool allocator 更優。


      專案:

      Github:MemoryPool


      基本使用:

      因為這是一個 allocator 類,所以所有使用 std::allocator 的地方都可以使用這個 MemoryPool。在專案的 test.cpp 中,MemoryPool 作為 allocator 用於 StackAlloc(作者實現的 demo 類) 的記憶體管理類。定義如下:

  StackAlloc<int, MemoryPool<int> > stackPool;
     其次,你也可以將其直接作為任一型別的記憶體池,用 newElement 建立新元素,deleteElement 釋放元素,就像 new/delete 一樣。用下面的例子和 new/delete 做對比:

#include <iostream>
#include <cassert>
#include <time.h>
#include <vector>
#include <stack>

#include "MemoryPool.h"

using namespace std;

/* Adjust these values depending on how much you trust your computer */
#define ELEMS 1000000
#define REPS 50

int main()
{

    clock_t start;

    MemoryPool<size_t> pool;
    start = clock();
    for(int i = 0;i < REPS;++i)
    {
        for(int j = 0;j< ELEMS;++j)
        {
            // 建立元素
            size_t* x = pool.newElement();

            // 釋放元素
            pool.deleteElement(x);
        }
    }
    std::cout << "MemoryPool Time: ";
    std::cout << (((double)clock() - start) / CLOCKS_PER_SEC) << "\n\n";


    start = clock();
    for(int i = 0;i < REPS;++i)
    {
        for(int j = 0;j< ELEMS;++j)
        {
            size_t* x = new size_t;

            delete x;
        }
    }
    std::cout << "new/delete Time: ";
    std::cout << (((double)clock() - start) / CLOCKS_PER_SEC) << "\n\n";

    return 0;

}
     執行的結果是:

     MemoryPool Time: 1.93389

     new/delete Time: 4.64903
     嗯,記憶體池快了一倍多。如果是自定義的類的話這個差距應該還會更大一點。


     程式碼分析:

     專案的實現有 C++11 和 C++98 兩個版本,C++11 版本似乎更加高效,不過個人 C++11 瞭解不多,就以 C++98 版本來分析吧。

      主要函式:

     allocate    分配一個物件所需的記憶體空間

     deallocate   釋放一個物件的記憶體(歸還給記憶體池,不是給作業系統)

     construct   在已申請的記憶體空間上構造物件

     destroy  析構物件

     newElement  從記憶體池申請一個物件所需空間,並呼叫物件的建構函式

     deleteElement  析構物件,將記憶體空間歸還給記憶體池

     allocateBlock  從作業系統申請一整塊記憶體放入記憶體池

       關鍵知識點:

      理解專案的關鍵在於理解 placement new 和 union 的用法.

      placement new: http://blog.csdn.net/zhangxinrun/article/details/5940019

      union:http://www.cnblogs.com/BeyondTechnology/archive/2010/09/19/1831293.html

      關於 union 的使用覺得好巧妙,這是相應的定義:

    union Slot_ {
      value_type element;
      Slot_* next;
    };
     Slot_ 在建立物件的時候存放物件的值,當這個物件被釋放時這塊記憶體作為一個 Slot_* 指標放入 free 的連結串列。所以 Slot_ 既可以用來存放物件,又可以用來構造連結串列。

       工作原理:

      記憶體池是一個一個的 block 以連結串列的形式連線起來,每一個 block 是一塊大的記憶體,當記憶體池的記憶體不足的時候,就會向作業系統申請新的 block 加入連結串列。還有一個 freeSlots_ 的連結串列,連結串列裡面的每一項都是物件被釋放後歸還給記憶體池的空間,記憶體池剛建立時 freeSlots_ 是空的,之後隨著使用者建立物件,再將物件釋放掉,這時候要把記憶體歸還給記憶體池,怎麼歸還呢?就是把指向這個物件的記憶體的指標加到 freeSlots_ 連結串列的前面(前插)。

     使用者在建立物件的時候,先檢查 freeSlots_ 是否為空,不為空的時候直接取出一項作為分配出的空間。否則就在當前 block 內取出一個 Slot_ 大小的記憶體分配出去,如果 block 裡面的記憶體已經使用完了呢?就向作業系統申請一個新的 block。

     記憶體池工作期間的記憶體只會增長,不釋放給作業系統。直到記憶體池銷燬的時候,才把所有的 block delete 掉。

       註釋原始碼:

      點我到 Github

      標頭檔案:

/*-
 * Copyright (c) 2013 Cosku Acay, http://www.coskuacay.com
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#ifndef MEMORY_POOL_H
#define MEMORY_POOL_H

#include <limits.h>
#include <stddef.h>

template <typename T, size_t BlockSize = 4096>
class MemoryPool
{
  public:
    /* Member types */
    typedef T               value_type;       // T 的 value 型別
    typedef T*              pointer;          // T 的 指標型別
    typedef T&              reference;        // T 的引用型別
    typedef const T*        const_pointer;    // T 的 const 指標型別
    typedef const T&        const_reference;  // T 的 const 引用型別
    typedef size_t          size_type;        // size_t 型別
    typedef ptrdiff_t       difference_type;  // 指標減法結果型別

    template <typename U> struct rebind {
      typedef MemoryPool<U> other;
    };

    /* Member functions */
    /* 建構函式 */
    MemoryPool() throw();
    MemoryPool(const MemoryPool& memoryPool) throw();
    template <class U> MemoryPool(const MemoryPool<U>& memoryPool) throw();

    /* 解構函式 */
    ~MemoryPool() throw();

    /* 元素取址 */
    pointer address(reference x) const throw();
    const_pointer address(const_reference x) const throw();

    // Can only allocate one object at a time. n and hint are ignored
    // 分配和收回一個元素的記憶體空間
    pointer allocate(size_type n = 1, const_pointer hint = 0);
    void deallocate(pointer p, size_type n = 1);

    // 可達到的最多元素數
    size_type max_size() const throw();

    // 基於記憶體池的元素構造和析構
    void construct(pointer p, const_reference val);
    void destroy(pointer p);

    // 自帶申請記憶體和釋放記憶體的構造和析構
    pointer newElement(const_reference val);
    void deleteElement(pointer p);

  private:
    // union 結構體,用於存放元素或 next 指標
    union Slot_ {
      value_type element;
      Slot_* next;
    };

    typedef char* data_pointer_;  // char* 指標,主要用於指向記憶體首地址
    typedef Slot_ slot_type_;     // Slot_ 值型別
    typedef Slot_* slot_pointer_; // Slot_* 指標型別

    slot_pointer_ currentBlock_;  // 記憶體塊連結串列的頭指標
    slot_pointer_ currentSlot_;   // 元素連結串列的頭指標
    slot_pointer_ lastSlot_;      // 可存放元素的最後指標
    slot_pointer_ freeSlots_;     // 元素構造後釋放掉的記憶體連結串列頭指標

    size_type padPointer(data_pointer_ p, size_type align) const throw();  // 計算對齊所需空間
    void allocateBlock();  // 申請記憶體塊放進記憶體池
   /*
    static_assert(BlockSize >= 2 * sizeof(slot_type_), "BlockSize too small.");
    */
};

#include "MemoryPool.tcc"

#endif // MEMORY_POOL_H

      實現檔案:

/*-
 * Copyright (c) 2013 Cosku Acay, http://www.coskuacay.com
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#ifndef MEMORY_BLOCK_TCC
#define MEMORY_BLOCK_TCC

// 計算對齊所需補的空間
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::size_type
MemoryPool<T, BlockSize>::padPointer(data_pointer_ p, size_type align)
const throw()
{
  size_t result = reinterpret_cast<size_t>(p);
  return ((align - result) % align);
}

/* 建構函式,所有成員初始化 */
template <typename T, size_t BlockSize>
MemoryPool<T, BlockSize>::MemoryPool()
throw()
{
  currentBlock_ = 0;
  currentSlot_ = 0;
  lastSlot_ = 0;
  freeSlots_ = 0;
}

/* 複製建構函式,呼叫 MemoryPool 初始化*/
template <typename T, size_t BlockSize>
MemoryPool<T, BlockSize>::MemoryPool(const MemoryPool& memoryPool)
throw()
{
  MemoryPool();
}

/* 複製建構函式,呼叫 MemoryPool 初始化*/
template <typename T, size_t BlockSize>
template<class U>
MemoryPool<T, BlockSize>::MemoryPool(const MemoryPool<U>& memoryPool)
throw()
{
  MemoryPool();
}

/* 解構函式,把記憶體池中所有 block delete 掉 */
template <typename T, size_t BlockSize>
MemoryPool<T, BlockSize>::~MemoryPool()
throw()
{
  slot_pointer_ curr = currentBlock_;
  while (curr != 0) {
    slot_pointer_ prev = curr->next;
    // 轉化為 void 指標,是因為 void 型別不需要呼叫解構函式,只釋放空間
    operator delete(reinterpret_cast<void*>(curr));
    curr = prev;
  }
}

/* 返回地址 */
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::pointer
MemoryPool<T, BlockSize>::address(reference x)
const throw()
{
  return &x;
}

/* 返回地址的 const 過載*/
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::const_pointer
MemoryPool<T, BlockSize>::address(const_reference x)
const throw()
{
  return &x;
}

// 申請一塊空閒的 block 放進記憶體池
template <typename T, size_t BlockSize>
void
MemoryPool<T, BlockSize>::allocateBlock()
{
  // Allocate space for the new block and store a pointer to the previous one
  // operator new 申請對應大小記憶體,返回 void* 指標
  data_pointer_ newBlock = reinterpret_cast<data_pointer_>
                           (operator new(BlockSize));
  // 原來的 block 鏈頭接到 newblock
  reinterpret_cast<slot_pointer_>(newBlock)->next = currentBlock_;
  // 新的 currentblock_
  currentBlock_ = reinterpret_cast<slot_pointer_>(newBlock);
  // Pad block body to staisfy the alignment requirements for elements
  data_pointer_ body = newBlock + sizeof(slot_pointer_);
  // 計算為了對齊應該空出多少位置
  size_type bodyPadding = padPointer(body, sizeof(slot_type_));
  // currentslot_ 為該 block 開始的地方加上 bodypadding 個 char* 空間
  currentSlot_ = reinterpret_cast<slot_pointer_>(body + bodyPadding);
  // 計算最後一個能放置 slot_type_ 的位置
  lastSlot_ = reinterpret_cast<slot_pointer_>
              (newBlock + BlockSize - sizeof(slot_type_) + 1);
}

// 返回指向分配新元素所需記憶體的指標
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::pointer
MemoryPool<T, BlockSize>::allocate(size_type, const_pointer)
{
  // 如果 freeSlots_ 非空,就在 freeSlots_ 中取記憶體
  if (freeSlots_ != 0) {
    pointer result = reinterpret_cast<pointer>(freeSlots_);
    // 更新 freeSlots_
    freeSlots_ = freeSlots_->next;
    return result;
  }
  else {
    if (currentSlot_ >= lastSlot_)
      // 之前申請的記憶體用完了,分配新的 block
      allocateBlock();
    // 從分配的 block 中劃分出去
    return reinterpret_cast<pointer>(currentSlot_++);
  }
}

// 將元素記憶體歸還給 free 記憶體連結串列
template <typename T, size_t BlockSize>
inline void
MemoryPool<T, BlockSize>::deallocate(pointer p, size_type)
{
  if (p != 0) {
    // 轉換成 slot_pointer_ 指標,next 指向 freeSlots_ 連結串列
    reinterpret_cast<slot_pointer_>(p)->next = freeSlots_;
    // 新的 freeSlots_ 頭為 p
    freeSlots_ = reinterpret_cast<slot_pointer_>(p);
  }
}

// 計算可達到的最大元素上限數
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::size_type
MemoryPool<T, BlockSize>::max_size()
const throw()
{
  size_type maxBlocks = -1 / BlockSize;
  return (BlockSize - sizeof(data_pointer_)) / sizeof(slot_type_) * maxBlocks;
}

// 在已分配記憶體上構造物件
template <typename T, size_t BlockSize>
inline void
MemoryPool<T, BlockSize>::construct(pointer p, const_reference val)
{
  // placement new 用法,在已有記憶體上構造物件,呼叫 T 的複製建構函式,
  new (p) value_type (val);
}

// 銷燬物件
template <typename T, size_t BlockSize>
inline void
MemoryPool<T, BlockSize>::destroy(pointer p)
{
  // placement new 中需要手動呼叫元素 T 的解構函式
  p->~value_type();
}

// 建立新元素
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::pointer
MemoryPool<T, BlockSize>::newElement(const_reference val)
{
  // 申請記憶體
  pointer result = allocate();
  // 在記憶體上構造物件
  construct(result, val);
  return result;
}

// 刪除元素
template <typename T, size_t BlockSize>
inline void
MemoryPool<T, BlockSize>::deleteElement(pointer p)
{
  if (p != 0) {
    // placement new 中需要手動呼叫元素 T 的解構函式
    p->~value_type();
    // 歸還記憶體
    deallocate(p);
  }
}

#endif // MEMORY_BLOCK_TCC