简单动态字符串sds

Redis并没有使用C语言传统的字符串,而是构建了一种名为简单动态字符串(Simple dynamic string,SDS),并作为默认字符串使用。
 
例如:执行如下命令
 
SET msg "hello world"
 
Redis数据库中将建立一个键值对,键是一个字符串对象,是一个负责保存"msg"的SDS,而值也是一个SDS,负责保存“hello world”。
 
注意:SDS还被用于缓冲区,AOF缓冲区以及客户端的输入缓冲区都是SDS实现的
 
sds的定义和实现分别位于sds.h 和sds.c
 
redis 3.0的sds结构体比较简单
/*
 * 保存字符串对象的结构
 */
struct sdshdr {
    
    // buf 中已占用空间的长度
    int len;

    // buf 中剩余可用空间的长度
    int free;

    // 数据空间
    char buf[];
};

到了6.0,就区分8位,32位,64位了,例如 

struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
解释下 “__attribute__ ((__packed__))” 是做什么的
通常定义一个U32 ,CPU 期望 这个 U32 地址是 DW 对齐的, 这样对CPU访问 mem bus 比较友好。
 
所以,当我们定义这样一个结构体:
 
struct test{
 
        char i,
 
        uint32 a
 
}
那么,编译器会默认在 i 和 a 之间插入 reserve,确保 a 的位置是 4 对齐的。sizeof(test) = 8.
 
 它就等效于:
 
struct test{
 
        char i,
 
        char reserve[3],
 
        uint32 a
 
}
加入  “__attribute__ ((__packed__))” 的效果,则在于避免编译器 “自作聪明”。 告诉编译器,我们这里不需要补全。
 
struct  __attribute__ ((__packed__)) test{
 
        char i,
 
        uint32 a
 
}
sizeof(test) = 5; 
 

当 SDS 的 API 对一个 SDS 进行修改, 并且需要对 SDS 进行空间扩展的时候, 程序不仅会为 SDS 分配修改所必须要的空间, 还会为 SDS 分配额外的未使用空间。

其中, 额外分配的未使用空间数量由以下公式决定:

  • 如果对 SDS 进行修改之后, SDS 的长度(也即是 len 属性的值)将小于 MB , 那么程序分配和 len 属性同样大小的未使用空间, 这时 SDS len 属性的值将和 free 属性的值相同。 举个例子, 如果进行修改之后, SDS 的 len 将变成 13 字节, 那么程序也会分配 13 字节的未使用空间, SDS 的 buf 数组的实际长度将变成 13 13 27 字节(额外的一字节用于保存空字符)。
  • 如果对 SDS 进行修改之后, SDS 的长度将大于等于 MB , 那么程序会分配 MB 的未使用空间。 举个例子, 如果进行修改之后, SDS 的 len 将变成 30 MB , 那么程序会分配 MB 的未使用空间, SDS 的 buf 数组的实际长度将为 30 MB MB byte 。

通过空间预分配策略, Redis 可以减少连续执行字符串增长操作所需的内存重分配次数。

 

当 SDS 的 API 需要缩短 SDS 保存的字符串时, 程序并不立即使用内存重分配来回收缩短后多出来的字节, 而是使用 free 属性将这些字节的数量记录起来, 并等待将来使用。

 

参考链接:

http://redisbook.com

 
posted @ 2021-02-06 22:23  diameter  阅读(76)  评论(0编辑  收藏  举报