《電子技術應用》
您所在的位置:首頁 > 嵌入式技術 > 設計應用 > 一種Linux多線程應用下內(nèi)存池的設計與實現(xiàn)
一種Linux多線程應用下內(nèi)存池的設計與實現(xiàn)
來源:電子技術應用2012年第11期
許 健, 于鴻洋
電子科技大學 電子科學技術研究院,四川 成都 611731
摘要: 對內(nèi)存池中內(nèi)存塊獲取、分配機制、內(nèi)存塊大小、內(nèi)存釋放,以及在多線程環(huán)境下的安全處理等細節(jié)進行了研究,保證了在多線程環(huán)境下能夠快速同時采用一種基于數(shù)組的鏈表機制,改進內(nèi)存池中內(nèi)存塊的查找算法,將其時間復雜度穩(wěn)定在O(1),避免了傳統(tǒng)內(nèi)存池中請求的線程數(shù)目過多時,引發(fā)的獲取內(nèi)存塊性能下降的問題。同時在內(nèi)部設置管理線程,動態(tài)增加或刪除空閑的內(nèi)存塊。實驗結(jié)果表明,改進后的內(nèi)存池與傳統(tǒng)的內(nèi)存分配方式相比消耗更小,效率更好。
中圖分類號: TP31
文獻標識碼: A
文章編號: 0258-7998(2012)11-0146-04
Design and implement of memory pool under multi-thread of Linux
Xu Jian,Yu Hongyang
Research Institute of Electronic Science and Technology, University of Electronic Science and Technology, Chengdu 611731, China
Abstract: After much research on the allocate and gain mechanism , dynamic adjustment, safety use, free method, the basic size of the memory block ensure it's can work well in mutli-mem enviorment. Meanwhile, using a mechanism of array-based linked list to improve the searching and allocation algorithm in the memory pool, making the time complexity stable at O(1),and avoid the degradation of allocation and query performance when aquired memory mem number is too large in the traditional mem pool. Experimental results show that the improved memory pool has a smaller cost and better efficiency compared with the traditional memory distribution.
Key words : memory pool; memroy block find method; Linux; multi-thread

    動態(tài)內(nèi)存管理非常耗時,對效率影響很大,然而在實際的編程應用中,卻不可避免地經(jīng)常要用到堆中的內(nèi)存。但是通過Malloc函數(shù)或New等進行的內(nèi)存分配存在先天缺陷: (1)利用默認的內(nèi)存管理函數(shù)在堆上分配和釋放內(nèi)存需要花費很多時間;(2)隨著時間的推移,堆上會形成許多內(nèi)存碎片,在應用程序進行內(nèi)存申請操作將受到更大的影響,導致應用程序的運行越來越慢[1-3]。

    當應用程序需要對固定大小的對象經(jīng)常性地申請內(nèi)存時,常會采用內(nèi)存池(Memory Pool)技術來提高內(nèi)存管理效率。經(jīng)典的內(nèi)存池做法是一次性分配大量大小相同的小塊內(nèi)存,通過該技術可以極大地加快內(nèi)存分配/釋放過程。內(nèi)存池技術通過批量申請內(nèi)存,降低了內(nèi)存申請次數(shù),從而使操作節(jié)省了時間。在減少了內(nèi)存碎片產(chǎn)生的同時,對性能的提升有顯著的幫助。
    綜上,內(nèi)存池有其巨大的優(yōu)勢,但是原有的內(nèi)存池也存在一定的缺陷。在多線程場合下應用時,每個新產(chǎn)生的線程如何在O(1)時間內(nèi)獲取內(nèi)存塊,如何保證其安全有效性,以及如何管理內(nèi)存塊的數(shù)量方面存在一定的不足的,本文對此進行研究,并給出一種新的解決方案。
1 內(nèi)存池制作原理以及工作流程
    本內(nèi)存池基于多線程環(huán)境,需要考慮到多線程下數(shù)據(jù)的安全,以及快速獲取內(nèi)存塊等條件。在獲取內(nèi)存塊索引號時,采用加鎖的方式,雖然會耗費一定的時間,但是運行安全得到了保障。在程序運行之前需創(chuàng)建好內(nèi)存池,并用一個結(jié)構(gòu)體struct mem_pool進行封裝,里面包含內(nèi)存池的一些私有數(shù)據(jù)。當有新線程產(chǎn)生時,直接像內(nèi)存池申請一塊已經(jīng)分配好了的內(nèi)存,線程的具體內(nèi)存操作都在該內(nèi)存塊中進行。同時,內(nèi)存池結(jié)構(gòu)體中隱藏一個管理線程,該線程的工作是定時檢查內(nèi)存池中空閑內(nèi)存塊的數(shù)量是否過多或者過少。當過多時,申請釋放,直到達到門檻值;當過少時,申請增加若干,以備不時之需。內(nèi)存池結(jié)構(gòu)如圖1所示。

    對于內(nèi)存池中的內(nèi)存塊,采用結(jié)構(gòu)struct mem_block維護其數(shù)據(jù),該結(jié)構(gòu)以一個鏈表的形式維護實際內(nèi)存區(qū)域,結(jié)構(gòu)體中有兩個管理內(nèi)存的區(qū)域:(1)常規(guī)大小鏈表區(qū)域。當需要的內(nèi)存小于常規(guī)大小時,則線程的內(nèi)存請求都將從該區(qū)域獲得;當該區(qū)域內(nèi)存將滿時,則線程可以繼續(xù)申請同樣大小的內(nèi)存塊,鏈接到該常規(guī)大小鏈表上。(2)大塊內(nèi)存鏈表區(qū)域。當線程申請的內(nèi)存超過該內(nèi)存塊的大小時,將在系統(tǒng)中申請一塊大的內(nèi)存鏈接到該大塊內(nèi)存鏈表上,這樣可以對內(nèi)存塊進行統(tǒng)一管理;當線程壽命結(jié)束時,調(diào)用reset函數(shù)將大塊內(nèi)存釋放,同時重設常規(guī)內(nèi)存鏈表區(qū)域,只保留最開始的一塊,其余后面申請的塊全部釋放,并標記內(nèi)存塊的使用狀態(tài)為空閑,同時重設一些內(nèi)部指針,指向內(nèi)存塊可用的起始位置[4]。
    創(chuàng)建內(nèi)存池結(jié)構(gòu),并初始化,此時在內(nèi)存中存儲著一份內(nèi)存池的動態(tài)管理單元。當創(chuàng)建新線程時,該線程通過內(nèi)存塊查找函數(shù),并查詢內(nèi)存池結(jié)構(gòu)。如有空閑內(nèi)存塊則直接將該內(nèi)存塊的索引號index送給線程,同時將該內(nèi)存塊的空閑標志flag設為繁忙;如果內(nèi)存池中沒有可用的空閑內(nèi)存塊,且內(nèi)存塊數(shù)量未達到設置的頂峰,則可以申請add_memblock;若正在使用的內(nèi)存塊超過了最大設置的內(nèi)存塊數(shù)量,則線程將調(diào)用Malloc函數(shù),并自行調(diào)用Free釋放該內(nèi)存塊。管理線程周期性地調(diào)用get_mp_status來查看內(nèi)存池狀態(tài),若空閑線程低于門檻值(最小空閑內(nèi)存塊數(shù)),則調(diào)度add_memblock函數(shù)創(chuàng)建新的內(nèi)存塊到池中;若空閑內(nèi)存塊高于門檻值(最大空閑內(nèi)存數(shù)),則調(diào)度del_memblock銷毀多余的內(nèi)存塊。線程生命周期結(jié)束后,將內(nèi)存塊的繁忙標記設置為空閑狀態(tài),并且重新初始化該內(nèi)存塊,將內(nèi)存塊重新投入到內(nèi)存池中,等待其他線程重復利用。內(nèi)存池的工作流程如圖2所示。

2 內(nèi)存池主要技術
2.1 內(nèi)存池中空閑內(nèi)存塊的查找方式

   當進程服務繁忙時,一些內(nèi)存塊長期被某些線程占用的情況下,也可能延長內(nèi)存池響應時間,影響響應速度。內(nèi)存池調(diào)度算法的一項重要任務就是盡可能提高查找空閑內(nèi)存塊的速度。而簡單地遍歷內(nèi)存池鏈表顯然不能滿足請求線程的需要,這種方式不僅延長了調(diào)用者的返回時間,而且極大增加了內(nèi)存池對請求線程的響應時間。特別是在服務器繁忙時,當處于請求內(nèi)存塊的線程越多,查找空閑內(nèi)存塊所花費的時間就越長。因此,本文提出以下兩種查找方法:
    (1) 位圖查找方式
    用位圖方式維護內(nèi)存池中的內(nèi)存塊單元,查找空閑內(nèi)存塊將只需遍歷位圖,位圖按單個字節(jié)進行查找效率較高。另外,在線程結(jié)束時的前一刻,維護當前空閑內(nèi)存塊的索引index,可在下次查找空閑內(nèi)存塊時直接獲取這個值,而時間耗費是O(1)級的,這將大大提高響應時間。
    (2) 基于數(shù)組的方式
    基于鏈表實現(xiàn)的內(nèi)存池,在創(chuàng)建內(nèi)存池時或者每次增加池中內(nèi)存塊數(shù)時都必須分配新的管理結(jié)構(gòu),再進行鏈接;并且在查找空閑內(nèi)存塊時,需要遍歷內(nèi)存池,其直接后果是造成線程請求內(nèi)存塊的時間大大增加。而數(shù)組的方式有其天然的優(yōu)勢,當用位圖查找到了空閑內(nèi)存塊的索引后,也即知道了內(nèi)存塊在數(shù)組中的位置,由此可以迅速地定位空閑內(nèi)存塊,大大提高了響應速度。
2.2 內(nèi)存池中內(nèi)存塊的數(shù)量動態(tài)調(diào)整
    固定的內(nèi)存池在有些情況下并不能滿足實際情況的需要,動態(tài)內(nèi)存池常見的調(diào)整方法有基于閾值觸發(fā)和基于預測公式兩種形式?;陬A測公式方法通過統(tǒng)計學的經(jīng)驗公式來預測,優(yōu)點是能夠反應內(nèi)存池消耗內(nèi)存的真實傾向,迅速查找和釋放內(nèi)存塊;缺點是按照統(tǒng)計公式計算的結(jié)果,通常局限于某些特定場合和應用,并且在內(nèi)存池中造成系統(tǒng)資源消耗較大。基于閾值觸發(fā)方法通常按照參數(shù)配置來控制內(nèi)存池的某些參數(shù),優(yōu)點是實現(xiàn)簡單、通用性強、可控性好;缺點是需要精確計算配置參數(shù),否則性能會急劇下降。為保證內(nèi)存池的通用性,這里使用參數(shù)可調(diào)整的閾值觸發(fā)方式動態(tài)調(diào)整內(nèi)存池。
    (1) 相關參數(shù)合理性設置
    設內(nèi)存池中最大內(nèi)存塊數(shù)為MAX_NUM,內(nèi)存池中最小內(nèi)存塊數(shù)為MIN_NUM,內(nèi)存池中最小空閑內(nèi)存塊數(shù)為MIN_IDLE,內(nèi)存池最大空閑內(nèi)存塊數(shù)為MAX_IDLE,方法是:
    ①初始化創(chuàng)建MIN_NUM個空閑內(nèi)存塊;
    ②池中空閑內(nèi)存塊數(shù)量低于MIN_IDLE時,觸發(fā)內(nèi)存池調(diào)整,添加MIN_IDLE個內(nèi)存塊;
  ③池中空閑內(nèi)存塊數(shù)量高于MAX_IDLE時,觸發(fā)內(nèi)存發(fā)池調(diào)整,刪除MIN_IDLE個內(nèi)存塊
    ④調(diào)整過程確保內(nèi)存池中內(nèi)存塊數(shù)不多于MAX_NUM個,也不少于MIN_NUM個。
    對以上參數(shù)的合理設置可以保證內(nèi)存池能動態(tài)地處理內(nèi)存塊過多或過少時的情況,同時在處理大量請求時,避免請求線程等待太久。
    (2) 設置內(nèi)存池模式
    內(nèi)存池的工作模式能夠影響的調(diào)整行為:
    ①可增可減模式:內(nèi)存池處于動態(tài)管理狀態(tài),實時調(diào)整內(nèi)存塊的數(shù)量,在條件允許的情況下增加或刪除空閑內(nèi)存塊。
    ②只增不減模式:內(nèi)存池處于動態(tài)管理狀態(tài),內(nèi)存池只會做出添加內(nèi)存塊的調(diào)整行為,不會做出刪減內(nèi)存塊的調(diào)整。
    ③不增不減模式:內(nèi)存池處于動態(tài)管理狀態(tài),既不增加內(nèi)存塊,也不刪除內(nèi)存塊。
    對內(nèi)存池模式的設置應盡可能多地滿足不同的應用場合,使內(nèi)存池具有更好的適應性和通用性。相對于其他兩種模式來說,可增可減模式適應性較強,既不浪費系統(tǒng)資源,又可提供良好的服務。
2.3 內(nèi)存池中內(nèi)存塊組織結(jié)構(gòu)的調(diào)整
   將內(nèi)存塊大小固定的內(nèi)存池在使用時將遇到諸多不便。不同的任務線程對于內(nèi)存大小的需求不一樣,對于一般的服務,可能線程所需要的內(nèi)存塊只在幾十~幾百字節(jié)之間,但對于另外一些服務,線程將需要幾千甚至幾兆的內(nèi)存來處理數(shù)據(jù)。因此,合適的內(nèi)存塊的大小將影響請求線程的效率。內(nèi)存塊組織結(jié)構(gòu)如圖3所示。

3 代碼組織
   借鑒C++語言中的面向?qū)ο蟮乃枷?,在C語言中利用結(jié)構(gòu)體模擬C++語言中的類,用函數(shù)指針模擬類方法,通過指針強制轉(zhuǎn)換實現(xiàn)數(shù)據(jù)隱藏。頭文件.h中包含數(shù)據(jù)結(jié)構(gòu),而.c文件中包含實際的內(nèi)存池結(jié)構(gòu),這樣可避免用戶操作結(jié)構(gòu)體中的數(shù)據(jù)成員雖然不能真正地像C++中隱藏數(shù)據(jù),但是也達到了一定的隱藏效果[5-6]。
3.1 內(nèi)存池使用方法
    mp_mem_pool *pool = create_mem_pool();
    pool->init(pool, NULL,“log.txt”);
    pool->find_min_idle_index(pool);
    pool->palloc(pool, index, size);
    destroy_mem_pool( pool);
3.2 函數(shù)與接口的功能
       struct mp_mem_pool_s{
       MPBOOL    (*init)(mp_mem_pool *pool, mp_mem_conf *conf, const char *log_file);
       void    (*reset)(mp_mem_pool *pool);
       void(*reset_memblock)(mp_mem_pool *pool, const int index);
     void( *get_mp_status )( mp_mem_pool *pool);
     void(*print_mp_status)(mp_mem_pool *pool);
       int(*find_min_idle_index)(mp_mem_pool *pool);
       void    *(*palloc)(mp_mem_pool *pool, const int index,  size_t size);
       void    (*pnalloc)(mp_mem_pool *pool, const int index, size_t size);
       void    (*pcalloc)(mp_mem_pool *pool, const int index,  size_t size);
       void    (*pmemalign)(mp_mem_pool *pool, const int index, size_t size, size_t alignment);
       mp_mem_pool *create_mem_pool();
       void destroy_mem_pool( mp_mem_pool* pool );
    函數(shù)用戶接口較為簡單,主要為創(chuàng)建和銷毀內(nèi)存池的接口,以及查找池中空閑內(nèi)存塊索引。內(nèi)存池本身也有自己的接口struct mp_mem_pool_s,只有類似C++中的成員函數(shù)沒有數(shù)據(jù),所有數(shù)據(jù)都在實現(xiàn)文件中進行處理,這樣隱藏數(shù)據(jù)的好處是避免用戶隨意操作內(nèi)存池管理單元中的數(shù)據(jù)。
    create_mem_pool:    創(chuàng)建內(nèi)存池;
    destroy_mem_pool:    銷毀內(nèi)存池;
    init: 初始化內(nèi)存池(沒有初始化是無法使用的,可以根據(jù)配置文件調(diào)節(jié)內(nèi)存池行為);
     reset:關閉內(nèi)存池,將其退回到創(chuàng)建時的狀態(tài);
       reset_memblock:重置具體的內(nèi)存塊,將其退回到初始化時的狀態(tài);
       get_mp_status:獲取內(nèi)存池狀態(tài)(當前的內(nèi)存塊數(shù)量,最大內(nèi)存塊數(shù),以及空閑內(nèi)存塊數(shù)量等);
       print_mp_status:打印內(nèi)存池的工作狀態(tài)到終端上顯示;
       find_min_idle_index:返回內(nèi)存池中空閑內(nèi)存塊的索引;
       palloc:請求線程申請到內(nèi)存塊之后,調(diào)用該函數(shù)進行內(nèi)存分配操作,分配時進行對齊處理;
       pnalloc:請求線程申請到內(nèi)存塊之后,調(diào)用該函數(shù)進行內(nèi)存分配操作,分配時不進行對齊處理;
       pcalloc:請求線程申請到內(nèi)存塊之后,調(diào)用該函數(shù)進行內(nèi)存分配操作,分配時進行對齊處理,同時將內(nèi)存清零;
       pmemalign:分配大塊內(nèi)存的操作函數(shù)。
     實際的應用中內(nèi)存池通常都是與線程池、以及任務池結(jié)合在一起使用,但各個模塊之間耦合越緊,模塊的重用就會越困難,移植性越低。因此內(nèi)存池的接口應盡可能地保持其獨立性,不依賴外部條件。而內(nèi)存池的使用者只需要做初期初始化工作,將描述內(nèi)存池的結(jié)構(gòu)體作為全局變量,然后在線程的工作函數(shù)中調(diào)用find_min_idle_index尋找到可用內(nèi)存塊索引即可,操作簡單方便[6-8]。
4 比較與測試
    (1) 測試環(huán)境
    Intel(R) Core(TM) i3-2100 CPU @ 2.80 GHz,2 GB內(nèi)存;Fedora 14(內(nèi)核2.6.35.14-106.fc14.i686,GCC 4.5.1,GLIBC 2.12.90)
    (2) 測試設計
    內(nèi)存池的使用相比線程中直接調(diào)用Malloc、Free函數(shù)分配和銷毀內(nèi)存的優(yōu)勢,主要體現(xiàn)在一次性申請連續(xù)N塊內(nèi)存,并且在程序結(jié)束后統(tǒng)一釋放。而多線程環(huán)境中每個線程單獨調(diào)用Malloc、Free則需要大量的系統(tǒng)調(diào)用開銷,同時,將可能產(chǎn)生許多內(nèi)存碎片。而內(nèi)存池的使用能夠節(jié)省Malloc、Free不斷地調(diào)用時間,避免了可能出現(xiàn)的內(nèi)存碎片,從而保證內(nèi)存池的使用有利于復用和管理。
    針對本測試所設計的測試程序為,在使用內(nèi)存池環(huán)境下,主線程先創(chuàng)建并初始化內(nèi)存池,內(nèi)存池中每個Memblock的大小設為1 KB,內(nèi)存池的配置文件中設置最大內(nèi)存塊數(shù)量為201,最小內(nèi)存塊數(shù)量為30,最大空閑內(nèi)存塊數(shù)量為60,最小空閑內(nèi)存塊數(shù)量為10。之后主線程產(chǎn)生200個線程,所有線程的工作就是向內(nèi)存池申請內(nèi)存塊,之后再在申請到的Memblock中分別分配128 B、1 KB、 2 KB,以及同時申請128 B和1 KB,然后用time命令來計算user time和sys time。
    在不使用內(nèi)存池情況下,每個線程將單獨調(diào)用malloc和free函數(shù)來分配和釋放內(nèi)存,同樣分別分配128 B,1 KB,2 KB以及同時申請128 B和1 KB內(nèi)存。需要注意的是,為了避免客觀因素影響,兩個測試程序中的其余部分應盡量一致。每種情況進行100次測試,平均得到的測試結(jié)果如表1所示。
    (3) 結(jié)果分析
    由表1可見,在不使用內(nèi)存池的測試中,當一個線程中多次分配以及釋放時,將消耗更多的時間。而使用內(nèi)存池的結(jié)果還是比較理想的。每個線程分配內(nèi)存的大小對于用戶時間和系統(tǒng)時間幾乎毫無影響,它不需要Malloc和Free不斷地操作,節(jié)約了大量庫函數(shù)調(diào)用、系統(tǒng)調(diào)用的開銷,減少了內(nèi)存碎片,特別對于服務器程序的運行,是非??捎^的。

 

 

    本文設計一種在多線程環(huán)境下的內(nèi)存池算法,優(yōu)化了池中內(nèi)存塊的維護和查找算法,并保證了接口的簡單易用性,使其更易于與項目集成。
參考文獻
[1] 翁小東. 電信級用系統(tǒng)中多進程共享內(nèi)存池的實現(xiàn)[J].電腦知識技術,2009,4(5-12):3300-3306
[2] 劉小華. 基于C++的內(nèi)存池的實現(xiàn)[J].福建電腦, 2008(1):82-83.
[3] 張海闊,趙沖沖,王玉,等. 鏈表快速查找的內(nèi)存池管理優(yōu)化技術研究[C]. 2007年全國高性能計算學術年會, 2007.
[4] 胡萌,趙衛(wèi)東,王志成,等. 線程池設計與動態(tài)優(yōu)化[J]. 電腦知識與技術,2008,4(9):2753-2755.
[5] STEVENS W R. UNIX網(wǎng)路編程(第2卷)[M]. 北京:人民郵電出版社,2010.
[6] 趙海,李志蜀,韓學為,等. 一種鏈式結(jié)構(gòu)在內(nèi)存管理中的應用[J].高等函授學報:自然科學版,2002,15(4):46-48.
[7] 翁小東.基于UNIX C語言的一種線程池實現(xiàn)[J]. 電腦知識與技術,2009,5(16):4222-4223.
[8] LOWELL R M. A C++ pooled, shared memory allocator for simulator development[C]. IEEE,2004,Proceedings of the 37th Annual Simulation Symposium,2004.

此內(nèi)容為AET網(wǎng)站原創(chuàng),未經(jīng)授權(quán)禁止轉(zhuǎn)載。