Redis源码学习:SDS设计与内存管理

为什么Redis选择SDS

1、缓解C语言字符串的缺陷

在 C 语言中可以使用 char* 字符数组来实现字符串。每个字符串分配一段连续的内存空间,依次存放字符串中的每一个字符,最后以null字符结尾。这种设计存在以下问题:
image.png

1、低效的操作
每次获取字符串长度都需要遍历字符串,找到null字符的位置,时间复杂度为O(n)。

2、二进制不安全
C字符串不能存储二进制数据,假如要存储的二进制图片中包含了null字符,会把它看做是字符串的结尾,出现错误。

3、缓冲区溢出
字符串操作(如拼接、复制)容易导致缓冲区溢出,进而引发安全问题。

2、提升性能和灵活性

SDS设计解决了传统C字符串存在的问题,提供了更高效、更安全的字符串操作能力,主要体现在以下几个方面:

1) 获取字符串长度的高效性

传统的C字符串需要线性遍历来获取长度,时间复杂度为O(n)。而SDS在结构体中维护了len字段,可以在常数时间O(1)内直接获取字符串长度,大大提升了效率。

2) 防止缓冲区溢出的安全性

C字符串在拼接、复制等操作时,很容易出现缓冲区溢出的问题,从而导致安全隐患。SDS则通过动态分配内存的方式,在字符串操作时自动调整内存大小,从根本上避免了缓冲区溢出。

3) 存储二进制数据的灵活性

传统C字符串由于使用null字符作为结尾标记,无法安全地存储包含null字符的二进制数据。而SDS则完全绕过了这一限制,可以方便地存储任意二进制数据,提高了数据存储的灵活性。

4) 空间预分配的优化

在对SDS进行扩展时,SDS会预先分配更大的内存空间,减少了频繁重新分配内存所带来的性能开销。当字符串长度小于1MB时,扩容后的空间是原长度的两倍;当长度超过1MB时,则会额外分配1MB的空间,防止过度使用内存。

SDS结构

在Redis源码中,SDS结构源码(位于sds.h文件)如下:

struct __attribute__ ((__packed__)) sdshdr5 {unsigned char flags; /* 3 lsb of type, and 5 msb of string length */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {uint8_t len; /* used */uint8_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {uint16_t len; /* used */uint16_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {uint32_t len; /* used */uint32_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
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[];
};

定义了sdshdr5sdshdr8sdshdr16sdshdr32sdshdr64多种结构,sdshdr5不再使用无需关注。每一个SDS结构都包含了实际存储字符串的字符数组buf[]记录当前字符串的长度len记录分配的总空间alloc(不包括头部和空终止符)SDS类型flags

它们的主要区别就在于,数据结构中的字符数组现有长度 len 和分配空间长度 alloc,这两个元数据的数据类型不同。uint8_t 是 8 位无符号整型,会占用 1 字节的内存空间。当字符串类型是sdshdr8时,它表示字符数组buf[]长度(包括数组最后一位\0)不会超过 256 字节(2 的 8 次方等于 256)。uint16_t、uint32_t、uint64_t,分别表示不超过 2 的 16 次方、32 次方和 64 次方。这两个元数据各自占用的内存空间在 sdshdr16、sdshdr32、sdshdr64 类型中,则分别是 2 字节、4 字节和 8 字节。

在SDS结构的定义上还使用了__attribute__ ((__packed__)),来优化空间,它的作用就是告诉编译器,在编译结构时,不要使用字节对齐的方式,而是采用紧凑的方式分配内存。

image.png

SDS的内存分配过程

SDS的内存分配包括创建、扩展和释放三个主要过程。以下是SDS的内存管理源码分析。

1、SDS创建

SDS的创建函数是sdsnewlen

sds _sdsnewlen(const void *init, size_t initlen, int trymalloc) {void *sh;sds s;char type = sdsReqType(initlen);/* Empty strings are usually created in order to append. Use type 8* since type 5 is not good at this. */if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;int hdrlen = sdsHdrSize(type);unsigned char *fp; /* flags pointer. */size_t usable;assert(initlen + hdrlen + 1 > initlen); /* Catch size_t overflow */sh = trymalloc?s_trymalloc_usable(hdrlen+initlen+1, &usable) :s_malloc_usable(hdrlen+initlen+1, &usable);if (sh == NULL) return NULL;if (init==SDS_NOINIT)init = NULL;else if (!init)memset(sh, 0, hdrlen+initlen+1);s = (char*)sh+hdrlen;fp = ((unsigned char*)s)-1;usable = usable-hdrlen-1;if (usable > sdsTypeMaxSize(type))usable = sdsTypeMaxSize(type);switch(type) {case SDS_TYPE_5: {*fp = type | (initlen << SDS_TYPE_BITS);break;}case SDS_TYPE_8: {SDS_HDR_VAR(8,s);sh->len = initlen;sh->alloc = usable;*fp = type;break;}case SDS_TYPE_16: {SDS_HDR_VAR(16,s);sh->len = initlen;sh->alloc = usable;*fp = type;break;}case SDS_TYPE_32: {SDS_HDR_VAR(32,s);sh->len = initlen;sh->alloc = usable;*fp = type;break;}case SDS_TYPE_64: {SDS_HDR_VAR(64,s);sh->len = initlen;sh->alloc = usable;*fp = type;break;}}if (initlen && init)memcpy(s, init, initlen);s[initlen] = '\0';return s;
}
1.1 确定类型和头部大小

首先,通过sdsReqType函数确定适合的SDS类型,然后计算头部大小。sdsReqType根据字符串长度选择合适的SDS类型,而sdsHdrSize返回相应类型的头部大小。

char type = sdsReqType(initlen);
int hdrlen = sdsHdrSize(type);
1.2. 分配内存

接着,通过s_trymalloc_usables_malloc_usable函数分配内存,这里&usable会返回实际可用的内存大小。

sh = trymalloc?s_trymalloc_usable(hdrlen+initlen+1, &usable) :s_malloc_usable(hdrlen+initlen+1, &usable);
1.3. 初始化内存

根据init参数的不同,初始化内存。

if (init==SDS_NOINIT)init = NULL;
else if (!init)memset(sh, 0, hdrlen+initlen+1);
1.4. 设置类型和长度

最后,设置SDS的类型标志和长度,并返回字符串的指针。s表示是SDS中buf[]的起始位置,len表示使用空间,alloc表示分配的空用空间。

s = (char*)sh+hdrlen;
fp = ((unsigned char*)s)-1;
SDS_HDR_VAR(32,s);
sh->len = initlen;
sh->alloc = usable;
*fp = type;

2、SDS扩容

SDS的扩展函数是sdsMakeRoomFor

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;len = sdslen(s);sh = (char*)s-sdsHdrSize(oldtype);reqlen = newlen = (len+addlen);assert(newlen > len);   /* Catch size_t overflow */if (greedy == 1) {if (newlen < SDS_MAX_PREALLOC)newlen *= 2;elsenewlen += 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 {/* 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 = usable-hdrlen-1;if (usable > sdsTypeMaxSize(type))usable = sdsTypeMaxSize(type);sdssetalloc(s, usable);return s;
}
2.1 计算可用空间

首先,计算当前字符串的可用空间和长度。

size_t avail = sdsavail(s);
size_t len = sdslen(s);
2.2 计算新长度

如果可用空间不足,则需要扩展。新长度通常是当前长度的两倍,以减少频繁的内存分配操作。SDS_MAX_PREALLOC是1M的大小,扩容后新空间的大小有两种情况:

  • 如果新长度小于1M,则新空间为扩容后字符串长度的两倍+1
  • 否则,则新空间为扩容后字符串长度+1M+1
    后面的+1是调用内存分配函数是添加的。
newlen = (len+addlen);
if (newlen < SDS_MAX_PREALLOC)newlen *= 2;
elsenewlen += SDS_MAX_PREALLOC;
2.3. 分配新内存

根据新长度分配内存。如果新的类型与旧的类型相同,直接使用s_realloc_usable重新分配内存,否则需要创建新的SDS,重新分配内存拷贝数据。

type = sdsReqType(newlen);
hdrlen = sdsHdrSize(type);
if (oldtype==type) {newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);if (newsh == NULL) return NULL;s = (char*)newsh+hdrlen;
} else {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);
}
2.4. 更新长度

最后,更新分配的总空间,并返回扩展后的SDS。

sdssetalloc(s, usable);
return s;

3、SDS的释放

SDS的释放函数是sdsfree

void sdsfree(sds s) {if (s == NULL) return;s_free((char*)s-sdsHdrSize(s[-1]));
}
3.1. 检查空指针

首先,检查传入的指针是否为空。

if (s == NULL) return;
3.2. 释放内存

通过s_free函数释放内存,注意需要将指针偏移到SDS头部的位置。

s_free((char*)s-sdsHdrSize(s[-1]));

总结

Redis使用简单动态字符串SDS来代替传统C字符串,解决了获取长度低效、缓冲区溢出和二进制不安全等问题。SDS在结构体中维护长度,支持二进制存储,自动扩容防止溢出,性能和灵活性均有提升。文中详细分析了SDS的内存分配、扩容和释放过程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/34405.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux根目录挂载点(/dev/mapper/centos-root)扩容

如果我们在安装系统是采用自定义分区的话&#xff0c;就可以提前规划好这个事情。但是如果平常没注意就直接采用默认安装的方式的话。一旦 根目录的容量耗尽&#xff0c;将会影响业务的运行。今天我们来扩容逻辑卷。 默认安装的话会给home目录分比较多的空间&#xff0c;我们可…

【Leetcode每日一题】 01背包 - DP41 【模板】01背包(难度⭐⭐)(80)

1. 题目解析 题目链接&#xff1a;DP41 【模板】01背包 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 第一问&#xff1a;不超过总体积的背包问题 1. 状态表示 dp[i][j] 表示&#xff1a;从前 i 个物品中挑选&…

汇编语言实验八

目录 一、实验目的 二、实验内容 三&#xff0e;实验步骤以及结果 1、阅读monthtab.asm 程序&#xff0c;要求写出该程序功能&#xff0c;并在实验报告中画出其程序流程图 2.编写一段程序&#xff0c;要求在长度为20的数组&#xff08;无符号数&#xff09;中&#xff0c;…

LabVIEW与C#相互调用dll

C#调用LabVIEW创建的dll 我先讲LabVIEW创建自己的.net类库的方法吧&#xff0c;重点是创建&#xff0c;C#调用的步骤&#xff0c;大家可能都很熟悉了。 1、创建LabVIEW项目&#xff0c;并创建一个简单的add.vi&#xff0c;内容就是abc&#xff0c;各个接线端都正确连接就好。 …

云层之间穿梭特效视频转场PR模板素材

12 个超赞的 Premiere Pro 云层穿梭特效视频转场模板 https://prmuban.com/39056.html &#x1f4fd; 你是否正在寻找一种方法&#xff0c;让你的视频更酷、更时尚、更吸引人&#xff1f;今天推荐的12个逼真的云层穿梭特效视频转场模板&#xff0c;绝对能为你的作品锦上添花 ✨…

智赢选品,OZON数据分析选品利器丨萌啦OZON数据

在电商行业的激烈竞争中&#xff0c;如何快速准确地把握市场动态、洞察消费者需求、实现精准选品&#xff0c;是每个电商卖家都面临的挑战。而在这个数据驱动的时代&#xff0c;一款强大的数据分析工具无疑是电商卖家们的得力助手。今天&#xff0c;我们就来聊聊这样一款选品利…

我也认为说 360 无法卸载这一说法,是一个 “彻头彻尾的谣言”

最近&#xff0c;360 公司董事长周鸿祎发布视频回应了 360 无法卸载这一说法&#xff0c;称其是一个 “彻头彻尾的谣言”。他解释道&#xff0c;360 软件完全可以卸载&#xff0c;在设置里面有卸载的入口&#xff0c;通过软件管家也可以正常卸载。不能卸载的说法完全是断章取义…

【UIDynamic-动力学-UIPushBehavior-推行为 Objective-C语言】

一、接下来,我们来说这个,推行为, 1.推行为,首先,它叫做UIPushBehavior, 这个里边呢,又分为持续推力、瞬时推力, 我们新建一个项目,叫做:13-推行为 我们这个里边,还是先来一个redView, UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(100,100,…

【开发】利用SSH端口转发通过阿里云服务器访问实验室设备

文章目录 写在前面公网服务器与实验室服务器连通性公网服务器ping实验室实验室ping公网服务器SSH隧道转发 写在前面 最近实验室搬家&#xff0c;导致无法访问内网&#xff0c;之前搭建的zerotier组网成功利用手机热点访问&#xff0c;但是无奈zerotier的不稳定导致开发效率低&…

B端产品竞品分析-总结版

B端竞品分析的难点 分析维度-业务逻辑复杂 B端产品与C端产品业务模型不同&#xff0c;B端产品主要以业务为导向&#xff0c;因此其业务流程与业务逻辑梳理起来也会较C端产品复杂的多&#xff0c;对于个人能力也有一定的要求&#xff0c;需要我们具备相关领域或行业专业知识。…

猫头虎分享已解决Bug:Array Index Out of Bounds Exception

&#x1f42f; 猫头虎分享已解决Bug&#xff1a;Array Index Out of Bounds Exception &#x1f42f; 摘要 大家好&#xff0c;我是猫头虎&#xff0c;今天我们要聊聊后端开发中经常遇到的一个问题&#xff1a;Array Index Out of Bounds Exception&#xff0c;即 java.lang.…

win10 修改远程桌面端口,在Win10上修改远程桌面端口的要怎么操作

在Windows 10上修改远程桌面端口是一个涉及系统配置的过程&#xff0c;这通常是为了增强安全性或满足特定网络环境的需要。 一、通过注册表编辑器修改远程桌面端口 1. 打开注册表编辑器&#xff1a; - 按下Win R组合键&#xff0c;打开“运行”对话框。 - 在“运行”对话框…

大模型揭秘:AI与CatGPT在实体识别中的创新应用

摘要 尽管大规模语言模型 (LLM) 在各种 NLP 任务上已经取得了 SOTA 性能&#xff0c;但它在 NER 上的性能仍然明显低于监督基线。这是由于 NER 和 LLMs 这两个任务之间的差距&#xff1a;前者本质上是序列标记任务&#xff0c;而后者是文本生成模型。在本文中&#xff0c;我们…

【大数据】—双均线策略(移动平均线)

声明&#xff1a;股市有风险&#xff0c;投资需谨慎&#xff01;本人没有系统学过金融知识&#xff0c;对股票有敬畏之心没有踏入其大门&#xff0c;今天用另外一种方法模拟炒股&#xff0c;后面的模拟的实战全部用同样的数据&#xff0c;最后比较哪种方法赚的钱多。 量化交易…

《2024云安全资源池 能力指南》

《2024云安全资源池 能力指南》这份报告不仅梳理了云安全资源池的发展历程,还深入探讨了其在当前云计算环境下的重要性和必要性。报告详细分析了云安全资源池的市场需求、技术架构、关键技术以及行业应用案例,为政企用户提供了全面的云安全解决方案。通过资料收集、问卷调研、企…

Unity | Shader基础知识(番外:模型的制作流程)

目录 一、前言 二、模型的诞生 三、模型的表面 四、模型的贴图 五、上完材质的模型 六、材质的来源 七、作者的碎碎念 一、前言 up发现&#xff0c;初学程序&#xff0c;除非你是美术&#xff0c;模型出生&#xff0c;要不然对这些都是萌萌哒&#xff08;蒙蒙哒&#x…

从宏基因组中鉴定病毒序列(2)

Introduction 在环境微生物学和生态学研究中&#xff0c;宏基因组学&#xff08;Metagenomics&#xff09;技术的应用已经彻底改变了我们对微生物群落的理解。宏基因组学通过对环境样本中的全部遗传物质进行测序和分析&#xff0c;可以全面揭示微生物群落的组成、功能和相互作…

Modbus转Profibus网关在汽车行业的应用

一、前言 在当前汽车工业的快速发展中&#xff0c;汽车制造商正通过自动化技术实现生产的自动化&#xff0c;目的是提高生产效率和减少成本。Modbus转Profibus网关&#xff08;XD-MDPB100&#xff09;应用于汽车行业&#xff0c;主要体现在提升自动化水平、优化数据传输以及实…

刷题之小欧的平均数(卡码网)

小欧的平均数 这道题不看解析的话完全没有思路&#xff0c;连题目都没读明白&#xff0c;甚至看了评论答出来了还是不知道为什么&#xff0c;有知道的朋友可以教教我 #include<iostream> using namespace std;int main() {int x,y,z;cin>>x>>y>>z;//…

【机器学习 复习】第10章 聚类算法

一、概念 1.聚类 &#xff08;1&#xff09;是无监督学习&#xff0c;其实无监督学习就是无中生有&#xff0c;不给你标准答案&#xff08;标签啊啥的&#xff09;&#xff0c;然后让你自己来。 &#xff08;2&#xff09;聚类就是这样&#xff0c;让机器自己根据相似特征把相…