1. 程式人生 > >Redis原始碼分析-記憶體分配

Redis原始碼分析-記憶體分配

本文轉載自Day Day Up部落格,文章對Redis記憶體分配封裝庫進行了分析,描述了Redis在記憶體分配和使用統計方面的各種細節和技巧。

Redis中到處都會進行記憶體分配操作。為了遮蔽不同平臺之間的差異,以及統計記憶體佔用量等,Redis對記憶體分配函式進行了一層封裝,程式中統一使用zmalloc,zfree一系列函式,位於zmalloc.h,zmalloc.c文中。

上邊說過,封裝就是為了遮蔽底層平臺的差異,同時方便自己實現相關的統計函式。具體來說就是:

  • 若系統中存在Google的TC_MALLOC庫,則使用tc_malloc一族函式代替原本的malloc一族函式。
  • 若當前系統是Mac系統,則使用<malloc/malloc.h>中的記憶體分配函式。
  • 其他情況,在每一段分配好的空間前頭,同時多分配一個定長的欄位,用來記錄分配的空間大小。

原始碼分別在 config.h 和 zmalloc.c 中:

/* config.h */
#if defined(USE_TCMALLOC)
#include <google/tcmalloc.h>
#if TC_VERSION_MAJOR >= 1 && TC_VERSION_MINOR >= 6
#define HAVE_MALLOC_SIZE 1
#define redis_malloc_size(p) tc_malloc_size(p)
#endif
#elif defined(__APPLE__)
#include <malloc/malloc.h>
#define HAVE_MALLOC_SIZE 1
#define redis_malloc_size(p) malloc_size(p)
#endif
/* zmalloc.c */
#ifdef HAVE_MALLOC_SIZE
#define PREFIX_SIZE (0)
#else
#if defined(__sun)
#define PREFIX_SIZE (sizeof(long long))
#else
#define PREFIX_SIZE (sizeof(size_t))
#endif
#endif

因為 tc_malloc 和 Mac平臺下的 malloc 函式族提供了計算已分配空間大小的函式(分別是tc_malloc_size和malloc_size),所以就不需要單獨分配一段空間記錄大小了。而針對linux和sun平臺則要記錄分配空間大小。對於linux,使用sizeof(size_t)定長欄位記錄;對於sun os,使用sizeof(long long)定長欄位記錄。也就是上邊原始碼中的 PREFIX_SIZE 巨集。

那麼這個記錄有什麼用呢?答案是,為了統計當前程序到底佔用了多少記憶體。在 zmalloc.c 中,有這樣一個靜態變數:

static size_t used_memory = 0;

它記錄了程序當前佔用的記憶體總數。每當要分配記憶體或是釋放記憶體的時候,都要更新這個變數。因為分配記憶體的時候,可以明確知道要分配多少記憶體。但是釋放記憶體的時候,(對於未提供malloc_size函式的平臺)僅通過指向要釋放記憶體的指標是不能知道釋放的空間到底有多大的。這個時候,上邊提到的PREFIX_SIZE定長欄位就起作用了,可以通過其中記錄的內容得到空間的大小。zmalloc函式如下(去掉無關程式碼):

void *zmalloc(size_t size) {
    void *ptr = malloc(size+PREFIX_SIZE);

    if (!ptr) zmalloc_oom(size);
    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE,size);
    return (char*)ptr+PREFIX_SIZE;
#endif
}

看到在分配空間的時候,空間大小是size+PREFIX_SIZE。對於mac系統或是使用tc_malloc的情況,PREFIX_SIZE 為0。之後將ptr指標指向的空間前size_t中記錄分配空間的大小。最後返回的是越過記錄區的指標。zfree函式類似(去掉無關程式碼):

void zfree(void *ptr) {
    void *realptr;
    size_t oldsize;

    if (ptr == NULL) return;
    realptr = (char*)ptr-PREFIX_SIZE;
    oldsize = *((size_t*)realptr);
    update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
    free(realptr);
#endif
}

先將指標向前移動PREFIX_SIZE,然後取出分配空間時儲存的空間長度。最後free整個空間。

update_zmalloc_stat_alloc(__n,__size) 和 update_zmalloc_stat_free(__n) 這兩個巨集負責在分配記憶體或是釋放記憶體的時候更新used_memory變數。定義成巨集主要是出於效率上的考慮。將其還原為函式,就是下邊這個樣子:

void update_zmalloc_stat_alloc(__n,__size)
{
    do {
        size_t _n = (__n);
        size_t _stat_slot = (__size < ZMALLOC_MAX_ALLOC_STAT) ? __size : ZMALLOC_MAX_ALLOC_STAT;
        if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1));
        if (zmalloc_thread_safe) {
            pthread_mutex_lock(&used_memory_mutex);
            used_memory += _n;
            zmalloc_allocations[_stat_slot]++;
            pthread_mutex_unlock(&used_memory_mutex);
        } else {
            used_memory += _n;
            zmalloc_allocations[_stat_slot]++;
        }
    } while(0)
}

void update_zmalloc_stat_free(__n)
{
    do {
        size_t _n = (__n);
        if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1));
        if (zmalloc_thread_safe) {
            pthread_mutex_lock(&used_memory_mutex);
            used_memory -= _n;
            pthread_mutex_unlock(&used_memory_mutex);
        } else {
            used_memory -= _n;
        }
    } while(0)
}

程式碼中除了更新used_memory變數外,還有幾個要關注的地方:

  1. 先對_n的低位向上取整,最後_n變為sizeof(long)的倍數,比如對於32位系統,sizeof(long) == 100(二進位制),_n向上取整之後,低兩位都變為0。
  2. 如果程序中有多個執行緒存在,則在更新變數的時候要加鎖。
  3. 在zmalloc函式中還有一個統計量要更新:zmalloc_allocations[]。

在 zmalloc.c 中,zmalloc_allocations是這樣定義的:

size_t zmalloc_allocations[ZMALLOC_MAX_ALLOC_STAT+1];

其作用是統計程式分配記憶體時,對不同大小空間的請求次數。統計的空間範圍從1位元組到256位元組,大於256位元組的算為256。統計結果通過呼叫 zmalloc_allocations_for_size 函式返回:

size_t zmalloc_allocations_for_size(size_t size) {
    if (size > ZMALLOC_MAX_ALLOC_STAT) return 0;
    return zmalloc_allocations[size];
}

另一個對記憶體使用量的統計通過呼叫 zmalloc_used_memory 函式返回:

size_t zmalloc_used_memory(void) {
    size_t um;

    if (zmalloc_thread_safe) pthread_mutex_lock(&used_memory_mutex);
    um = used_memory;
    if (zmalloc_thread_safe) pthread_mutex_unlock(&used_memory_mutex);
    return um;
}

另外 zmalloc.c 中,還針對不同的系統實現了 zmalloc_get_rss 函式,在linux系統中是通過讀取/proc/$pid/stat檔案獲得系統統計的記憶體佔用量。關於/proc虛擬檔案系統可以看之前的文章

相關推薦

Redis原始碼分析-記憶體分配

本文轉載自Day Day Up部落格,文章對Redis的記憶體分配封裝庫進行了分析,描述了Redis在記憶體分配和使用統計方面的各種細節和技巧。 Redis中到處都會進行記憶體分配操作。為了遮蔽不同平臺之間的差異,以及統計記憶體佔用量等,Redis對記憶體分配函式進

Libevent原始碼分析-----記憶體分配

    Libevent的記憶體分配函式還是比較簡單的,並沒有定義記憶體池之類的東西。如同前一篇部落格那樣,給予Libevent庫的使用者充分的設定權(定製),即可以設定使用者(Libevent庫的使用者)自己的記憶體分配函式。至於怎麼分配,主動權在於使用者。但在設定(定製)

python原始碼分析----記憶體分配(2)

早就應該寫部分的內容了。。。。最近比較負能量。。。傷不起啊。。 上一篇說到了,在python的記憶體分配中兩個非常重要的方法:PyObject_Malloc和PyObject_Free 在具體的來這兩個方法之前,先要看看別的一些東西 //這裡用usedpool構成了一個雙

Netty學習之旅----原始碼分析記憶體分配與釋放原理

static PooledHeapByteBuf newInstance(int maxCapacity) { PooledHeapByteBuf buf = RECYCLER.get(); buf.setRefCnt(1); buf.maxCapacity(m

死磕Netty原始碼記憶體分配詳解(二)PoolArena記憶體分配結構分析

前言 在應用層通過設定PooledByteBufAllocator來執行ByteBuf的分配,但是最終的記憶體分配工作被委託給PoolArena。由於Netty通常用於高併發系統所以各個執行緒進行記憶體分配時競爭不可避免,這可能會極大的影響記憶體分配的效率,為

Redis原始碼分析-基礎功能(記憶體

Redis簡介: Redis是一個高效能key-value儲存系統,有以下幾個優點: 1.支援資料持久化,可以將記憶體中的資料儲存到磁碟中,重啟的時候重新載入使用。 2.Redis支援簡單的字串的key-value ,並且支援比較複雜的儲存結構,例如list,set,zse

63.ImageLoader原始碼分析-記憶體快取演算法

一. 前言 圖片記憶體快取可以提高圖片顯示速度,但是有些問題,比如佔用記憶體,如果不加以控制,甚至可能會OOM 所以,需要提供各種各樣的演算法來控制記憶體的使用,以適應不同的使用場景,目前,ImageLoader提供了若干記憶體管理演算法。 預設記憶體快取是關閉的,需要手動開啟 二. 繼承關係圖

redis原始碼分析與思考(十九)——AOF持久化

    為了解決持久化檔案很龐大以及會阻塞伺服器的 情況,redis提出一種新的持久化方案:AOF持久化。AOF持久化是redis儲存資料的另外一種方式,全稱Append Only File,與RDB持久化不同的是,AOF持久化是隻儲存從客戶端鍵入

redis原始碼分析與思考(十八)——RDB持久化

    redis是一個鍵值對的資料庫伺服器,伺服器中包含著若干個非空的資料庫,每個非空資料庫裡又包含著若干個鍵值對。因為redis是一個基於記憶體存貯的資料庫,他將自己所存的資料存於記憶體中,如果不將這些資料及時的儲存在硬碟中,當電腦關機或者進行

redis原始碼分析與思考(十七)——有序集合型別的命令實現(t_zset.c)

    有序集合是集合的延伸,它儲存著集合元素的不可重複性,但不同的是,它是有序的,它利用每一個元素的分數來作為有序集合的排序依據,現在列出有序集合的命令: 有序集合命令 命令 對應操作 時

redis原始碼分析與思考(十六)——集合型別的命令實現(t_set.c)

    集合型別是用來儲存多個字串的,與列表型別不一樣,集合中不允許有重複的元素,也不能以索引的方式來通過下標獲取值,集合中的元素還是無序的。在普通的集合上增刪查改外,集合型別還實現了多個集合的取交集、並集、差集,集合的命令如下表所示: 集合命

redis原始碼分析與思考(十五)——雜湊型別的命令實現(t_hash.c)

    雜湊型別又叫做字典,在redis中,雜湊型別本身是一個鍵值對,而雜湊型別裡面也存貯著鍵值對,其對應關係是,每個雜湊型別的值對應著一個鍵值對或多對鍵值對,如圖所示: 雜湊型別命令 命令 對應操

redis原始碼分析與思考(十四)——列表型別的命令實現(t_list.c)

    列表型別是用來存貯多個字串物件的結構。一個列表可以存貯232-1個元素,可以對列表兩端進行插入(push)、彈出(pop),還可以獲取指定範圍內的元素列表、獲取指定索引的元素等等,它可以靈活的充當棧和佇列的角色。下面列出列表的命令: 列

redis原始碼分析與思考(十三)——字串型別的命令實現(t_string.c)

    在對字串操作的命令中,主要有增加刪查該、批處理操作以及編碼的轉換命令,現在列出對字串物件操作的主要常用命令: 常用命令表 命令 對應操作 時間複雜度

Redis原始碼剖析--記憶體管理zmalloc

功能函式總覽 在zmalloc.h中,定義了Redis記憶體分配的主要功能函式,這些函式基本上實現了Redis記憶體申請,釋放和統計等功能,其函式宣告如下: void *zmalloc(size_t size); /

redis原始碼分析1------dict的實現

1. 總體結構 redis的dict就是hash表,使用鏈式結構來解決key值衝突,典型的資料結構 結構體的定義如下: typedef struct dictEntry { void *key; union { void *val; uint64_t

redis原始碼分析與思考(三)——字典中鍵的兩種hash演算法

      在Redis字典中,得到鍵的hash值顯得尤為重要,因為這個不僅關乎到是否字典能做到負載均衡,以及在效能上優勢是否突出,一個良好的hash演算法在此時就能發揮出巨大的作用。而一個良好的has

redis原始碼分析之intset.c

#include <stdio.h> #include <stdlib.h> #include <string.h> #include "intset.h" #include "zmalloc.h" #include "end

redis原始碼分析之zipmap.c

/** * 建立空zipmap * @return */ /* Create a new empty zipmap. */ unsigned char *zipmapNew(void) { unsigned char *zm = zmalloc(2

redis原始碼分析與思考(八)——物件

    談及物件,我們不免會立即聯想到Java、C++等面向物件的語言,而在C中是沒有物件這一說法的,為了方便管理與程式碼整體的優化,redis基於前面幾篇部落格的資料結構自建了一套物件系統。這個系統包含著字串物件、列表物件、雜湊物件、集合物件以及有序集合物件。