Redis SDS总结

97 阅读2分钟

C语言char*字符串的缺点


  1. 以\0为结尾, 就无法存储任意的二进制数据

  2. 拿字符串长度时需要遍历

  3. 拼接字符串的时候要遍历两次

    1. 首先遍历原串末尾,然后再判断新的串加进来空间够不够
    2. 够的话再遍历目的串,依次添加进来

SDS做了哪些优化


  1. 数据结构上 → 空间换时间

    1. len → 当前字符串的长度
    2. alloc → 当前分配了多长的空间, 这样就可以减少了内存重复分配的次数,而且还是惰性删除的
    3. flag → 标记sds hdr的类型
    4. char* sds → buf[] → 存储真正的字符串
  2. 根据不同的字符串长度分配不同的元数据的长度 sdshdr有4种类型(sdshdr5被废弃了)

  3. 紧凑型字符串结构:在sdshdr上用了编译器优化,__attribute((packed))__来告诉编译器在内存中顺序存放,不用字节对齐

SDS空间预分配 sdsMakeRoomFor函数


sds sdsMakeRoomFor(sds s, size_t addlen)
{
    void *sh, *newsh;
    // s最初在sdsavail函数中会通过s[-1]直接拿到hdr的类型
    size_t avail = sdsavail(s);
    size_t len, newlen;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen;

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

    // 剩余的空间不够了
    len = sdslen(s);

    sh = (char *)s - sdsHdrSize(oldtype);
    // 新长度暂时为当前字符串的长度 + 想要增加的长度
    newlen = (len + addlen);
    if (newlen < SDS_MAX_PREALLOC)
        // 如果小于1MB,那么分配 2倍的newLen这么长
        newlen *= 2;
    else
        // 否则直接分配 newLen + 1MB
        newlen += SDS_MAX_PREALLOC;

    type = sdsReqType(newlen);
    // 准备变更整个hdr的长度
    /* 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);
    if (oldtype == type)
    {
        // 和以前一样的类型的话,就分配hdrlen + newlen + 1字节
        newsh = s_realloc(sh, hdrlen + newlen + 1);
        if (newsh == NULL)
            return NULL;
        s = (char *)newsh + hdrlen;
    }
    else
    {
        /* Since the header size changes, need to move the string forward,
         * and can't use realloc */
        newsh = s_malloc(hdrlen + newlen + 1);
        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);
    }
    sdssetalloc(s, newlen);
    return s;
}
  • 预分配包含三个步骤:

    • 先判断原来的字符串是不是空间足够的,是的话直接返回

    • 空间不够的话就先算出newLen= addLen + 现有长度len

      • 如果小于1MB, 分配 2 * newLen
      • 否则分配 newLen + 1MB
    • 调整hdr的长度

      • 分配hdrlen + newLen + 1字节