2019独角兽企业重金招聘Python工程师标准>>>

翻开书第一篇就是介绍sds的,sds代码简单但结构设计非常精妙,通过内存上的操作硬是把C字符串改造成了符合日常操作的sds了。具体的代码细节不做细述,只记录体会,毕竟需要看真正的代码才能明白,而且本文章主要还是给自己一个回忆,自己看过就好,另外就是夹杂一些天马行空的内容帮助理解。

sds.h/.c的代码总结起来有这几点:内存对齐、内存偏移。
sds的好处则有这几点:杜绝溢出、内存预分配、兼容C字符串。

首先说内存对齐和内存偏移,这两者是结合在一起的,或者说内存偏移是基于内存对齐的。sds结构追求性能,所以内存操作是少不了的,但需要面临一个内存对齐的问题,否则内存偏移会出现差错,在sdshdr的定义了如下模式:

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[];
};

对于__attribute__ ((__packed__)),这是设置内存对齐的一种方法,效果和#pragma pack一样,可以参考下面的代码:

#include <iostream>
using namespace std;struct student
{char name[7];int id; char subject[5];
} __attribute__ ((aligned(4)));struct teacher
{char name[7];int id; char subject[5];
} __attribute__ ((packed));#pragma pack(4)
struct studentA
{char name[7];int id; char subject[5];
};
#pragma pack()#pragma pack(1)
struct teacherA
{char name[7];int id; char subject[5];
};
#pragma pack()int main(int argc, char* argv[])
{cout<<"student: "<<sizeof(student)<<endl;cout<<"teacher: "<<sizeof(teacher)<<endl;cout<<"studentA: "<<sizeof(studentA)<<endl;cout<<"teacherA: "<<sizeof(teacherA)<<endl;
}

sds中定义的代码主要作用是取消默认的内存对齐,用变量的实际长度来存储,这样整个sds结构中的各个变量都是互相挨着的,才使得用s[-1]来定位flags变量成为可能,否则难免进行更加复杂的偏移计算。内存对齐有两个好处,一个是平台移植,一个是加速CPU读取,本来我还有个疑问:sds取消了内存对齐,难免会影响cpu的访问性能。但仔细看过之后发现多虑了:redis结构体设计的非常好,前面2个变量是len和alloc,都是8的整数倍,在32位或者64位CPU中基本都能一并读取,而flags是一个unsigned char,不管如何都占据一次加载到寄存器的时间,所以个人感觉不会影响CPU的读取性能。

static inline size_t sdslen(const sds s) {unsigned char flags = s[-1];switch(flags&SDS_TYPE_MASK) {case SDS_TYPE_5:return SDS_TYPE_5_LEN(flags);case SDS_TYPE_8:return SDS_HDR(8,s)->len;case SDS_TYPE_16:return SDS_HDR(16,s)->len;case SDS_TYPE_32:return SDS_HDR(32,s)->len;case SDS_TYPE_64:return SDS_HDR(64,s)->len;}return 0;
}

之后,大部分的函数操作都会与内存偏移发生联系,例如下面这两个宏在代码中大量使用:

#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))

再来说说内存溢出和预分配:在C字符串中,当发生strcat的时候,需要由程序员保证足够的内存空间,一旦内存不足,就会发生溢出;另外一方面,则需要反复的malloc或者realloc进行内存分配,影响系统的性能。在sds中,则不会发生此类事情,以sdscat函数为例,其内部调用了sdscatlen,而在sdscatlen内部又通过sdsMakeRoomFor进行预分配内存,避免了频繁的内存分配操作:

sds sdscatlen(sds s, const void *t, size_t len) {size_t curlen = sdslen(s);s = sdsMakeRoomFor(s,len);if (s == NULL) return NULL;memcpy(s+curlen, t, len);sdssetlen(s, curlen+len);s[curlen+len] = '\0';return s;
}

再说说sdsMakeRoomFor方法,其实也没啥内涵,就是根据传参进行内存分配,当需要的内存小于1M时,则按照2倍大小分配,否则直接给1M,同时还会重新检查header类型,决定用realloc还是malloc进行操作:

sds sdsMakeRoomFor(sds s, size_t addlen) {void *sh, *newsh;size_t avail = sdsavail(s);size_t len, newlen;char type, oldtype = s[-1] & SDS_TYPE_MASK;int hdrlen;if (avail >= addlen) return s;len = sdslen(s);sh = (char*)s-sdsHdrSize(oldtype);newlen = (len+addlen);if (newlen < SDS_MAX_PREALLOC)//SDS_MAX_PREALLOC大小是1Mnewlen *= 2;elsenewlen += SDS_MAX_PREALLOC;type = sdsReqType(newlen);if (type == SDS_TYPE_5) type = SDS_TYPE_8;hdrlen = sdsHdrSize(type);if (oldtype==type) { //类型不变newsh = s_realloc(sh, hdrlen+newlen+1);if (newsh == NULL) return NULL;s = (char*)newsh+hdrlen;} else { //类型发生变化,导致sds header大小变化newsh = s_malloc(hdrlen+newlen+1);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);}sdssetalloc(s, newlen);return s;
}

这边有个地方需要注意,在SDS_HDR_VAR的定义中使用了强转,把void*转换成了sdshdr*,这种转换在C中是可以的,但在C++中就会报错,属于C和C++语言层面的不同。

在3.2.6版本中,sds自带了main函数用来测试,只需要编译时加上-DSDS_TEST_MAIN,基本覆盖了全部函数了,这非常不错,要赞一个。

总的来说,sds非常不错,特别是对于header的设计之处值得学习,不过对于sds本身来说能用的地方应该是非常底层的,要结合具体项目和团队成员背景使用,也需要再度定制过之后才能用到项目中。

我拆分了redis中的sds相关代码,可以到https://github.com/dodomouse/RedisStructure.git中下载。

参考:
http://zhangtielei.com/posts/blog-redis-sds.html

转载于:https://my.oschina.net/dodomouse/blog/1825610

Redis源码解析1:SDS--完美的C字符串替代相关推荐

  1. Redis源码解析——双向链表

    相对于之前介绍的字典和SDS字符串库,Redis的双向链表库则是非常标准的.教科书般简单的库.但是作为Redis源码的一部分,我决定还是要讲一讲的.(转载请指明出于breaksoftware的csdn ...

  2. Redis源码解析——前言

    今天开启Redis源码的阅读之旅.对于一些没有接触过开源代码分析的同学来说,可能这是一件很麻烦的事.但是我总觉得做一件事,不管有多大多难,我们首先要在战略上蔑视它,但是要在战术上重视它.除了一些高大上 ...

  3. Redis源码解析——字典基本操作

    有了<Redis源码解析--字典结构>的基础,我们便可以对dict的实现进行展开分析.(转载请指明出于breaksoftware的csdn博客) 创建字典 一般字典创建时,都是没有数据的, ...

  4. Redis源码解析——内存管理

    在<Redis源码解析--源码工程结构>一文中,我们介绍了Redis可能会根据环境或用户指定选择不同的内存管理库.在linux系统中,Redis默认使用jemalloc库.当然用户可以指定 ...

  5. Redis源码解析(15) 哨兵机制[2] 信息同步与TILT模式

    Redis源码解析(1) 动态字符串与链表 Redis源码解析(2) 字典与迭代器 Redis源码解析(3) 跳跃表 Redis源码解析(4) 整数集合 Redis源码解析(5) 压缩列表 Redis ...

  6. Redis源码解析——有序整数集

    有序整数集是Redis源码中一个以大尾(big endian)形式存储,由小到大排列且无重复的整型集合.它存储的类型包括16位.32位和64位的整型数.在介绍这个库的实现前,我们还需要先熟悉下大小尾内 ...

  7. Redis源码解析——Zipmap

    本文介绍的是Redis中Zipmap的原理和实现.(转载请指明出于breaksoftware的csdn博客) 基础结构 Zipmap是为了实现保存Pair(String,String)数据的结构,该结 ...

  8. Redis源码解析——字典结构

    C++语言中有标准的字典库,我们可以通过pair(key,value)的形式存储数据.但是C语言中没有这种的库,于是就需要自己实现.本文讲解的就是Redis源码中的字典库的实现方法.(转载请指明出于b ...

  9. redis源码解析(二)——SDS(简单动态字符串)

    版本:redis - 5.0.4 参考资料:redis设计与实现 文件:src下的sds.c sds.h 一.c语言中的字符串 vs SDS 二.sds.h 1.sdshdr header sds的存 ...

  10. Redis源码解析(1)——源码目录介绍

    概念 redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合)和zset(有序集合).这些 ...

最新文章

  1. iOS摄像头和相册-UIImagePickerController-浅析(转)
  2. python 类可以调用实例变量_python中的实例方法、静态方法、类方法、类变量和实例变量浅析...
  3. 【David Silver强化学习公开课】-4:Model-Free Prediction
  4. windows 8   远程桌面(RemoteFX )
  5. 算法转换c语言程序,(转)C语言实现卡尔曼滤波算法程序
  6. 腾讯云mysql架构_一个数据库存储架构的独白
  7. 360浏览器 - 自定义切换内核 meta标签 总结
  8. 数据:以太坊2.0合约余额新增1.52万ETH
  9. Flink API之Source入门
  10. itextSharp 附pdf文件解析
  11. 阿里部署edas,采用hsf框架,启动项目时无法启动的问题。
  12. python做算法题优势_算法优点和缺点汇总(推荐AAA)
  13. MF前传——探索者一号液晶屏接线
  14. 数据走进宫崎骏的动画世界
  15. PDF文件去除页边距空白
  16. python随机森林库_随机森林算法入门(python)
  17. hadoop存储与分析
  18. 说说在CMD命令行模式下ADB命令显示为不是内部或外部命令,亦不是可运行程序和批处理文件的解决办法
  19. LHS查询和RHS查询
  20. (C语言打印图形题5)编程,输入n,输出如下例(n=4)所示的高和上底均为n的等腰梯形。

热门文章

  1. 【技术开发】酒精测试仪解决方案开发设计
  2. Docker容器热迁移技术(基于CRIU实现)
  3. 浅谈Jsch SSH2
  4. ENVI5.3对Landsat8 OLI影像数据进行辐射矫正、辐射定标(保姆级教程)
  5. vultr 云主机收不到来自客户端的udp数据包
  6. 信息收集之外网信息收集
  7. Clk Gating的verilog写法及简析-基础小知识(六)
  8. Linux 设置开机自启动程序
  9. Cisco Packet Tracer的简单使用
  10. SUSE配置zypper