Boost內存池使用與測試

QQ:386859647
微信:herelsp
轉自:http://tech.it168.com/a2011/0726/1223/000001223399_all.shtmlios

  • Boost庫是一個可移植的開源C++函數庫,鑑於STL(標準模板庫)已經成爲C++語言的一個組成部分,能夠絕不誇張的說,Boost是目前影響最大的通用C++庫。Boost庫由C++標準委員會庫工做組成員發起,其中有些內容有望成爲下一代C++標準庫內容,是一個「準」標準庫。
  • Boost內存池,即boost.pool庫,是由Boost提供的一個用於內存池管理的開源C++庫。做爲Boost中影響較大的一個庫,Pool已經被普遍使用。

Boost內存池使用與測試

什麼是內存池

   是在計算機技術中常用的一種設計模式,其內涵在於:將程序中須要常用的核心資源先申請出來,放到一個池內,由程序本身管理,這樣能夠提升資源的使用效率,也能夠保證本程序佔有的資源數量
常用的池技術包括內存池、線程池和鏈接池等,其中尤之內存池和線程池使用最多。
內存池(Memory Pool) 是一種動態內存分配與管理技術。
一般狀況下,程序員習慣直接使用 new、delete、malloc、free 等API申請分配和釋放內存,這樣致使的後果是:當程序長時間運行時,因爲所申請內存塊的大小不定,頻繁使用時會形成大量的內存碎片從而下降程序和操做系統的性能
內存池則是在真正使用內存以前,先申請分配一大塊內存(內存池)留做備用,當程序員申請內存時,從池中取出一塊動態分配,當程序員釋放內存時,將釋放的內存再放入池內,並儘可能與周邊的空閒內存塊合併。若內存池不夠時,則自動擴大內存池,從操做系統中申請更大的內存池。程序員

內存池的應用場景

  早期的內存池技術是爲了專門解決那種頻繁申請和釋放相同大小內存塊的程序,所以早期的一些內存池都是用相同大小的內存塊鏈表組織起來的。
  Boost的內存池則對內存塊的大小是否相同沒有限制,所以只要是頻繁動態申請釋放內存的長時間運行程序,都適用Boost內存池。這樣能夠有效減小內存碎片並提升程序運行效率。算法

安裝

  Boost的pool庫是以C++頭文件的形式提供的,不須要安裝,也沒有lib或者dll文件,僅僅須要將頭文件包含到你的C++工程中就能夠了。Boost的最新版本能夠到 http://www.boost.org/ 下載。設計模式

內存池的特徵

無內存泄露

正確的使用內存池的申請和釋放函數不會形成內存泄露,更重要的是,即便不正確的使用了申請和釋放函數,內存池中的內存也會在進程結束時被所有自動釋放,不會形成系統的內存泄露。數組

申請的內存數組沒有被填充

  例如一個元素的內存大小爲A,那麼元素數組若包含n個元素,則該數組的內存大小必然是A*n,不會有多餘的內存來填充該數組。儘管每一個元素也許包含一些填充的東西。安全

任何數組內存塊的位置都和使用operator new[]分配的內存塊位置一致

  這代表你仍可使用那些經過數組指針計算內存塊位置的算法。微信

內存池要比直接使用系統的動態內存分配快

  這個快是機率意義上的,不是每一個時刻,每種內存池都比直接使用new或者malloc快。例如,當程序使用內存池時內存池剛好處於已經滿了的狀態,那麼此次內存申請會致使內存池自我擴充,確定比直接new一塊內存要慢。但在大部分時候,內存池要比new或者malloc快不少。多線程

內存池效率測試

測試1:連續申請和連續釋放

  分別用內存池和new連續申請和連續釋放大量的內存塊,比較其運行速度,代碼以下:函數

#include "stdafx.h"
#include <iostream>
#include <ctime>
#include <vector>
#include <boost/pool/pool.hpp>
#include <boost/pool/object_pool.hpp>
using namespace std;
using namespace boost;

const int MAXLENGTH = 100000;

int main ( )
{
    boost::pool<> p(sizeof(int));
    int* vec1[MAXLENGTH];
    int* vec2[MAXLENGTH];

    clock_t clock_begin = clock();
    for (int i = 0; i < MAXLENGTH; ++i)
    {
        vec1[i] = static_cast<int*>(p.malloc());
    }
    for (int i = 0; i < MAXLENGTH; ++i)
    {
        p.free(vec1[i]);
        vec1[i] = NULL;
    }

    clock_t clock_end = clock();
    cout<<"程序運行了 "<<clock_end-clock_begin<<" 個系統時鐘"<<endl;

    clock_begin = clock();
    for (int i = 0; i < MAXLENGTH; ++i)
    {
        vec2[i] = new int;
    }
    for (int i = 0; i < MAXLENGTH; ++i)
    {
        delete vec2[i];
        vec2[i] = NULL;
    }

    clock_end = clock();
    cout<<"程序運行了 "<<clock_end-clock_begin<<" 個系統時鐘"<<endl;

    return 0;
}

  測試環境:VS2008,WindowXP SP2,Pentium 4 CPU雙核,1.5GB內存。
image.png

  結論:在連續申請和連續釋放10萬塊內存的狀況下,使用內存池耗時是使用new耗時的47.46%。

測試2:反覆申請和釋放小塊內存

代碼以下:

#include "stdafx.h"
#include <iostream>
#include <ctime>
#include <vector>
#include <boost/pool/pool.hpp>
#include <boost/pool/object_pool.hpp>
using namespace std;
using namespace boost;

const int MAXLENGTH = 500000;

int main ( )
{
    boost::pool<> p(sizeof(int));

    clock_t clock_begin = clock();
    for (int i = 0; i < MAXLENGTH; ++i)
    {
        int * t = static_cast<int*>(p.malloc());
        p.free(t);
    }
    clock_t clock_end = clock();
    cout<<"程序運行了 "<<clock_end-clock_begin<<" 個系統時鐘"<<endl;

    clock_begin = clock();
    for (int i = 0; i < MAXLENGTH; ++i)
    {
        int* t = new int;
        delete t;
    }
    clock_end = clock();
    cout<<"程序運行了 "<<clock_end-clock_begin<<" 個系統時鐘"<<endl;

    return 0;
}

  測試結果以下:
image.png

  結論:在反覆申請和釋放50萬次內存的狀況下,使用內存池耗時是使用new耗時的64.34%。

測試3:反覆申請和釋放C++對象

  C++對象在動態申請和釋放時,不只要進行內存操做,同時還要調用構造和析購函數。所以有必要對C++對象也進行內存池的測試。
  代碼以下:

#include "stdafx.h"
#include <iostream>
#include <ctime>
#include <vector>
#include <boost/pool/pool.hpp>
#include <boost/pool/object_pool.hpp>
using namespace std;
using namespace boost;

const int MAXLENGTH = 500000;
class A
{
public: 
    A()
    {
        m_i++; 
    }
    ~A( )
    {
        m_i--; 
    }
private:
    int m_i;
};

int main ( )
{
    object_pool<A> q;

    clock_t clock_begin = clock();
    for (int i = 0; i < MAXLENGTH; ++i)
    {
        A* a = q.construct();
        q.destroy(a);
    }

    clock_t clock_end = clock();
    cout<<"程序運行了 "<<clock_end-clock_begin<<" 個系統時鐘"<<endl;

    clock_begin = clock();
    for (int i = 0; i < MAXLENGTH; ++i)
    {
        A* a = new A; 
        delete a;
    }
    clock_end = clock();
    cout<<"程序運行了 "<<clock_end-clock_begin<<" 個系統時鐘"<<endl;

    return 0;
}

  測試結果以下:
image.png

  結論:在反覆申請和釋放50萬個C++對象的狀況下,使用內存池耗時是使用new耗時的112.03%。這是由於內存池的construct和destroy函數增長了函數調用次數的緣由。這種狀況下使用內存池並不能得到性能上的優化。

Boost內存池的分類

  Boost內存池按照不一樣的理念分爲四類。主要是兩種理念的不一樣形成了這樣的分類。
  一是Object Usage和Singleton Usage的不一樣。

  • Object Usage意味着每一個內存池都是一個能夠建立和銷燬的對象,一旦內存池被銷燬則其所分配的全部內存都會被釋放。
  • Singleton Usage意味着每一個內存池都是一個被靜態分配的對象,直至程序結束纔會被銷燬,這也意味着這樣的內存池是多線程安全的。只有使用release_memory或者 purge_memory方法才能釋放內存。
      二是內存溢出的處理方式。第一種方式是返回NULL表明內存池溢出了;第二種方式是拋出異常表明內存池溢出。
      根據以上的理念,boost的內存池分爲四種。

    Pool

      Pool是一個Object Usage的內存池,溢出時返回NULL。

    object_pool

      object_pool與pool相似,惟一的區別是當其分配的內存釋放時,它會嘗試調用該對象的析購函數。

    singleton_pool

      singleton_pool是一個Singleton Usage的內存池,溢出時返回NULL。

    pool_alloc

      pool_alloc是一個Singleton Usage的內存池,溢出時拋出異常。

    內存池溢出的原理與解決方法

    必然溢出的內存

      內存池簡化了不少內存方面的操做,也避免了一些錯誤使用內存對程序形成的損害。可是,使用內存池時最須要注意的一點是要處理內存池溢出的狀況。
      沒有不溢出的內存,看看下面的代碼:
#include "stdafx.h"
#include <iostream>
#include <ctime>
#include <vector>
#include <boost/pool/pool.hpp>
#include <boost/pool/object_pool.hpp>
using namespace std;
using namespace boost;

int _tmain(int argc, _TCHAR* argv[])
{
    clock_t clock_begin = clock();
    int iLength = 0;
    for (int i = 0; ;++i)
    {
        void* p = malloc(1024*1024);
        if (p == NULL)
        {
            break;
        }
        ++iLength;
    }
    clock_t clock_end = clock();
    cout<<"共申請了"<<iLength<<"M內存,程序運行了"<<clock_end-clock_begin<<" 個系統時鐘"<<endl;
    return 0;
}

  運行的結果是「共申請了1916M內存,程序運行了 69421 個系統時鐘」,意思是在分配了1916M內存後,malloc已經不可以申請到1M大小的內存塊了。
  內存池在底層也是調用了malloc函數,所以內存池也是必然會溢出的。並且內存池可能會比直接調用malloc更早的溢出,看看下面的代碼:

#include "stdafx.h"
#include <iostream>
#include <ctime>
#include <vector>
#include <boost/pool/pool.hpp>
#include <boost/pool/object_pool.hpp>
using namespace std;
using namespace boost;

int _tmain(int argc, _TCHAR* argv[])
{
    boost::pool<> pl(1024*1024);
    clock_t clock_begin = clock();
    int iLength = 0;
    for (int i = 0; ;++i)
    {
        void* p = pl.malloc();
        if (p == NULL)
        {
            break;
        }
        ++iLength;
    }
    clock_t clock_end = clock();
    cout<<"共申請了"<<iLength<<"M內存,程序運行了"<<clock_end-clock_begin<<" 個系統時鐘"<<endl;
    return 0;
}

  運行的結果是「共申請了992M內存,程序運行了 1265 個系統時鐘」,意思是在分配了992M內存後,內存池已經不可以申請到1M大小的內存塊了。

內存池的基本原理

  從上面的兩個測試能夠看出內存池要比malloc溢出早,個人機器內存是1.5G,malloc分配了1916M才溢出(顯然分配了虛擬內存),而內存池只分配了992M就溢出了。第二點是內存池溢出快,只用了1265微秒就溢出了,而malloc用了69421微秒才溢出。
  這些差異是內存池的處理機制形成的,內存池對於內存分配的算法以下,以pool內存池爲例:

    1. pool初始化時帶有一個塊大小的參數memSize,那麼pool剛開始會申請一大塊內存,例如其大小爲32*memSize。固然它還會申請一些空間用以管理鏈表,爲方便述說,這裏忽略這些內存。
    1. 用戶不停的申請大小爲memSize的內存,終於超過了內存池的大小,因而內存池啓動重分配機制;
    1. 重分配機制會再申請一塊大小爲原內存池大小兩倍的內存(那麼第一次會申請64*memSize),而後將這塊內存加到內存池的管理鏈表末尾;
    1. 用戶繼續申請內存,終於又一次超過了內存池的大小,因而又一次啓動重分配機制,直至重分配時沒法申請到新的內存塊。
    1. 因爲每次都是兩倍於原內存,所以當內存池大小達到了992M時,再一次申請就須要1984M,可是malloc最多隻能申請到1916M,所以malloc失敗,內存池溢出。
        經過以上原理也能夠理解爲何內存池溢出比malloc溢出要快得多,由於它是以2的指數級來擴大內存池,真正調用malloc的次數約等於log2(1916),而malloc是實實在在進行了1916次調用。因此內存池只用了1秒多就溢出了,而malloc用了69秒。

      內存池溢出的解決方法

        對於malloc形成的內存溢出,通常來講沒有太多辦法可想。基本上就是報一個異常或者錯誤,而後讓用戶關閉程序。固然有的程序會有內存自我管理功能,可讓用戶選擇關閉一切次要功能來維持主要功能的繼續運行。
        而對於內存池的溢出,仍是能夠想一些辦法的,由於畢竟系統內存還有潛力可挖。
        第一個方法是儘可能延緩內存池的溢出,作法是在程序啓動時就儘可能申請最大的內存池,若是在程序運行好久後再申請,可能OS由於內存碎片增多而不能提供最大的內存池。其方法是在程序啓動時就不停的申請內存直到內存池溢出,而後清空內存池並開始正常工做。因爲內存池並不會自動減少,因此這樣能夠一直維持內存池保持最大狀態。
        第二個方法是在內存池溢出時使用第二個內存池,因爲第二個內存池能夠繼續申請較小塊的內存,因此程序可繼續運行。代碼以下:
#include "stdafx.h"
#include <iostream>
#include <ctime>
#include <vector>
#include <boost/pool/pool.hpp>
#include <boost/pool/object_pool.hpp>
using namespace std;
using namespace boost;

int _tmain(int argc, _TCHAR* argv[])
{
    boost::pool<> pl(1024*1024);
    clock_t clock_begin = clock();
    int iLength = 0;
    for (int i = 0; ;++i)
    {
        void* p = pl.malloc();
        if (p == NULL)
        {
            break;
        }
        ++iLength;
    }
    clock_t clock_end = clock();
    cout<<"共申請了"<<iLength<<"M內存,程序運行了"<<clock_end-clock_begin<<" 個系統時鐘"<<endl;

    clock_begin = clock();
    iLength = 0;
    boost::pool<> pl2(1024*1024);
    for (int i = 0; ;++i)
    {
        void* p = pl2.malloc();
        if (p == NULL)
        {
            break;
        }
        ++iLength;
    }
    clock_end = clock();
    cout<<"又申請了"<<iLength<<"M內存,程序運行了"<<clock_end-clock_begin<<" 個系統時鐘"<<endl;

    return 0;
}

  運行結果以下:
image.png

  結果代表在第一個內存池溢出後,第二個內存池又提供了480M的內存。

內存池溢出的終極方案

  若是不管如何都不能再申請到新的內存了,那麼仍是老老實實告訴用戶重啓程序吧。

相關文章
相關標籤/搜索