【Redis 源码】压缩链表结构

压缩链表结构

文章目录

  • 压缩链表结构
    • 1. ziplist 由来
    • 2. 组成
    • 3. ziplist对象
      • 3.1 ziplist对象创建
      • 3.2 创建一个空的ziplist
      • 3.3 创建对象
      • 3.4 字符编码指定
    • 4. 总结

1. ziplist 由来

​ 在说压缩链表前,我先拿数组链表来做个引子,来更好理解为什么ziplist的由来,当我们在业务编写时,会涉及链表相关的处理,这时会有这两个进行筛选来应对业务场景,他俩各有各的好处,在不同的业务场景,也发挥着极大的作用。那他俩为什么会有区别呢?

​ 那就要从他俩的存储原理来说起,数组在内存空间是一段连续的存储,而链表是通过存入下一个节点地址(指针)进行查找。

​ 相对于数组,它的空间局部性更好,为什么这么说呢?CPU当要处理数据时,会先读缓存行(Cache Line)从内存拿出局,到缓存行里,CPU读取缓存行里的数据,缓存行大小通常为64kb,当程序访问数组中的某个元素的时候,CPU不仅会把数组的指定元素加载到缓存行中,还会预取(prefetch)相邻的几个元素,因为它们的物理内存地址是连续的。这使便利数组时,接下来要访问的数据很可能已经在内存中了,从而提高了访问速度。

​ 相对于链表,访问其中的元素,CPU必须跟随指针从当前节点跳到另一个节点,每次跳跃都可能导致访问不在缓存行中的内存地址,这会导致缓存未命中(cache miss),进而需要从更慢的内存层级加载数据,降低性能。

🚪从前面解读数组链表,可看出存储地址的连续性的重要,那么我们需要引入压缩链表了,压缩链表(ziplist),既然提到压缩,那么从字面意思,可以理解为内存占用比例会压缩,也就是很节约内存空间,压缩链表相当于把能否将少量数据通过压缩到一串连续的空间进行空间局部性加速,但如果数据量过大,或者key的长度过大,占用过多的缓存行,加速就没有作用,这也是redis建议用小key的原因。

2. 组成

下面的注释,取自redis v2.6中的ziplist.c注释,讲解ziplist如何组成

🐯:通过这几大部分组成ziplist

  1. zlbytes:ziplist长度
  2. zltail:最后一个entry的偏移量
  3. zllen:entries的长度
  4. zlend:ziplist的结束符,255代表是ziplist的结尾

🐯Entry:每个entry都有预先定义的头,头部包含了2组信息,一个是entry的长度,一个是编码

通过下面的二进制看出,前面几位代表了编码类型,类似于汇编指令的操作码

为了加深理解,可看下图

下图取自深入理解计算机系统(csapp)中的第四章处理器的体系结构。可看到它们通过1级和2级的数字来决定操作的指令类型。ziplist同理

 * ZIPLIST OVERALL LAYOUT:* The general layout of the ziplist is as follows:* <zlbytes><zltail><zllen><entry><entry><zlend>** <zlbytes> is an unsigned integer to hold the number of bytes that the* ziplist occupies. This value needs to be stored to be able to resize the* entire structure without the need to traverse it first.** <zltail> is the offset to the last entry in the list. This allows a pop* operation on the far side of the list without the need for full traversal.** <zllen> is the number of entries.When this value is larger than 2**16-2,* we need to traverse the entire list to know how many items it holds.** <zlend> is a single byte special value, equal to 255, which indicates the* end of the list.** ZIPLIST ENTRIES:* Every entry in the ziplist is prefixed by a header that contains two pieces* of information. First, the length of the previous entry is stored to be* able to traverse the list from back to front. Second, the encoding with an* optional string length of the entry itself is stored.** The length of the previous entry is encoded in the following way:* If this length is smaller than 254 bytes, it will only consume a single* byte that takes the length as value. When the length is greater than or* equal to 254, it will consume 5 bytes. The first byte is set to 254 to* indicate a larger value is following. The remaining 4 bytes take the* length of the previous entry as value.** The other header field of the entry itself depends on the contents of the* entry. When the entry is a string, the first 2 bits of this header will hold* the type of encoding used to store the length of the string, followed by the* actual length of the string. When the entry is an integer the first 2 bits* are both set to 1. The following 2 bits are used to specify what kind of* integer will be stored after this header. An overview of the different* types and encodings is as follows:** |00pppppp| - 1 byte*      String value with length less than or equal to 63 bytes (6 bits).* |01pppppp|qqqqqqqq| - 2 bytes*      String value with length less than or equal to 16383 bytes (14 bits).* |10______|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 bytes*      String value with length greater than or equal to 16384 bytes.* |11000000| - 1 byte*      Integer encoded as int16_t (2 bytes).* |11010000| - 1 byte*      Integer encoded as int32_t (4 bytes).* |11100000| - 1 byte*      Integer encoded as int64_t (8 bytes).* |11110000| - 1 byte*      Integer encoded as 24 bit signed (3 bytes).* |11111110| - 1 byte*      Integer encoded as 8 bit signed (1 byte).* |1111xxxx| - (with xxxx between 0000 and 1101) immediate 4 bit integer.*      Unsigned integer from 0 to 12. The encoded value is actually from*      1 to 13 because 0000 and 1111 can not be used, so 1 should be*      subtracted from the encoded 4 bit value to obtain the right value.* |11111111| - End of ziplist.** All the integers are represented in little endian byte order.

ziplist的定义的结构体源码,和上述的注释是相对应

typedef struct zlentry {unsigned int prevrawlensize, prevrawlen;unsigned int lensize, len;unsigned int headersize;unsigned char encoding;unsigned char *p;
} zlentry;

3. ziplist对象

3.1 ziplist对象创建

redis v2.6源码

// object.c(src) - 105line// 创建zipList对象
robj *createZiplistObject(void) {// 创建一个空的ziplistunsigned char *zl = ziplistNew();// 创建对象robj *o = createObject(REDIS_LIST,zl);// 字符编码指定为ziplisto->encoding = REDIS_ENCODING_ZIPLIST;// 返回地址指针return o;
}

3.2 创建一个空的ziplist

这里Redis提供了创建空的ziplist源码,我们进行简单阅读下

// ziplist.c(src) - 418 line/* Create a new empty ziplist. */
unsigned char *ziplistNew(void) {// 定义元数据头大小unsigned int bytes = ZIPLIST_HEADER_SIZE+1;// 分配内存unsigned char *zl = zmalloc(bytes);// 小端序转换成大端序ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);// 用来获取 ziplist 尾部元素相对于 ziplist 开头的偏移量。ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE);// 定义ziplist长度为0ZIPLIST_LENGTH(zl) = 0;// 尾部结束符zl[bytes-1] = ZIP_END;// 返回地址指针return zl;
}

进行里面的api引用进行阅读

⭐️ZIPLIST_HEADER_SIZE也就是头部信息大小

// unit32_t:32bit
// uint16_t:16bit
// (32/8) * 2 + 16/8 = 8 + 2 = 10byte
// 头大小定义为10大小的字节
#define ZIPLIST_HEADER_SIZE     (sizeof(uint32_t)*2+sizeof(uint16_t))

⭐️​zmalloc,也就是对内存分配的算法,底层是glibc的malloc源码,可看我另一篇malloc源码讲解

intrev32ifbe,这里是将小端序转换为大端序,统一数据类型的字节序

🐱字节序:

当使用int时,你会用4byte进行存储,在内存里,分为高低地址,那么int里你的高位是在内存里的高地址,还是低地址,所以出现了字节序这个概念。

在这里插入图片描述

⭐️ZIPLIST_TAIL_OFFSET:用来获取 ziplist 尾部元素相对于 ziplist 开头的偏移量。

// (zl):  unsigned char *zl 传入时指定
// uint32_t:32bit = 32/8 = 4byte
// 将传入的zl,进行获取(zl)地址,偏移4byte,获取末尾地址
// 然后将该末尾地址转化为uinit32_t类型的指针,然后进行解引用获取该地址里的值
// 这里获取的是ziplist结构体里的prevrawlen属性
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))

⭐️ZIPLIST_LENGTH

// (zl):  unsigned char *zl 传入时指定
// 将zl指针偏移32 * 2bit大小
// 然后转换为uint16_t类型的指针
// 然后解引用获取该地址里的值
// 这里获取的是ziplist结构体里的lensize属性
#define ZIPLIST_LENGTH(zl)      (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))

⭐️ zl[bytes-1] = ZIP_END;

// 将zl的末尾值,存储为一个255的数字,告诉redis,这个ziplist的末尾标志位 
zl[bytes-1] = ZIP_END;

3.3 创建对象

// 指定对象类型是list,并且将刚才创建的空的ziplist指针放入 
robj *o = createObject(REDIS_LIST,zl);

⭐️对象类型

// redis.h(src) - 139 line/* Object types */
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4

⭐️创建对象源码

robj *createObject(int type, void *ptr) {// 分配内存robj *o = zmalloc(sizeof(*o));// 指定数据类型o->type = type;// 指定编码o->encoding = REDIS_ENCODING_RAW;// 指定指针o->ptr = ptr;// 使用次数o->refcount = 1;/* Set the LRU to the current lruclock (minutes resolution). */o->lru = server.lruclock;return o;
} 	

3.4 字符编码指定

// 指定该对象的类型编码是压缩链表的字符编码
o->encoding = REDIS_ENCODING_ZIPLIST;

⭐️字符编码类型

// redis.h(src) - 139 line/* Objects encoding. Some kind of objects like Strings and Hashes can be* internally represented in multiple ways. The 'encoding' field of the object* is set to one of this fields for this object. */
#define REDIS_ENCODING_RAW 0     /* Raw representation */
#define REDIS_ENCODING_INT 1     /* Encoded as integer */
#define REDIS_ENCODING_HT 2      /* Encoded as hash table */
#define REDIS_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
#define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define REDIS_ENCODING_INTSET 6  /* Encoded as intset */
#define REDIS_ENCODING_SKIPLIST 7  /* Encoded as skiplist */

4. 总结

​ 通过上面的描述,清晰的看出ziplist的由来,组成,以及如何创建一个ziplist对象,以及部分源码的理解

​ 如果读者想了解更多的ziplist更多源码,可阅读ziplist.c源码,里面涵盖了插入,删除等等api的实现

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

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

相关文章

【Java数据结构】二叉树相关算法

第一题&#xff1a;获取二叉树中结点个数 得到二叉树结点个数&#xff0c;如果结点为空则返回0&#xff0c;然后再用递归计算左树结点个数根结点&#xff08;1个&#xff09;右树结点个数。 public int nodeSize(Node root){if (root null)return 0;return nodeSize1(root.l…

PCM5142集成32位384kHz PCM音频立体声114dB差分输出DAC编解码芯片

目录 PCM5142 简介PCM5142功能框图PCM5142特性 参考原理图 PCM5142 简介 PCM514x 属于单片 CMOS 集成电路系列&#xff0c;由立体声数模转换器 (DAC) 和采用薄型小外形尺寸 (TSSOP) 封装的附加支持电路组成。PCM514x 使用 TI 最新一代高级分段 DAC 架构产品&#xff0c;可实现…

debian中apt的配置与解析

引言 在系统使用过程中&#xff0c;我们可能会遭遇 apt update 操作出现问题&#xff0c;或者 apt upgrade 速度迟缓的情况。这往往是由于所使用软件源本身存在诸如服务器性能不佳、维护不及时等质量问题&#xff0c;同时&#xff0c;软件源服务器与我们所处地理位置的距离较远…

深度学习 Pytorch 张量的线性代数运算

pytorch中并未设置单独的矩阵对象类型&#xff0c;因此pytorch中&#xff0c;二维张量就相当于矩阵对象&#xff0c;并且拥有一系列线性代数相关函数和方法。 在实际机器学习和深度学习建模过程中&#xff0c;矩阵或者高维张量都是基本对象类型&#xff0c;而矩阵所涉及到的线…

ESP8266-01S、手机、STM32连接

1、ESP8266-01S的工作原理 1.1、AP和STA ESP8266-01S为WIFI的透传模块&#xff0c;主要模式如下图&#xff1a; 上节说到&#xff0c;我们需要用到AT固件进行局域网应用&#xff08;ESP8266连接的STM32和手机进行连接&#xff09;。 ESP8266为一个WiFi透传模块&#xff0c;和…

简历_基于 Cache Aside 模式解决数据库与缓存一致性问题。

系列博客目录 文章目录 系列博客目录缓存更新策略总结案例&#xff1a;给查询商铺的缓存添加超时剔除和主动更新的策略 说到解决数据库与缓存一致性的问题&#xff0c;其实就是要解决缓存更新的问题。 缓存更新策略 业务场景: 低一致性需求:使用内存淘汰机制。例如店铺类型的…

XML在线格式化 - 加菲工具

XML在线格式化 打开网站 加菲工具 选择“XML 在线格式化” 输入XML&#xff0c;点击左上角的“格式化”按钮 得到格式化后的结果

python学opencv|读取图像(三十八 )阈值自适应处理

【1】引言 前序学习了5种阈值处理方法&#xff0c;包括(反)阈值处理、(反)零值处理和截断处理&#xff0c;相关文章链接为&#xff1a; python学opencv|读取图像&#xff08;三十三&#xff09;阈值处理-灰度图像-CSDN博客 python学opencv|读取图像&#xff08;三十四&#…

数据可视化:让数据讲故事的艺术

目录 1 前言2 数据可视化的基本概念2.1 可视化的核心目标2.2 传统可视化手段 3 数据可视化在知识图谱中的应用3.1 知识图谱的可视化需求3.2 知识图谱的可视化方法 4 数据可视化叙事&#xff1a;让数据讲故事4.1 叙事可视化的关键要素4.2 数据可视化叙事的实现方法 5 数据可视化…

vue | 插值表达式

Vue 是一个用于 构建用户界面 的 渐进式 框架 1. 构建用户界面&#xff1a;基于 数据 动态 渲染 页面 2. 渐进式&#xff1a;循序渐进的学习 3. 框架&#xff1a;一套完整的项目解决方案&#xff0c;提升开发效率↑ (理解记忆规则) 插值表达式&#xff1a; 插值表达式是一种 Vu…

单片机存储器和C程序编译过程

1、 单片机存储器 只读存储器不是并列关系&#xff0c;是从ROM发展到FLASH的过程 RAM ROM 随机存储器 只读存储器 CPU直接存储和访问 只读可访问不可写 临时存数据&#xff0c;存的是CPU正在使用的数据 永久存数据&#xff0c;存的是操作系统启动程序或指令 断电易失 …

二、点灯基础实验

嵌入式基础实验第一个就是点灯&#xff0c;地位相当于编程界的hello world。 如下为LED原理图&#xff0c;要让相应LED发光&#xff0c;需要给I/O口设置输出引脚&#xff0c;低电平&#xff0c;二极管才会导通 2.1 打开初始工程&#xff0c;编写代码 以下会实现BLINKY常亮&…

豆包MarsCode:构造特定数组的逆序拼接

问题描述 思路分析 1. 数组的组成&#xff1a; 我们要根据 i 的不同值拼接出不同长度的子数组。对于每个 i 从 1 到 n&#xff0c;我们要把数字从 n 逆序到 i 拼接成一个子数组。 例如&#xff0c;当 i 1 时&#xff0c;拼接 [n, n-1, ..., 1]。当 i 2 时&#xff0c;拼接 …

RK3588平台开发系列讲解(NPU篇)NPU 驱动的组成

文章目录 一、NPU 驱动组成二、查询 NPU 驱动版本三、查询 rknn_server 版本四、查询 librknn_runtime 版本沉淀、分享、成长,让自己和他人都能有所收获!😄 一、NPU 驱动组成 NPU 驱动版本、rknn_server 版本、librknn_runtime 版本以及 RKNN Toolkit 版本的对应关系尤为重…

论文阅读:CosAE Learnable Fourier Series for Image Restoration

这是 2024 NeurIPS 上发表的一篇文章&#xff0c;介绍了一种新型的基于傅里叶级数的通用编码器。 Abstract 本文介绍了余弦自动编码器&#xff08;Cosine Autoencoder, CosAE&#xff09;&#xff0c;这是一种新颖的通用自动编码器&#xff0c;它将经典傅里叶级数与前馈神经网…

YOLOv11改进,YOLOv11检测头融合RepConv卷积,并添加小目标检测层(四头检测),适合目标检测、分割等任务

摘要 作者提出了一种简单而强大的卷积神经网络架构,其推理阶段采用与 VGG 类似的网络体结构,仅由一堆 3x3 卷积和 ReLU 组成,而训练阶段的模型具有多分支拓扑。这种训练阶段和推理阶段架构的解耦通过结构重参数化技术实现,因此我们将该模型命名为 RepVGG。 # 理论介绍 Re…

深度学习笔记——循环神经网络RNN

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍面试过程中可能遇到的循环神经网络RNN知识点。 文章目录 文本特征提取的方法1. 基础方法1.1 词袋模型&#xff08;Bag of Words, BOW&#xff09;工作原…

Selenium工具使用Python 语言实现下拉框定位操作

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 我们通常遇到的下拉框有显性的下拉框和隐性的下拉框&#xff1b;有的下拉框还可以进行单选或多选操作&#xff0c;在selenium中如何实现下拉框的定位通常使用selec…

使用 Continue 插件时,发现调用外部地址

https://us.i.posthog.com/e/?ip1&_1737025525924&ver1.163.0&compressiongzip-js 看是一个帮助改善产品的网址。估计类似某推广流量监控的插件工具吧。网上没用查到其他说明&#xff0c;可能国内使用不多的原因。 但是发送的数据看不出来是个什么内容。 我用来搜…

【PyQt】图像处理系统

[toc]pyqt实现图像处理系统 图像处理系统 1.创建阴影去除ui文件 2.阴影去除代码 1.创建阴影去除ui文件 UI文件效果图&#xff1a; 1.1QT Desiger设置组件 1.两个Pushbutton按钮 2.两个label来显示图像 3.Text Browser来显示输出信息 1.2布局的设置 1.先不使用任何La…