Redis源代码分析-内存数据结构intset-阿里云开发者社区

开发者社区> 技术mix呢> 正文

Redis源代码分析-内存数据结构intset

简介:
+关注继续查看

这次研究了一下intset。研究的过程中,一度看不下过去,可是还是咬牙挺过来了。看懂了也就是那么回事。静下心来,切莫浮躁

Redis为了追求高效,在存储下做了非常多的优化,像intset就是作者为了节约内存定制的数据结构,包含后面将要阅读的压缩列表。

intset是一个有序的整数集,提供了添加,删除,查找的接口,针对uint16_t uint32_t uint64_t,提供了不同编码的转换(严格的说仅仅是类型的提升)

首先。看一下它的结构定义:

typedef struct intset {                                                                                                                                                
    uint32_t encoding;                                                                                                                                                 
    uint32_t length;                                                                                                                                                                                 
    int8_t contents[];                                                                                                                          
} intset;
encoding:有例如以下几种编码

#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))
实际上这里使用一个uint8_t存储就够了

length:当前整数集有多少个整数

contents[]:详细存储的位置。这里以一个字节为存储单元,方便对高类型进行寻址

看一下它对外提供的接口:

intset *intsetNew(void); 
intset *intsetAdd(intset *is, int64_t value, uint8_t *success);                                         
intset *intsetRemove(intset *is, int64_t value, int *success);                                          
uint8_t intsetFind(intset *is, int64_t value);                                                          
int64_t intsetRandom(intset *is);
uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value);                                            
uint32_t intsetLen(intset *is);
size_t intsetBlobLen(intset *is); 
一种数据结构。必定要提供类似插入。查询。删除这种接口。另外不要暴露内部使用的接口,这里提供的接口,我们详细分析几个

初始化接口:

/* Create an empty intset. */
intset *intsetNew(void) {
    intset *is = malloc(sizeof(intset));
    is->encoding = intrev32ifbe(INTSET_ENC_INT16);
    is->length = 0;
    return is; 
}
没什么难的,注意默认使用最低的2字节存储

/* Insert an integer in the intset */
intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
    uint8_t valenc = _intsetValueEncoding(value);
    uint32_t pos;
    if (success) *success = 1;

    /* Upgrade encoding if necessary. If we need to upgrade, we know that
     * this value should be either appended (if > 0) or prepended (if < 0),
     * because it lies outside the range of existing values. */
    if (valenc > intrev32ifbe(is->encoding)) {
        /* This always succeeds, so we don't need to curry *success. */
        return intsetUpgradeAndAdd(is,value);
    } else {
        /* Abort if the value is already present in the set.
         * This call will populate "pos" with the right position to insert
         * the value when it cannot be found. */
        if (intsetSearch(is,value,&pos)) {
            if (success) *success = 0;
            return is;
        }

        is = intsetResize(is,intrev32ifbe(is->length)+1);
        if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);
    }

    _intsetSet(is,pos,value);
    is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
    return is;
}

这个接口比較有难度。详细分析:

1、首先推断要添加的值的编码是否大于当前编码,大于则进行类型提升。并添加value

2、假设小于当前编码,首先查询数据是否存在,存在则返回,不存在则设置插入位置pos

3、又一次分配内存大小

4、移动数据。全部数据往后移动。复杂度有点高啊

5、插入数据,设置数据个数

当中。类型提升并插入value的接口例如以下:

/* Upgrades the intset to a larger encoding and inserts the given integer. */
static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
    uint8_t curenc = intrev32ifbe(is->encoding);
    uint8_t newenc = _intsetValueEncoding(value);
    int length = intrev32ifbe(is->length);
    int prepend = value < 0 ? 1 : 0;

    /* First set new encoding and resize */
    is->encoding = intrev32ifbe(newenc);
    is = intsetResize(is,intrev32ifbe(is->length)+1);

    /* Upgrade back-to-front so we don't overwrite values.
     * Note that the "prepend" variable is used to make sure we have an empty
     * space at either the beginning or the end of the intset. */
    while(length--)
        _intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));

    /* Set the value at the beginning or the end. */
    if (prepend)
        _intsetSet(is,0,value);
    else
        _intsetSet(is,intrev32ifbe(is->length),value);
    is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
    return is;
}
能够看到,类型提升的步骤例如以下:

1、由于整数集是有序的,所以首先推断要加入的数是正数还是负数,正数就在尾部加入,负数则在头部加入

2、添加内存大小

3、移动数据,这里和第一步挂钩。并且移动的过程比較难以理解,首先依据原来编码取出数据,然后依据新的编码插入数据

4、插入数据。在头部还是尾部插入

5、改动数据个数


另外移动数据的接口例如以下:

static void intsetMoveTail(intset *is, uint32_t from, uint32_t to) {
    void *src, *dst;
    uint32_t bytes = intrev32ifbe(is->length)-from;
    uint32_t encoding = intrev32ifbe(is->encoding);

    if (encoding == INTSET_ENC_INT64) {
        src = (int64_t*)is->contents+from;
        dst = (int64_t*)is->contents+to;
        bytes *= sizeof(int64_t);
    } else if (encoding == INTSET_ENC_INT32) {
        src = (int32_t*)is->contents+from;
        dst = (int32_t*)is->contents+to;
        bytes *= sizeof(int32_t);
    } else {
        src = (int16_t*)is->contents+from;
        dst = (int16_t*)is->contents+to;
        bytes *= sizeof(int16_t);
    }
    memmove(dst,src,bytes);
}
由于是连续的内存,找到移动的起始位置,然后memmove(),bingo!

!!


查找数据的接口实现:

static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
    int min = 0, max = intrev32ifbe(is->length)-1, mid = -1;
    int64_t cur = -1;

    /* The value can never be found when the set is empty */
    if (intrev32ifbe(is->length) == 0) {
        if (pos) *pos = 0;
        return 0;
    } else {
        /* Check for the case where we know we cannot find the value,
         * but do know the insert position. */
        if (value > _intsetGet(is,intrev32ifbe(is->length)-1)) {
            if (pos) *pos = intrev32ifbe(is->length);
            return 0;
        } else if (value < _intsetGet(is,0)) {
            if (pos) *pos = 0;
            return 0;
        }
    }

    while(max >= min) {
        mid = ((unsigned int)min + (unsigned int)max) >> 1;
        cur = _intsetGet(is,mid);
        if (value > cur) {
            min = mid+1;
        } else if (value < cur) {
            max = mid-1;
        } else {
            break;
        }
    }

    if (value == cur) {
        if (pos) *pos = mid;
        return 1;
    } else {
        if (pos) *pos = min;
        return 0;
    }
} 

还是个二分查找,niubility!!

!个人感觉这样的数据结构的高效就体如今这里。由于是有序。所以查找高速,由于是数组。所以插入。删除。是连续内存拷贝,也非常快

有时间突然想去看一下STL Vector的实现了,它的insert是怎样实现的?






本文转自mfrbuaa博客园博客,原文链接:http://www.cnblogs.com/mfrbuaa/p/5265268.html,如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
redis数据结构、持久化、缓存淘汰策略
redis数据结构、持久化、缓存淘汰策略Redis 单线程高性能,它所有的数据都在内存中,所有的运算都是内存级别的运算,而且单线程避免了多线程的切换性能损耗问题。redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,依次放到文件事件分派器,事件分派器将事件分发给事件处理器。
931 0
IntelliJ IDEA 设置代码提示或自动补全的快捷键
对于中国的Java开发者来说,可能使用Eclipse的人最多。 使用Idea的程序员也不少, 而且每个人都在鼓吹其好用之处。 试用半个月,感觉各有千秋,关键看熟练程度和配置是否好用。
1175 0
Intellij IDEA 设置代码着色
从visual studio到Eclipse再到Intllij IDEA,其实每个编译器都有自己的风格,但是就美观上来说,个人觉得还是visual studio的代码着色比较美观,exlipse次之,idea真的没有美观而言,虽然它的功能很强大,于是自己更改了idea的代码着色,下面几张图示,希望你...
637 0
内存数据库
        内存数据库,顾名思义就是将数据放在内存中直接操作的数据库。相对于磁盘,内存的数据读写速度要高出几个数量级,将数据保存在内存中相比从磁盘上访问能够极大地提高应用的性能。同时,内存数据库抛弃了磁盘数据管理的传统方式,基于全部数据都在内存中重新设计了体系结构,并且在数据缓存、快速算法、并行操作方面也进行了相应的改进,所以数据处理速度比传统数据库的数据处理速度要快很多,一般都在10
1251 0
+关注
2969
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载