redis 5.0.7 源碼閱讀——整數集合intset

来源:https://www.cnblogs.com/chinxi/archive/2020/02/05/12262901.html
-Advertisement-
Play Games

redis中整數集合intset相關的文件為:intset.h與intset.c intset的所有操作與操作一個排序整形數組 int a[N]類似,只是根據類型做了記憶體上的優化。 一、數據結構 1 typedef struct intset { 2 uint32_t encoding; 3 uin ...


redis中整數集合intset相關的文件為:intset.h與intset.c

intset的所有操作與操作一個排序整形數組 int a[N]類似,只是根據類型做了記憶體上的優化。

一、數據結構

1 typedef struct intset {
2     uint32_t encoding;
3     uint32_t length;
4     int8_t contents[];
5 } intset;

intset的數據結構比較簡單,使用了一個變長結構體,成員length記錄當前成員數量,成員encoding記錄當前的int類型,共有以下三種:

1 #define INTSET_ENC_INT16 (sizeof(int16_t))
2 #define INTSET_ENC_INT32 (sizeof(int32_t))
3 #define INTSET_ENC_INT64 (sizeof(int64_t))

並使用以下方法進行判斷類型:

1 static uint8_t _intsetValueEncoding(int64_t v) {
2     if (v < INT32_MIN || v > INT32_MAX)
3         return INTSET_ENC_INT64;
4     else if (v < INT16_MIN || v > INT16_MAX)
5         return INTSET_ENC_INT32;
6     else
7         return INTSET_ENC_INT16;
8 }

intset是已排序好的整數集合,其大致結構如下:

1 /*
2 +--------+--------+--------...--------------+
3 |encoding|length  |contents(encoding*length)|
4 +--------+--------+--------...--------------+
5 */

intset嚴格按照小端位元組序進行存儲,不論機器的位元組序類型。如果是大端機器,需要進行轉換,才進行存儲。endianconv.h中有如下定義:

 1 #if (BYTE_ORDER == LITTLE_ENDIAN)
 2 #define memrev16ifbe(p) ((void)(0))
 3 #define memrev32ifbe(p) ((void)(0))
 4 #define memrev64ifbe(p) ((void)(0))
 5 #define intrev16ifbe(v) (v)
 6 #define intrev32ifbe(v) (v)
 7 #define intrev64ifbe(v) (v)
 8 #else
 9 #define memrev16ifbe(p) memrev16(p)
10 #define memrev32ifbe(p) memrev32(p)
11 #define memrev64ifbe(p) memrev64(p)
12 #define intrev16ifbe(v) intrev16(v)
13 #define intrev32ifbe(v) intrev32(v)
14 #define intrev64ifbe(v) intrev64(v)
15 #endif

具體實現在endianconv.c中,此處略過。

 

二、創建

1 intset *intsetNew(void) {
2     intset *is = zmalloc(sizeof(intset));
3     is->encoding = intrev32ifbe(INTSET_ENC_INT16);
4     is->length = 0;
5     return is;
6 }

剛創建好的intset是空的,預設使用最小的類型。其結構為:

1 /*此處用一根“-”表示一位元組,後同
2 +----+----+
3 |  16|   0|
4 +----+----+
5 */

 

三、 操作

若有以下intset:

1 /*
2 +----+----+--+--+--+--+--+--+--+
3 |  16|   7| 1| 2| 3| 4| 5| 7| 8|
4 +----+----+--+--+--+--+--+--+--+
5           |contents
6 
7 */

現在插入一個數字6,需要調用以下方法:

 1 /* Insert an integer in the intset */
 2 intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
 3     uint8_t valenc = _intsetValueEncoding(value);
 4     uint32_t pos;
 5     if (success) *success = 1;
 6 
 7     /* Upgrade encoding if necessary. If we need to upgrade, we know that
 8      * this value should be either appended (if > 0) or prepended (if < 0),
 9      * because it lies outside the range of existing values. */
10     if (valenc > intrev32ifbe(is->encoding)) {
11         /* This always succeeds, so we don't need to curry *success. */
12         return intsetUpgradeAndAdd(is,value);
13     } else {
14         /* Abort if the value is already present in the set.
15          * This call will populate "pos" with the right position to insert
16          * the value when it cannot be found. */
17         if (intsetSearch(is,value,&pos)) {
18             if (success) *success = 0;
19             return is;
20         }
21 
22         is = intsetResize(is,intrev32ifbe(is->length)+1);
23         if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);
24     }
25 
26     _intsetSet(is,pos,value);
27     is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
28     return is;
29 }

因int16_t足以存儲數字“6”,所以新插入數字的int類型與intset一致,然後需要查找插入的pos:

 1 static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
 2     int min = 0, max = intrev32ifbe(is->length)-1, mid = -1;
 3     int64_t cur = -1;
 4 
 5     /* The value can never be found when the set is empty */
 6     if (intrev32ifbe(is->length) == 0) {
 7         if (pos) *pos = 0;
 8         return 0;
 9     } else {
10         /* Check for the case where we know we cannot find the value,
11          * but do know the insert position. */
12         if (value > _intsetGet(is,max)) {
13             if (pos) *pos = intrev32ifbe(is->length);
14             return 0;
15         } else if (value < _intsetGet(is,0)) {
16             if (pos) *pos = 0;
17             return 0;
18         }
19     }
20 
21     while(max >= min) {
22         mid = ((unsigned int)min + (unsigned int)max) >> 1;
23         cur = _intsetGet(is,mid);
24         if (value > cur) {
25             min = mid+1;
26         } else if (value < cur) {
27             max = mid-1;
28         } else {
29             break;
30         }
31     }
32 
33     if (value == cur) {
34         if (pos) *pos = mid;
35         return 1;
36     } else {
37         if (pos) *pos = min;
38         return 0;
39     }
40 }

因intset是已排序好的,所以使用了二分查找。過程如下

 1 /*
 2 find 6
 3         +----+----+--+--+--+--+--+--+--+
 4         |  16|   7| 1| 2| 3| 4| 5| 7| 8|
 5         +----+----+--+--+--+--+--+--+--+
 6 pos               | 0| 1| 2| 3| 4| 5| 6|
 7 step1             |min=0              
 8                                     |max=6
 9                            |mid=(0+6)>>1=3
10                            |mid_val=4
11                         
12 pos               | 0| 1| 2| 3| 4| 5| 6|
13 step2                         |min=4
14                                     |max=6
15                                  |mid=(4+6)>>1=5
16                                  |mid_val=7
17 
18 pos               | 0| 1| 2| 3| 4| 5| 6|
19 step3                         |min=4
20                               |max=4
21                               |mid=(4+4)>>1=5
22                               |mid_val=5
23 
24 pos               | 0| 1| 2| 3| 4| 5| 6|
25 step4                            |min=5
26                               |max=4
27 min>max  break
28 */

6在intset中不存在,查找到需要插入到pos=5的位置,此時首先要擴展intset的content:

1 static intset *intsetResize(intset *is, uint32_t len) {
2     uint32_t size = len*intrev32ifbe(is->encoding);
3     is = zrealloc(is,sizeof(intset)+size);
4     return is;
5 }

擴展後:

1 /*        
2 +----+----+--+--+--+--+--+--+--+--+
3 |  16|   7| 1| 2| 3| 4| 5| 7| 8|  |
4 +----+----+--+--+--+--+--+--+--+--+
5 pos       | 0| 1| 2| 3| 4| 5| 6| 7|
6 */

然後把原來在pos=5及之後的所有的元素向後移一格:

 1 static void intsetMoveTail(intset *is, uint32_t from, uint32_t to) {
 2     void *src, *dst;
 3     uint32_t bytes = intrev32ifbe(is->length)-from;
 4     uint32_t encoding = intrev32ifbe(is->encoding);
 5 
 6     if (encoding == INTSET_ENC_INT64) {
 7         src = (int64_t*)is->contents+from;
 8         dst = (int64_t*)is->contents+to;
 9         bytes *= sizeof(int64_t);
10     } else if (encoding == INTSET_ENC_INT32) {
11         src = (int32_t*)is->contents+from;
12         dst = (int32_t*)is->contents+to;
13         bytes *= sizeof(int32_t);
14     } else {
15         src = (int16_t*)is->contents+from;
16         dst = (int16_t*)is->contents+to;
17         bytes *= sizeof(int16_t);
18     }
19     memmove(dst,src,bytes);
20 }

移動後:

1 /*        
2 +----+----+--+--+--+--+--+--+--+--+
3 |  16|   7| 1| 2| 3| 4| 5| 7| 7| 8|
4 +----+----+--+--+--+--+--+--+--+--+
5 pos       | 0| 1| 2| 3| 4| 5| 6| 7|
6 */

其使用memmove,並不全修改未覆蓋到的記憶體,所以此時pos=5的值 還是7

最後修改pos=5的值:

 1 static void _intsetSet(intset *is, int pos, int64_t value) {
 2     uint32_t encoding = intrev32ifbe(is->encoding);
 3 
 4     if (encoding == INTSET_ENC_INT64) {
 5         ((int64_t*)is->contents)[pos] = value;
 6         memrev64ifbe(((int64_t*)is->contents)+pos);
 7     } else if (encoding == INTSET_ENC_INT32) {
 8         ((int32_t*)is->contents)[pos] = value;
 9         memrev32ifbe(((int32_t*)is->contents)+pos);
10     } else {
11         ((int16_t*)is->contents)[pos] = value;
12         memrev16ifbe(((int16_t*)is->contents)+pos);
13     }
14 }

修改後並增加了length:

1 /*        
2 +----+----+--+--+--+--+--+--+--+--+
3 |  16|   8| 1| 2| 3| 4| 5| 6| 7| 8|
4 +----+----+--+--+--+--+--+--+--+--+
5 pos       | 0| 1| 2| 3| 4| 5| 6| 7|
6 */

 

如果此時要插入的數字是65536,超出了int16_t所能表示的範圍,要先進行擴展int類型操作:

 1 static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
 2     uint8_t curenc = intrev32ifbe(is->encoding);
 3     uint8_t newenc = _intsetValueEncoding(value);
 4     int length = intrev32ifbe(is->length);
 5     int prepend = value < 0 ? 1 : 0;
 6 
 7     /* First set new encoding and resize */
 8     is->encoding = intrev32ifbe(newenc);
 9     is = intsetResize(is,intrev32ifbe(is->length)+1);
10 
11     /* Upgrade back-to-front so we don't overwrite values.
12      * Note that the "prepend" variable is used to make sure we have an empty
13      * space at either the beginning or the end of the intset. */
14     while(length--)
15         _intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));
16 
17     /* Set the value at the beginning or the end. */
18     if (prepend)
19         _intsetSet(is,0,value);
20     else
21         _intsetSet(is,intrev32ifbe(is->length),value);
22     is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
23     return is;
24 }

因其超出原來的int類型所能表示的範圍,若為正數,一定是最大的,則應該插入在intset最後,否則應該在最前面。擴展完之後,從後往前將原來的數字,以新的int類型,放置在新的位置上,保證不會有未處理的數字被覆蓋,處理完整。

 

刪除操作:

 1 intset *intsetRemove(intset *is, int64_t value, int *success) {
 2     uint8_t valenc = _intsetValueEncoding(value);
 3     uint32_t pos;
 4     if (success) *success = 0;
 5 
 6     if (valenc <= intrev32ifbe(is->encoding) && intsetSearch(is,value,&pos)) {
 7         uint32_t len = intrev32ifbe(is->length);
 8 
 9         /* We know we can delete */
10         if (success) *success = 1;
11 
12         /* Overwrite value with tail and update length */
13         if (pos < (len-1)) intsetMoveTail(is,pos+1,pos);
14         is = intsetResize(is,len-1);
15         is->length = intrev32ifbe(len-1);
16     }
17     return is;
18 }

找到指定元素之後,直接把後面的記憶體移至前面,然後resize。

 

redis 5.0.7 下載鏈接

http://download.redis.io/releases/redis-5.0.7.tar.gz

源碼閱讀順序參考:

https://github.com/huangz1990/blog/blob/master/diary/2014/how-to-read-redis-source-code.rst

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前面痞子衡講過嵌入式里的堆棧原理,本篇算是堆棧原理的工程實踐,更具體點說是在ARM Cortex-M上的應用。ARM Cortex-M家族發展至今已經很多代,我們且以最簡單的Cortex-M0為例來講述堆棧機制 ...
  • 棧這種結構在嵌入式里其實是非常常用的,比如函數調用與返回就是典型的棧應用,雖然很多時候棧都是CPU系統在自動管理,我們只需要在鏈接文件里分配棧大小以及棧存放位置,但稍微瞭解一下棧的原理會更加利於我們去理解嵌入式代碼執行機制,以及幫助我們進一步去調試。 ...
  • 錯誤信息:./configure: error: C compiler cc is not found解決方案:yum -y install gcc gcc-c++ autoconf automake make 錯誤信息:./configure: error: the HTTP rewrite mo ...
  • 0. 本blog 簡單說明一下 Linux測試環境尤其是 CentOS測試環境的開發測試使用, 教程可能不會很長, 主要是入門. 0.1 Linux簡介: Linux 的歷史基本上不用闡述, linus作為自己的興趣愛好進行編碼實現的一種開源的操作系統. Linux很好的切合了GNU裡面一直沒有可用 ...
  • 1.flink運行時的組件 ​ Flink 運行時架構主要包括四個不同的組件,它們會在運行流處理應用程式時協同工作: 作業管理器(JobManager)、資源管理器(ResourceManager)、任務管理器(TaskManager), 以及分發器(Dispatcher)。因為 Flink 是用 ...
  • 在消費Kafka中分區的數據時,我們需要跟蹤哪些消息是讀取過的、哪些是沒有讀取過的。這是讀取消息不丟失的關鍵所在。Kafka是通過offset順序讀取事件的。如果一個消費者退出,再重啟的時候,它知道從哪兒繼續讀取消息進行處理。所以,消費者需要「提交」屬於它們自己的偏移量。如果消費者已經提交了偏移量,... ...
  • 首先看一下我的基本的開發環境: 操作系統:MacOS 10.13.5 編輯器:IDEA 2018.3 其他:MySQL8.0.15、Maven 3.3.9、JDK 1.8 好,下麵就正式開始: 第一步:在IDEA中新建一個maven項目 1.使用骨架創建maven項目,此處選擇:maven-arch ...
  • 2020-02-05 mysqli擴展 phpl連接Mysql mysqli_connect($servername,$username,$password,$database,$port); //參數1:連接的主機 參數2:資料庫登錄賬號 參數3:資料庫登錄密碼 參數4:將要連接的資料庫 參數5: ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...