C++性能優化(十) —— JeMalloc

C++性能優化(十) —— JeMalloc

1、JeMalloc簡介

一、JeMalloc簡介

JeMalloc 是一款內存分配器,最大的優勢在於多線程狀況下的高性能以及內存碎片的減小。
GitHub地址:
https://github.com/jemalloc/jemallocnode

二、JeMalloc安裝

JeMalloc源碼下載:
git clone https://github.com/jemalloc/jemalloc.git
構建工具生成:
autogen.sh
編譯選項配置:
configure
編譯:
make -j4
安裝:
make installgit

2、JeMalloc架構

一、JeMalloc架構簡介

C++性能優化(十) —— JeMalloc
JeMalloc將內存分紅多個相同大小的chunk,數據存儲在chunks中;每一個chunk分爲多個run,run負責請求、分配相應大小的內存並記錄空閒和使用的regions的大小。
C++性能優化(十) —— JeMallocgithub

二、Arena

Arena是JeMalloc的核心分配管理區域,對於多核系統,會默認分配4x邏輯CPU的Arena,線程採起輪詢的方式來選擇相應的Arena來進行內存分配。
每一個arena內都會包含對應的管理信息,記錄arena的分配狀況。arena都有專屬的chunks, 每一個chunk的頭部都記錄chunk的分配信息。在使用某一個chunk的時候,會把chunk分割成多個run,並記錄到bin中。不一樣size class的run屬於不一樣的bin,bin內部使用紅黑樹來維護空閒的run,run內部使用bitmap來記錄分配狀態。
JeMalloc使用Buddy allocation 和 Slab allocation 組合做爲內存分配算法,使用Buddy allocation將Chunk劃分爲不一樣大小的 run,使用 Slab allocation 將run劃分爲固定大小的 region,大部份內存分配直接查找對應的 run,從中分配空閒的 region,釋放則標記region爲空閒。
run被釋放後會和空閒的、相鄰的run進行合併;當合併爲整個 chunk 時,若發現有相鄰的空閒 chunk,也會進行合併。算法

三、Chunk

Chunk是JeMalloc進行內存分配的單位,默認大小4MB。Chunk以Page(默認爲4KB)爲單位進行管理,每一個Chunk的前6個Page用於存儲後面其它Page的狀態,好比是否待分配仍是已經分配;然後面其它Page則用於進行實際的分配。數組

四、Bin

JeMalloc 中 small size classes 使用 slab 算法分配,會有多種不一樣大小的run,相同大小的run由bin 進行管理。
run是分配的執行者, 而分配的調度者是bin,bin負責記錄當前arena中某一個size class範圍內全部non-full run的使用狀況。當有分配請求時,arena查找相應size class的bin,找出可用於分配的run,再由run分配region。因爲只有small region分配須要run,所以bin也只對應small size class。
在arena中, 不一樣bin管理不一樣size大小的run,在任意時刻, bin中會針對當前size保存一個run用於內存分配。緩存

五、Run

Run是chunk的一塊內存區域,大小是Page的整數倍,由bin進行管理,好比8字節的bin對應的run就只有1個page,能夠從裏面選取一個8字節的塊進行分配。
small classes 從 run 中使用 slab 算法分配,每一個 run 對應一塊連續的內存,大小爲 page size 倍數,劃分爲相同大小的 region,分配時從run 中分配一個空閒 region,釋放時標記region爲空閒,重複使用。
run中採用bitmap記錄分配區域的狀態,bitmap可以快速計算出第一塊空閒區域,且能很好的保證已分配區域的緊湊型。性能優化

六、TCache

TCache是線程的私有緩存空間,在分配內存時首先從tcache中分配,避免加鎖;當TCache沒有空閒空間時纔會進入通常的分配流程。
每一個TCache內部有一個arena,arena內部包含tbin數組來緩存不一樣大小的內存塊,但沒有run。多線程

3、JeMalloc內存分配

一、JeMalloc內存分配

JeMalloc基於申請內存的大小把內存分配分爲三個等級:small、large、huge。
Small objects的size以8字節、16字節、32字節等分隔開的,小於Page大小。
Large objects的size以Page爲單位, 等差間隔排列,小於chunk(4MB)的大小。
Huge objects的大小是chunk大小的整數倍。
C++性能優化(十) —— JeMalloc
JeMalloc經過將內存劃分紅大小相同的chunk進行管理,chunk的大小爲2的k次方,大於Page大小。Chunk起始地址與chunk大小的整數倍對齊,能夠經過指針操做在常量時間內找到分配small/large objects的元數據,在對數時間內定位到分配huge objects的元數據。爲了得到更好的線程擴展性,JeMalloc採用多個arenas來管理內存,減小了多線程間的鎖競爭。每一個線程獨立管理本身的內存arena,負責small和large的內存分配,線程按第一次分配small或者large內存請求的順序Round-Robin地選擇arena。從某個arena分配的內存塊,在釋放時必定會回到原arena。JeMalloc引入線程緩存來解決線程間的同步問題,經過對small和large對象的緩存,實現一般狀況下內存的快速申請和釋放。
C++性能優化(十) —— JeMalloc架構

二、small內存分配

若是請求內存size不大於arena的最小的bin,那麼經過線程對應的tcache來進行分配。
small objects分配流程以下:
(1)查找對應 size classes 的 bin
(2)從 bin 中獲取未滿的run。
(3)從 arena 中獲取空閒run。
(4)從 run 中返回一個空閒 region。ide

三、large內存分配

若是請求內存size大於arena的最小的bin,同時不大於tcache能緩存的最大塊,也會經過線程對應的tcache來進行分配,但方式不一樣。
若是tcache對應的tbin裏有緩存塊,直接分配;若是沒有,從chunk裏直接找一塊相應的page整數倍大小的空間進行分配;

四、Huge內存分配

若是請求分配內存大於chunk(4MB)大小,直接經過mmap進行分配。

4、多線程支持

一、JeMalloc多線程支持

JeMalloc對於多線程內存分配與單線程相同,每一個線程從 Arena 中分配內存,但多線程間須要同步和競爭,所以提升多線程內存分配性能方法以下:
(1)減小鎖競爭。縮小臨界區,使用更細粒度鎖。
(2)避免鎖競爭。線程間不共享數據,使用局部變量、線程特有數據(tsd)、線程局部存儲(tls)等。

二、Arena選擇

JeMalloc會建立多個Arena,每一個線程由一個Arena 負責。JeMalloc默認建立4x邏輯CPU個Arena。
arena->nthreads 記錄負責的線程數量。
每一個線程分配時會首先調用arena_choose選擇一個arena來負責線程的內存分配。線程選擇 arena 的邏輯以下:
(1)若是有空閒的(nthreads==0)已建立arena,則選擇空閒arena。
(2)若還有未建立的arena,則選擇新建立一個arena。
(3)選擇負載最低的arena (nthreads 最小)。

三、線程鎖

線程鎖儘可能使用 spinlock,減小線程間的上下文切換。Linux操做系統能夠在編譯時經過定義JEMALLOC_OSSPIN宏能夠指定使用自選鎖。
爲了縮小臨界區,arena 中提供多個細粒度鎖管理不一樣部分:
(1)arenas_lock: arena 的初始化、分配等
(2)arena->lock: run 和 chunk 的管理
(3)arena->huge_mtx: huge object 的管理
(4)bin->lock: bin 中的操做

四、tsd

當選擇完arena後,會將arena綁定到tsd中,直接從tsd中獲取arena。
tsd用於保存每一個線程本地數據,主要arena和tcache,避免鎖競爭。tsd_t中的數據會在第一次訪問時延遲初始化,tsd 中各元素使用宏生成對應的 get/set 函數來獲取/設置,在線程退出時,會調用相應的 cleanup 函數清理。

五、tcache

tcache 用於 small object和 large object的分配,避免多線程同步。
tcache 使用slab內存分配算法分配內存:
(1)tcache中有多種bin,每一個bin管理一個size class。
(2)當分配時,從對應bin中返回一個cache slot。
(3)當釋放時,將cache slot返回給對應的bin。

六、線程退出

線程退出時,會調用 tsd_cleanup() 對 tsd 中數據進行清理:
(1)arena,下降arena負載(arena->nthreads--)
(2)tcache,調用tcache_bin_flush_small/large釋放 tcache->tbins[]全部元素,釋放tcache。
當從一個線程分配的內存由另外一個線程釋放時,內存仍是由原先arena來管理,經過chunk的extent_node_t來獲取對應的arena。

5、JeMalloc使用指南

一、JeMalloc庫簡介

JeMalloc提供了靜態庫libjemalloc.a和動態庫libjemalloc.so,默認安裝在/usr/local/lib目錄。

二、JeMalloc動態方式

經過-ljemalloc將JeMalloc連接到應用程序。
經過LD_PRELOAD預載入JeMalloc庫能夠不用從新編譯應用程序便可使用JeMalloc。
LD_PRELOAD="/usr/lib/libjemalloc.so"

三、JeMalloc靜態方式

在編譯選項的最後加入/usr/local/lib/libjemalloc.a連接靜態庫。

四、JeMalloc生效

jemalloc利用malloc的hook來對代碼中的malloc進行替換。

JEMALLOC_EXPORT void (*__free_hook)(void *ptr) = je_free;
JEMALLOC_EXPORT void *(*__malloc_hook)(size_t size) = je_malloc;
JEMALLOC_EXPORT void *(*__realloc_hook)(void *ptr, size_t size) = je_realloc;

五、JeMalloc測試

malloc.cpp:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <time.h>

#define MAX_OBJECT_NUMBER       (1024)
#define MAX_MEMORY_SIZE         (1024*100)

struct BufferUnit{
   int   size;
   char* data;
};

struct BufferUnit   buffer_units[MAX_OBJECT_NUMBER];

void MallocBuffer(int buffer_size) {

for(int i=0; i<MAX_OBJECT_NUMBER; ++i)  {
    if (NULL != buffer_units[i].data)   continue;

    buffer_units[i].data = (char*)malloc(buffer_size);
    if (NULL == buffer_units[i].data)  continue;

    memset(buffer_units[i].data, 0x01, buffer_size);
    buffer_units[i].size = buffer_size;
    }
}

void FreeHalfBuffer(bool left_half_flag) {
    int half_index = MAX_OBJECT_NUMBER / 2;
    int min_index = 0;
    int max_index = MAX_OBJECT_NUMBER-1;
    if  (left_half_flag)
        max_index =  half_index;
    else
        min_index = half_index;

    for(int i=min_index; i<=max_index; ++i) {
        if (NULL == buffer_units[i].data) continue;

        free(buffer_units[i].data);
        buffer_units[i].data =  NULL;
        buffer_units[i].size = 0;
    }
}

int main() {
    memset(&buffer_units, 0x00, sizeof(buffer_units));
    int decrease_buffer_size = MAX_MEMORY_SIZE;
    bool left_half_flag   =   false;
    time_t  start_time = time(0);
    while(1)  {
        MallocBuffer(decrease_buffer_size);
        FreeHalfBuffer(left_half_flag);
        left_half_flag = !left_half_flag;
        --decrease_buffer_size;
        if (0 == decrease_buffer_size) break;
    }
    FreeHalfBuffer(left_half_flag);
    time_t end_time = time(0);
    long elapsed_time = difftime(end_time, start_time);

    printf("Used %ld seconds. \n", elapsed_time);
    return 1;
}

使用TCMalloc編譯連接:
g++ malloc.cpp -o test -ljemalloc
執行test,耗時558秒。
使用默認GLibc編譯連接:
g++ malloc.cpp -o test執行test,耗時744秒。

相關文章
相關標籤/搜索