84366992 发表于 2015-7-21 07:37:16

Redis 增加intset的降级特性

  Redis的数据结构非常丰富,比如实现了内存紧凑型的数据结构:intset、ziplist等. Redis 2.3.16版本只提供了数据升级功能(比如16bit->32bit等)。
  然而这种存在一个问题就,增加1个大数据,然后删除此大数据后,那么可能存在内存浪费现象,比如:ADD N 个16bit的数据,再ADD 1个64bit的数据,
  再删除此64bit的数据,那么浪费的空间大小为:N*(64-16)bits。不知道redis里面为什么没有增加降级功能,而且代码量和复杂度都不高,也许intset的
  使用都是在数据规模小的时候才用吧。所以我增加了降级功能(degrade)。
  思路:类似于升级功能,升级是通过判断添加的数据的编码和intset头中存储的当前编码来判断是否需要升级,因为intset是有序的(小->大),所以此升
  级的Value要么插在contents最后(length),要么在最前(0).
  同样的道理,检测是通过intset里面有多少个64bit的数据和32bit的数据来决策的。比如:intset里面此时有1个64bit的数据,有非0个32bit的数据,要
  删除这个64bit的数据,那么就需要降级。所以在intset的结构里增加num64bit、 num32bit两个即可。(不增加num16bit的字段,不是可以通过length
  算出来,而是根本不需要此字段,减少内存的消耗)。
  修改inset.h和inset.c文件,具体如下:
  1、intset.h
  intset的结构中增加num64bit、 num32bit字段,用来判断是否需要降级.
  而且在intset.c的功能代码中增加了调试日志代码,可通过编译宏来控制。





1 #ifndef DEGRADE
2   #define DEGRADE 1
3 #endif
4
5 #ifndef INTSET_DEBUG
6   #define INTSET_DEBUG
7 #endif
8
9 typedef struct intset {
10   uint32_t encoding;
11   uint32_t length;
12 #ifdef DEGRADE
13   uint32_t num64bit;
14   uint32_t num32bit;
15 #endif   
16   int8_t contents[];
17 } intset;
View Code   2、intset.c
  (1)intsetNew
  修改函数,初始化新增字段值0.





1 /* Create an empty intset. */
2 intset *intsetNew(void) {
3   intset *is = zmalloc(sizeof(intset));
4   is->encoding = intrev32ifbe(INTSET_ENC_INT16);
5   is->length = 0;
6 #ifdef DEGRADE
7   is->num32bit = 0;
8   is->num64bit = 0;
9 #endif // DEGRADE
10   return is;
11 }
View Code   
  (2)_intsetSetTypeNum
  新增函数,根据add和remove来增加和减少num64bit、 num32bit值。





1 #ifdef DEGRADE
2 static void _intsetSetTypeNum(intset* is,uint8_t enc,int change)
3 {
4   if(enc == INTSET_ENC_INT32)
5   {
6      is->num32bit = intrev32ifbe(intrev32ifbe(is->num32bit) + change);
7   }
8   else if(enc == INTSET_ENC_INT64)
9   {
10          is->num64bit = intrev32ifbe(intrev32ifbe(is->num64bit) + change);
11   }
12 }
13 #endif
View Code   
  (3)intsetUpgradeAndAdd和intsetAdd
  修改函数,升级增加和普通增加,需要增加num64bit、 num32bit值。





1 /* Upgrades the intset to a larger encoding and inserts the given integer. */
2 static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
3   uint8_t curenc = intrev32ifbe(is->encoding);
4   uint8_t newenc = _intsetValueEncoding(value);
5   int length = intrev32ifbe(is->length);
6   int prepend = value < 0 ? 1 : 0;
7
8   /* First set new encoding and resize */
9   is->encoding = intrev32ifbe(newenc);
10   is = intsetResize(is,intrev32ifbe(is->length)+1);
11
12   /* Upgrade back-to-front so we don't overwrite values.
13      * Note that the "prepend" variable is used to make sure we have an empty
14      * space at either the beginning or the end of the intset. */
15   while(length--)
16         _intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));
17
18   /* Set the value at the beginning or the end. */
19   if (prepend)
20         _intsetSet(is,0,value);
21   else
22         _intsetSet(is,intrev32ifbe(is->length),value);
23   is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
24 #ifdef DEGRADE
25   _intsetSetTypeNum(is,newenc,1);
26 #endif
27 #ifdef INTSET_DEBUG
28   redisLog(3,"after intsetUpgradeAndAdd func, num:%lld,lenght:%d,32bitnum:%d,64bitnum:%d,encoding:%d\n",
29            value,intrev32ifbe(is->length),intrev32ifbe(is->num32bit),intrev32ifbe(is->num64bit),
30            intrev32ifbe(is->encoding));
31 #endif
32   return is;
33 }
34
35 /* Insert an integer in the intset */
36 intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
37   uint8_t valenc = _intsetValueEncoding(value);
38   uint32_t pos;
39   if (success) *success = 1;
40
41   /* Upgrade encoding if necessary. If we need to upgrade, we know that
42      * this value should be either appended (if > 0) or prepended (if < 0),
43      * because it lies outside the range of existing values. */
44   if (valenc > intrev32ifbe(is->encoding)) {
45         /* This always succeeds, so we don't need to curry *success. */
46         return intsetUpgradeAndAdd(is,value);
47   } else {
48         /* Abort if the value is already present in the set.
49          * This call will populate "pos" with the right position to insert
50          * the value when it cannot be found. */
51         if (intsetSearch(is,value,&pos)) {
52             if (success) *success = 0;
53             return is;
54         }
55
56         is = intsetResize(is,intrev32ifbe(is->length)+1);
57         if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);
58   }
59
60   _intsetSet(is,pos,value);
61   is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
62 #ifdef DEGRADE
63   _intsetSetTypeNum(is,valenc,1);
64 #endif
65
66 #ifdef INTSET_DEBUG
67   redisLog(3,"after intsetAdd func, num:%lld,lenght:%d,32bitnum:%d,64bitnum:%d,encoding:%d\n",
68            value,intrev32ifbe(is->length),intrev32ifbe(is->num32bit),intrev32ifbe(is->num64bit),
69            intrev32ifbe(is->encoding));
70 #endif
71   return is;
72 }
View Code   
  (4)isDegradeAndRemove
  新增函数,判断删除1个数据时,是否需要降级.





1 /**< want to delete value whose type is enc. */
2 static uint8_t isDegradeAndRemove(intset* is,uint64_t value, uint8_t *degradeEnc)
3 {
4
5   uint8_t enc = _intsetValueEncoding(value);
6   if(intrev32ifbe(is->length)>1&&intsetSearch(is,value,NULL))
7   {
8         if((enc == INTSET_ENC_INT64) && (intrev32ifbe(is->num64bit) == 1))
9         {
10             if(intrev32ifbe(is->num32bit)>0)
11             {
12               if(degradeEnc)*degradeEnc = INTSET_ENC_INT32;
13               return 1;
14             }
15             else
16             {
17               if(degradeEnc)*degradeEnc = INTSET_ENC_INT16;
18               return 1;
19             }
20         }
21         else if((enc == INTSET_ENC_INT32) && (intrev32ifbe(is->num32bit) == 1))
22         {
23             if(intrev32ifbe(is->num64bit) == 0)
24             {
25               if(degradeEnc)*degradeEnc = INTSET_ENC_INT16;
26               return 1;
27             }
28         }
29   }
30   return 0;
31 }
View Code   
  (5)intSetDegradeAndRemove
  新增函数,删除降级





1 static intset *intSetDegradeAndRemove(intset *is, int64_t value, uint8_t degradeEnc)
2 {
3   uint8_t curEnc = intrev32ifbe(is->encoding);
4   int length = intrev32ifbe(is->length);
5   uint8_t loop,temp,prepend = value>0?0:1;
6
7   //first:set the encode
8   is->encoding = intrev32ifbe(degradeEnc);
9 #if 0
10   if(!prepend)//delete +
11   {
12         for(loop=0;looplength),intrev32ifbe(is->num32bit),intrev32ifbe(is->num64bit),
36            intrev32ifbe(is->encoding));
37 #endif
38   
39   return is;
40
41 }
View Code   
  (6)intsetRemove
  修改函数,增加了删除降级代码和普通删除操作,需要减少num64bit、 num32bit值。





1 /* Delete integer from intset */
2 intset *intsetRemove(intset *is, int64_t value, int *success) {
3   uint8_t valenc = _intsetValueEncoding(value);
4   uint32_t pos;
5   if (success) *success = 0;
6
7 #ifdef DEGRADE
8   uint8_t degrade;
9   if(isDegradeAndRemove(is,value,&degrade))
10   {
11         if(success) *success = 1;
12         return intSetDegradeAndRemove(is,value,degrade);
13   }
14 #endif
15   if (valenc encoding) && intsetSearch(is,value,&pos)) {
16         uint32_t len = intrev32ifbe(is->length);
17
18         /* We know we can delete */
19         if (success) *success = 1;
20
21         /* Overwrite value with tail and update length */
22         if (pos < (len-1)) intsetMoveTail(is,pos+1,pos);
23         is = intsetResize(is,len-1);
24         is->length = intrev32ifbe(len-1);
25 #ifdef DEGRADE
26         _intsetSetTypeNum(is,valenc,-1);
27 #ifdef INTSET_DEBUG
28         redisLog(3,"after intsetRemove func, num:%lld,lenght:%d,32bitnum:%d,64bitnum:%d,encoding:%d\n",
29            value,intrev32ifbe(is->length),intrev32ifbe(is->num32bit),intrev32ifbe(is->num64bit),
30            intrev32ifbe(is->encoding));
31 #endif
32 #endif   
33   }
34   return is;
35 }
View Code   总结:刚学习redis,发现里面丰富的数据结构深深的吸引住了我,学习intset的时候,发现没有降级功能(当然redis对intset的entry使用做了限制,默认不超过512个),自己就手动加了这个功能。
  也希望对redis的源码感兴趣的同学,能动手实践一下。抽空把在linux下的实验结果贴出来,可以明显看见内存是消耗是减少的,而且代码里面还增加了调试,可以看出数据编码和插入删除数据
  的变化。
  ps:其实5.23号写了这篇文章,今天修改了些内容,重新发布一下。
页: [1]
查看完整版本: Redis 增加intset的降级特性