1. 程式人生 > >redis sds 實現的幾個巧妙的想法

redis sds 實現的幾個巧妙的想法

    翻redis 原始碼的時候,發現有些用法真是很巧妙的,一個是指標變換,一個是記憶體管理策略,當然後者是有利弊的。    

    sds.h 就這兩個資料型別,這裡,能夠sds 和 sdshdr 相互轉化。對外的介面,引數和返回只有char * 。redis 為每個char * 都維護了個數據結構 sdshdr ,防止溢位,又可以減少記憶體分配。這裡char * 怎麼跟 sdshdr 關聯上?

typedef char *sds;

struct sdshdr {
    long len;
    long free;
    char buf[];
};

    這裡 sdshdr 是16個位元組,buf 是動態陣列,計算偏移的時候size 為0。通過sdshdr 取 buf 地址,強制轉成 char * ,也就是sds ,這個陣列轉字串指標的操作很好理解。當我們獲得sds 的時候,想知道預分配長度,直接向左偏移struct 大小,就是sdshdr的地址,強制轉換型別後,就額可以取len。

    看原始碼 sdslen 函式就可以看出:

size_t sdslen(const sds s) {
    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); //先偏移struct大小,強制轉成sdshdr
    return sh->len;
}

    第二個有意思的是sds 的記憶體預分配,在對字串拼接的時候,有個惰性預分配的操作,每次free 為0 ,觸發重新拷貝,都將多分配一倍多餘記憶體,備用。這裡好處是減少記憶體分配,當然也浪費了記憶體。對應底層函式如下:

static sds sdsMakeRoomFor(sds s, size_t addlen) {
    struct sdshdr *sh, *newsh;
    size_t free = sdsavail(s);
    size_t len, newlen;

    if (free >= addlen) return s;
    len = sdslen(s);
    sh = (void*) (s-(sizeof(struct sdshdr)));
    newlen = (len+addlen)*2; //記憶體空間在要重新分配的情況下,直接加倍
    newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
#ifdef SDS_ABORT_ON_OOM
    if (newsh == NULL) sdsOomAbort();
#else
    if (newsh == NULL) return NULL;
#endif

    newsh->free = newlen - len;
    return newsh->buf;
}