redis源码之内存管理源码分析

动态字符串sds分析

Posted by chensong on 2019-03-22 22::48::45

前言

redis源码只有23000行代码, 可以说压缩的代码非常经典, 以最少代码写出存储管理

正文

一, redis 中内存管理

redis中提供接口有

void *zmalloc(size_t size);
void *zcalloc(size_t size);
void *zrealloc(void *ptr, size_t size);
void zfree(void *ptr);
char *zstrdup(const char *s);
//已经使用内存的大小
size_t zmalloc_used_memory(void);
void zmalloc_enable_thread_safeness(void);
void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
float zmalloc_get_fragmentation_ratio(size_t rss);
// 获取
size_t zmalloc_get_rss(void);
size_t zmalloc_get_private_dirty(void);
size_t zmalloc_get_smap_bytes_by_field(char *field);
void zlibc_free(void *ptr);

#ifndef HAVE_MALLOC_SIZE
size_t zmalloc_size(void *ptr);
#endif

zmalloc(size)在分配内存的时候会多申请sizeof(size_t)个字节大小的内存 【64位系统中是8字节】,即调用malloc(size+8), 所以一共申请分配size+8个字节,zmalloc(size)会在已分配内存的首地址开始的8字节 中存储size的值,实际上因为内存对齐,malloc(size+8)分配的内存可能会比size+8要多一些, 目的是凑成8的倍数,所以实际分配的内存大小是size+8+X【(size+8+X)%8==0 (0<=X<=7)】。 然后内存指针会向右偏移8个字节的长度。zfree()就是zmalloc()的一个逆操作, 而zmalloc_size()的目的就是计算出size+8+X的总大小

size&(sizeof(long) - 1)

二, 动态字符串sds.h

字符串结构体

typedef char *sds;
//柔性数组成员不占用结构体的空间,只作为一个符号地址存在,而且必须是结构体的最后一个成员。
//柔性数组成员不仅可以用于字符数组,还可以是元素为其它类型的数组。
//C99中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员,
//但结构中的柔性数组成员前面必须至少一个其他成员。柔性数组成员允许结构中包含一个大小可变的
//数组。sizeof返回的这种结构大小不包括柔性数组的内存。包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
/************************************************************************/
/* sizeof(struct sdshdr) == 8                                           */
/************************************************************************/
struct sdshdr {
    unsigned int len;// 记录 buf 数组中已使用字节的数量// 等于 SDS 所保存字符串的长度
    unsigned int free;// 记录 buf 数组中未使用字节的数量
    char buf[];  // ==> char buf[0] // 字节数组,用于保存字符串 
};

sdsMakeRoomFor 方法是 申请内存 分为两个方向 扩充 分别是:

  1. newlen = (老的数据的大小 + addlen ) * 2
  2. newlen = 老的数据的大小 + addlen + SDS_MAX_PREALLOC
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);   // new ---> old + new_len
	if (newlen < SDS_MAX_PREALLOC)
	{
		newlen *= 2;
	}
	else
	{
		newlen += SDS_MAX_PREALLOC; /// [1024 * 1024] 1M
	}
    newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
	if (newsh == NULL)
	{
		return NULL;
	}

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

格式化输出

关于va_开头使用方法可以看我这份博客: https://blog.csdn.net/Poisx/article/details/78822693

方法一: sdscatvprintf

// 得到
sds sdscatvprintf(sds s, const char *fmt, va_list ap) 
{
    va_list cpy;
    char staticbuf[1024], *buf = staticbuf, *t;
    size_t buflen = strlen(fmt) * 2;

    /* We try to start using a static buffer for speed.
     * If not possible we revert to heap allocation. */
    if (buflen > sizeof(staticbuf)) 
	{
        buf = zmalloc(buflen);
		if (buf == NULL)
		{
			return NULL;
		}
    }
	else 
	{
        buflen = sizeof(staticbuf);
    }

    /* Try with buffers two times bigger every time we fail to
     * fit the string in the current buffer size. */
    while(1) 
	{
        buf[buflen-2] = '\0';
        va_copy(cpy,ap);
        vsnprintf(buf, buflen, fmt, cpy);
        va_end(cpy);
		// 判断 buf 大小是否可以放的下 cpy的数据, 不够从新申请内存
        if (buf[buflen-2] != '\0') 
		{
			if (buf != staticbuf)
			{
				zfree(buf);
			}
            buflen *= 2;
            buf = zmalloc(buflen);
			if (buf == NULL)
			{
				return NULL;
			}
            continue;
        }
        break;
    }

    /* Finally concat the obtained string to the SDS string and return it. */
    t = sdscat(s, buf);
	if (buf != staticbuf)
	{
		zfree(buf);
	}
    return t;
}

方法二:sdscatfmt

sds sdscatfmt(sds s, char const *fmt, ...) 
{
    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
    size_t initlen = sdslen(s);
    const char *f = fmt;
    int i;
    va_list ap;

    va_start(ap,fmt);
    f = fmt;    /* Next format specifier byte to process. */
    i = initlen; /* Position of the next byte to write to dest str. */
    while(*f) 
	{
        char next, *str;
        unsigned int l;
        long long num;
        unsigned long long unum;

        /* Make sure there is always space for at least 1 char. */
        if (sh->free == 0) 
		{
            s = sdsMakeRoomFor(s,1);
            sh = (void*) (s-(sizeof(struct sdshdr)));
        }

        switch(*f) {
        case '%':
            next = *(f+1);
            f++;
            switch(next) {
            case 's':
            case 'S':
                str = va_arg(ap,char*);
                l = (next == 's') ? strlen(str) : sdslen(str);
                if (sh->free < l) 
				{
                    s = sdsMakeRoomFor(s,l);
                    sh = (void*) (s-(sizeof(struct sdshdr)));
                }
                memcpy(s+i,str,l);
                sh->len += l;
                sh->free -= l;
                i += l;
                break;
            case 'i':
            case 'I':
				if (next == 'i')
				{
					num = va_arg(ap, int);
				}
				else
				{
					num = va_arg(ap, long long);
				}
                {
                    char buf[SDS_LLSTR_SIZE];
                    l = sdsll2str(buf,num);
                    if (sh->free < l) 
					{
                        s = sdsMakeRoomFor(s,l);
                        sh = (void*) (s-(sizeof(struct sdshdr)));
                    }
                    memcpy(s+i,buf,l);
                    sh->len += l;
                    sh->free -= l;
                    i += l;
                }
                break;
            case 'u':
            case 'U':
				if (next == 'u')
				{
					unum = va_arg(ap, unsigned int);
				}
				else
				{
					unum = va_arg(ap, unsigned long long);
				}
                {
                    char buf[SDS_LLSTR_SIZE];
                    l = sdsull2str(buf, unum);
                    if (sh->free < l) {
                        s = sdsMakeRoomFor(s,l);
                        sh = (void*) (s-(sizeof(struct sdshdr)));
                    }
                    memcpy(s+i,buf,l);
                    sh->len += l;
                    sh->free -= l;
                    i += l;
                }
                break;
            default: /* Handle %% and generally %<unknown>. */
                s[i++] = next;
                sh->len += 1;
                sh->free -= 1;
                break;
            }
            break;
        default:
            s[i++] = *f;
            sh->len += 1;
            sh->free -= 1;
            break;
        }
        f++;
    }
    va_end(ap);

    /* Add null-term */
    s[i] = '\0';
    return s;
}

int转换 string类型的数据

这个工具很好使用的在C++ 相当于 stringostream工具类

#define SDS_LLSTR_SIZE 21
int sdsll2str(char *s, long long value) 
{
    char *p, aux;
    unsigned long long v;
    size_t l;

    /* Generate the string representation, this method produces
     * an reversed string. */
    v = (value < 0) ? -value : value;
    p = s;
    do 
	{
        *p++ = '0'+(v%10);  // assic 一个字节 = 8bit // string hex -> 是不能超过9的数字的  
        v /= 10; 
    } while(v);
	if (value < 0)
	{
		*p++ = '-';
	}

    /* Compute length and add null term. */
    l = p-s;
    *p = '\0';

    /* Reverse the string. */
    p--;
    while(s < p) 
	{
        aux = *s;
        *s = *p;
        *p = aux;
        s++;
        p--;
    }
    return l;
}

下面内存中数据

34 33 00 00 00 00 00 00 00 00 00 00 00 00 00 00 43..............

,
 =, p =
[110] num = 2,  v = 293918262, p =
34 33 38 00 00 00 00 00 00 00 00 00 00 00 00 00 438.............

,
 =, p =
[110] num = 3,  v = 458888555, p =
34 33 38 38 00 00 00 00 00 00 00 00 00 00 00 00 4388............

比较字符串出现

/*
 Example:
 *
 * s = sdsnew("AA...AA.a.aa.aHelloWorld     :::");
 * s = sdstrim(s,"A. :");
 * printf("%s\n", s);
 *
 * Output will be just "Hello World".*/
sds sdstrim(sds s, const char *cset) 
{
    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
    char *start, *end, *sp, *ep;
    size_t len;

    sp = start = s;
    ep = end = s + sdslen(s)-1;
	/************************************************************************/
	/* char* strchr(char *s, char c)
		{
			while(*s != '\0' && *s != c)
			{
				++s;
			}
			return *s==c ? s : NULL;
		}     
	*/
	/************************************************************************/
	while (sp <= end && strchr(cset, *sp)) //  比较的是直到 *sp中字符不可能出现在cset字符串中
	{
		sp++;
	}
	while (ep > start && strchr(cset, *ep))
	{
		ep--;
	}
    len = (sp > ep) ? 0 : ((ep-sp)+1);
	if (sh->buf != sp)
	{
		memmove(sh->buf, sp, len);
	}
    sh->buf[len] = '\0';
    sh->free = sh->free+(sh->len-len);
    sh->len = len;
    return s;
}

在文件sds.c中,给出了所有操作sds字符串的操作,主要包括以下操作

function 翻译
sdsnew 字符串新建
sdsfree 字符串释放
sdsdup 字符串复制
sdssetlen 字符串的长度更新
sdsinclen 字符串增加长度
sdsalloc 得到字符串分配的内存长度(仅仅是用来存储字符串的部分的长度)
sdsmakeroomfor 当字符串长度增加时,用来扩展字符串,值得注意的是,当出现类型提升(比如从sdshdr8提升到sdshdr16),代码会有相应的变化
sdsremoveFreeSpace 压缩字符串多余的存储空间,同上,注意出现的类型下降相关的代码
sdstrim 删除掉字符串2头的在指定字符集合内的字符
sdsrange 将字符串内容设置为指定区间内的字符,注意其中copy字符串时,使用的是memmove函数
sdscatlen 为当前字符串增加新的内容。
sdssplitlen 将字符串按照分隔符,切割成若干个sds字符串。
sdssplitargs 将命令行字符串的参数解析出来,将其设置为一个sds数组的形式,返回给调用者。
sdsmapchars sds字符串内按照[from, to]的字符map映射进行转换

结语