Redis源码阅读sds扩容 | 青训营

78 阅读2分钟

SDS扩容源码阅读

根据sds的初始化过程我们可以知道一个sds的内存分布如下

17bfa9204064c5dfa1ac01a66532122.png

sds _sdsMakeRoomFor(sds s, size_t addlen, int greedy) {
    void *sh, *newsh;
    size_t avail = sdsavail(s);
    size_t len, newlen, reqlen;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen;
    size_t usable;

如果还有内存空间,就直接返回

    /* Return ASAP if there is enough space left. */
    if (avail >= addlen) return s;

获取sds的头部地址,和目前len的大小

    len = sdslen(s);
    sh = (char*)s-sdsHdrSize(oldtype);
    reqlen = newlen = (len+addlen);
    assert(newlen > len);   /* Catch size_t overflow */

这里有两种策略,一种是贪心策略,一种是非贪心策略

贪心策略: 如果需要的新内存大小小于sds的最大预分配大小,就直接申请两倍的空间。 如果大于最大预分配空间就多申请一个最大预分配空间的大小

这样可以减少后续的内存分配次数,减少系统态的调用,提高整体速度

    if (greedy == 1) {
        if (newlen < SDS_MAX_PREALLOC)
            newlen *= 2;
        else
            newlen += SDS_MAX_PREALLOC;
    }

    type = sdsReqType(newlen);

    /* Don't use type 5: the user is appending to the string and type 5 is
     * not able to remember empty space, so sdsMakeRoomFor() must be called
     * at every appending operation. */
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;

    hdrlen = sdsHdrSize(type);
    assert(hdrlen + newlen + 1 > reqlen);  /* Catch size_t overflow */
    if (oldtype==type) {
        newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);
        if (newsh == NULL) return NULL;
        s = (char*)newsh+hdrlen;
    } else {

在新申请的内存里先用sdshdrlen定位到s所在的位置,然后将原来的数据复制过去,再对s前面的sdshdrlen长度的部分内存进行赋值,同时释放原来的sh的内存

        /* Since the header size changes, need to move the string forward,
         * and can't use realloc */
        newsh = s_malloc_usable(hdrlen+newlen+1, &usable);
        if (newsh == NULL) return NULL;
        memcpy((char*)newsh+hdrlen, s, len+1);
        s_free(sh);
        s = (char*)newsh+hdrlen;
        s[-1] = type;
        sdssetlen(s, len);
    }

usable这里多减了1是流出字符串结束符\0的位置

    usable = usable-hdrlen-1;
    if (usable > sdsTypeMaxSize(type))
        usable = sdsTypeMaxSize(type);
    sdssetalloc(s, usable);
    return s;
}

总结

redis里sds的扩容采用了预分配的思路,提前分配更多的内存,以节省之后扩容的次数。但是这样会增加内存的消耗,是一种空间换时间的思路。