Redis原始碼閱讀筆記—sds
Redis系統當中,針對字串進行的更加完善的封裝,建立了一個動態字串,並構建了大量的實用api。相關的實現程式碼為sds.h及sds.c,以下為我的原始碼閱讀筆記。內容較多,逐步更新
- typedefchar *sds;
- struct __attribute__ ((__packed__)) sdshdr5 {
- usigned char flags;
- char buf[];
- };
- struct __attribute__ ((__packed__)) sdshdr8 {
- uint8_t len;
- uint8_t alloc;
- unsigned char
- char buf[];
- };
- struct __attribute__ ((__packed__)) sdshdr16 {
- uint16_t len;
- uint16_t alloc;
- unsigned char flags;
- char buf[];
- };
- struct __attribute__ ((__packed__)) sdshdr32 {
- uint32_t len;
- uint32_t alloc;
- unsigned char flags;
- char buf[];
- };
- struct __attribute__ ((__packed__)) sdshdr64 {
- uint64_t len;
- uint64_t alloc;
- unsigned char flags;
- char buf[];
- };
- #define SDS_TYPE_5 0
- #define SDS_TYPE_8 1
- #define SDS_TYPE_16 2
- #define SDS_TYPE_32 3
- #define SDS_TYPE_64 4
- #define SDS_TYPE_MASK 7
- #define SDS_TYPE_BITS 3
- #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))))
- #define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
sdshdr5:長度為小於32的字串
sdshdr8:長度為小於256的字串
sdshdr16:長度為小於2^16的字串
sdshdr32:長度為小於2^32的字串。這裡有一點注意,若是機器的LONG_MAXbu不等於LLONG_MAX,則返回sdshdr64型別。
sdshdr64:其他所有長度都實用此類。
sdshdr5這個型別不同於其他型別,它缺少len成員與alloc成員,它的判斷與處理都比較特別。但是官方在程式碼有過一段註釋,如下:
/* Note: sdshdr5 is never used, we just access the flags byte directly.
* However is here to document the layout of type 5 SDS strings. */
註釋中說明,這個型別從未被使用,所以我們在這裡姑且不考慮它,而實際上,它的處理操作本質上與其他型別並沒有什麼區別。方便起見,我們以通用的型別進行研究。
結構體當中,有四個類,分別為len、alloc、flags與buf。
len:字串的長度。
alloc:字串記憶體總大小,注意,alloc不同於len。len是實際字串的長度,而alloc,則是實際分配的記憶體大小(不包含sds頭與結尾的'\0'的大小)。為了減少字串內容增加時反覆的重新申請記憶體,redis當中會申請更多記憶體以備使用。當字串大小小於1MB的時候,申請兩倍大小的記憶體使用,當字串大小大於等於1MB的時候,多申請1MB的記憶體以備使用。詳細的設定可以看之後的sdsMakeRoomFor函式的解析。
flags:作為區分不同型別的標記使用,暫時只使用了低3位來標記,高5位暫未使用,也供以後增加新功能時使用。上面程式碼中define宣告的SDS_TYPE_*型別為相應的標記內容,用於區分不同的字串型別。比如flags等於SDS_TYPE_8時,則可以從字串開始位元組在向前17位元組,或者字串頭部開始獲取8位元組資料獲取當前字串的實際長度。等於SDS_TYPE_16時,從字串開始位元組向前33位元組,或者字串頭部開始獲取16自己獲取當前字串的實際長度。flags與頭部資訊的配合使用,將在之後的函式解析裡面大量出現。
buf:實際儲存字串內容的陣列,同傳統陣列一樣,結尾需要'\0'字元。
在sdshdr的聲明當中,我們可以看到 __attribute__ ((__packed__)) 關鍵字,這將使這個結構體在記憶體中不再遵守字串對齊規則,而是以記憶體緊湊的方式排列。所以可以從字串位置開始向前一個位元組便可以獲取flags資訊了,比如buf[-1]。具體__attribute__ ((__packed__))與字串對齊的內容請檢視另一篇部落格。
SDS_HDR_VAR函式則通過結構體型別與字串開始位元組,獲取到動態字串頭部的開始位置,並賦值給sh指標。SDS_HDR函式則通過型別與字串開始位元組,返回動態字串頭部的指標。使用方式為可在之後的程式碼當中看到,具體define宣告中的雙'#'號的使用方式與意義,請看草另一篇部落格。
sds比起傳統的字串,有著自己優勢跟便利之處。
1、記憶體預分配:sds通過預先分配了更多的記憶體量,來避免頻繁的申請、分配記憶體,無端的浪費了效能
2、惰性釋放:sds作為普通字串使用之時,可以通過在不同位元組打上'\0'字元,來代表字串的截斷及減少長度,而不是需要清空多餘的位元組並釋放它們,那些記憶體再之後的操作當中可以當做空閒記憶體繼續使用。
3、二進位制安全:作為非字串使用儲存資料之時,通過善用頭部的len屬性,可以儲存一些包含'\0'字元的資料。當然,一定要善用len屬性,api當中,如長度更新的函式,同樣通過'\0'字元來判斷結尾!
接下來開始介紹sds相關的api函式,第一批是宣告、定義在sds.h檔案內的靜態函式,這些函式都是針對動態字串頭部的屬性的獲取與修改,簡單易懂。
- //獲取動態字串長度
- staticinlinesize_t sdslen(const sds s) {
- unsigned char flags = s[-1];//獲取頭部資訊中的flags屬性,因記憶體緊密相連,可以直接通過這種方式獲取
- switch(flags&SDS_TASK_MASK) {//獲取型別,SDS_TASK_MASK為7,所以flags&SDS_TASK_MASK等於flags
- case SDS_TYPE_5:
- return SDS_TYPE_5_LEN(flags);//SDS_TYPE_5型別的長度獲取稍微不同,它的長度被定義在flags的高5位當中,具體可檢視之後的sdsnewlen函式,或者下面的sdssetlen函式
- case SDS_TYPE_8:
- return SDS_HDR(8,s)->len;//SDS_HDR函式通過型別與字串開始位元組獲取頭部,以此獲取字串的長度
- 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;
- }
- //獲取動態字串的剩餘記憶體
- staticinlinesize_t sdsavail(const sds s) {
- unsigned char flags = s[-1];//獲取flags
- switch(flags&SDS_TYPE_MASK) {
- case SDS_TYPE_5://SDS_TYPE_5直接返回0,
- return 0;
- case SDS_TYPE_8: {
- SDS_HDR_VAR(8,s);//通過SDS_HDR_VAR函式,將頭部指標放置在sh變數
- return sh->alloc - sh->len;//總記憶體大小 - 字串長度,獲取可用記憶體大小
- }
- case SDS_TYPE_16: {
- SDS_HDR_VAR(16,s);
- return sh->alloc - sh->len;
- }
- case SDS_TYPE_32: {
- SDS_HDR_VAR(32,s);
- return sh->alloc - sh->len;
- }
- case SDS_TYPE_64: {
- SDS_HDR_VAR(64,s);
- return sh->alloc - sh-len;
- }
- }
- return 0;
- }
- //重置字串長度
- staticinlinevoid sdssetlen(sds s, size_t newlen) {
- unsigned char flags = s[-1];//獲取flags
- switch(flags&SDS_TASK_MASK) {
- //SDS_TYPE_5的長度設定較為特殊,長度資訊寫在flags的高5位
- case SDS_TYPE_5:
- {
- unsigned char *fp = ((unsigned char*)s)-1;
- *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
- }
- break;
- //其他型別則是統一修改len屬性的值
- case SDS_TYPE_8:
- SDS_HDR(8,s)->len = newlen;
- break;
- case SDS_TYPE_16:
- SDS_HDR(16,s)->len = newlen;
- break;
- case SDS_TYPE_32:
- SDS_HDR(32,s)->len = newlen;
- break;
- case SDS_TYPE_64:
- SDS_HDR(64,s)->len = newlen;
- break
相關推薦
Redis原始碼閱讀筆記—sds
Redis系統當中,針對字串進行的更加完善的封裝,建立了一個動態字串,並構建了大量的實用api。相關的實現程式碼為sds.h及sds.c,以下為我的原始碼閱讀筆記。內容較多,逐步更新typedefchar *sds; struct __attribute__ ((__pac
Redis原始碼閱讀筆記--資料庫redisDb
一. 資料庫 Redis的資料庫使用字典作為底層實現,資料庫的增、刪、查、改都是構建在字典的操作之上的。 redis伺服器將所有資料庫都儲存在伺服器狀態結構redisServer(redis.h/redisServer)的db陣列(應該是一個連結串列)裡:
redis原始碼閱讀筆記-dict.h
dict.h 在redis中,dict.h主要是hash的底層實現方式。 在dict.h中主要是一些資料結構的定義,以及一些巨集函式的定義相關的內容。 雜湊節點定義 原始碼中的dictEntry就是雜湊節點的相關定義 typedef struct dictEnt
原始碼閱讀筆記——Tablib
文章目錄 Tablib 用法示例 程式碼結構 程式碼結構與風格 細節 Reference Tablib Tablib是一個支援多格式資料轉換的庫,支援的格式包括XLSX、XLS、JSON、YA
原始碼閱讀筆記——HowDoI
文章目錄 HowDoI 用法示例 程式碼結構 主要依賴 程式碼結構與風格 細節 Reference HowDoI HowDoI是命令列應用,用以搜尋程式設計問題的答案,原始碼十分簡短,
Redis原始碼閱讀(六)叢集-故障遷移(下)
Redis原始碼閱讀(六)叢集-故障遷移(下) 最近私人的事情比較多,沒有抽出時間來整理部落格。書接上文,上一篇裡總結了Redis故障遷移的幾個關鍵點,以及Redis中故障檢測的實現。本篇主要介紹叢集檢測到某主節點下線後,是如何選舉新的主節點的。注意到Redis叢集是無中心的,那麼使用分散式一
jdk原始碼閱讀筆記-LinkedHashMap
Map是Java collection framework 中重要的組成部分,特別是HashMap是在我們在日常的開發的過程中使用的最多的一個集合。但是遺憾的是,存放在HashMap中元素都是無序的,原因是我們在put或get資料的時候都是根據key的hash值來確定元素的位置。在具體的業務場景中,我們更
jdk原始碼閱讀筆記-ArrayList
一、ArrayList概述 首先我們來說一下ArrayList是什麼?它解決了什麼問題?ArrayList其實是一個數組,但是有區別於一般的陣列,它是一個可以動態改變大小的動態陣列。ArrayList的關鍵特性也是這個動態的特性了,ArrayList的設計初衷就是為了解決Java陣列長度不可變的
.NetCore原始碼閱讀筆記系列之HttpAbstractions(五) Authentication
說道認證&授權其實這塊才是核心,這款跟前面Security這塊有者緊密的聯絡,當然 HttpAbstractions 不光是認證、授權、還包含其他Http服務和中間價 接下來先就認證這塊結合前面的Security作一個補充說明 前面 AddCookie 、OpenIdConnect、Go
Redis原始碼閱讀(四)叢集-請求分配
叢集搭建好之後,使用者傳送的命令請求可以被分配到不同的節點去處理。那Redis對命令請求分配的依據是什麼?如果節點數量有變動,命令又是如何重新分配的,重分配的過程是否會阻塞對外提供的服務?接下來會從這兩個問題入手,分析Redis3.0的原始碼實現。 1. 分配依據—
mxnet原始碼閱讀筆記之include
寫在前面 mxnet程式碼的規範性比Caffe2要好,看起來核心程式碼量也小很多,但由於對dmlc其它庫的依賴太強,程式碼的獨立性並不好。依賴的第三方庫包括: cub dlpack dmlc-core googletest mkldnn mshadow onnx-tensorrt openmp ps-lite
JAVA 10原始碼閱讀筆記之JEP-307(G1的並行Full GC)
# 1. 背景 JEP-307解決了G1垃圾回收器的一個嚴重的問題,截止到Java 9,G1的Full GC採用的是單執行緒演算法,嚴重影響效能,無法利用到多核能力進行垃圾回收。JEP-307修復了此問題,發生Full GC時允許使用多個執行緒進行並行回收。 # 2. G1
base_local_planner原始碼閱讀筆記
ROS的navigation stack中區域性規劃器的介面plugin類為nav_core::BaseLocalPlanner,給出的包有4種: base_local_planner - 提供了 D
hashMap原始碼閱讀筆記1.7
這幾天一直在看hashMap的原始碼,也借鑑了很多大佬的文章以便更好的理解,也從大佬文章中借鑑了很多的內容,如果侵權,請告知,我將立刻刪除。 hashMap繼承AbstractMap,實現了map介面。 public class HashMap<K,V> extends
Disruptor原始碼閱讀筆記
Disruptor是什麼 關於 Disruptor,網路上有很多的解釋和說法。這裡簡單的概括下。Disruptor 是一個消費者生產者佇列框架,據官網介紹,可以提供非常強大的效能。Disruptor 與其說為我們帶來了一個框架,更多的是為我們帶來了一個獨特思路的程式設計實踐
spark原始碼閱讀筆記Dataset(二)Dataset中Actions、function、transformations
package Dataset import org.apache.spark.sql.functions._ import org.apache.spark.sql.{DataFrame, Dataset, SparkSession} /** * Cr
Flask 原始碼閱讀筆記 開篇
Flask 是一個 Python 實現的 Web 開發微框架, 有豐富的生態資源。本文從一段官方的示例程式碼通過一步步打斷點方式解釋 Flask 內部的執行機制,在一些關鍵概念會有相關解釋,這些前提概念對整體理解 Flask框架十分重要,本文基於flask 0
jdk原始碼閱讀筆記-LinkedList
一、LinkedList概述 LinkedList的底層資料結構為雙向連結串列結構,與ArrayList相同的是LinkedList也可以儲存相同或null的元素。相對於ArrayList來說,LinkedList的插入與刪除的速度更快,時間複雜度為O(1),查詢的速度就相對比較慢了,因為每次遍歷的時
spark原始碼閱讀筆記Dataset(三)structField、structType、schame
StructType(fields: Seq[StructField]) 一個StructType物件,可以有多個StructField,同時也可以用名字(name)來提取,就想當於Map可以用key來提取value,但是他StructType提取的是整條欄位的資訊 在原始碼中structType是一個cas
jvm原始碼閱讀筆記[3]:從記憶體分配到觸發GC的細節
除了第一篇說到的,對於使用cms回收的應用,會有執行緒輪詢判斷老年代是否滿足GC的條件,若滿足,則會觸發一次cms老年代的回收。 針對年輕代,更常見的是,執行緒優先在eden區分配物件的時候,若eden區空間不足,則會觸發一次y