SDS扩容源码阅读
根据sds的初始化过程我们可以知道一个sds的内存分布如下
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的扩容采用了预分配的思路,提前分配更多的内存,以节省之后扩容的次数。但是这样会增加内存的消耗,是一种空间换时间的思路。