Redisソース読み取りノート-sds

8673 ワード

Redisシステムでは,文字列をより完全にカプセル化し,動的文字列を構築し,実用的なapiを大量に構築した.関連する実装コードはsdsである.h及びsds.c,以下は私のソースコードのためにノートを読みます.内容が多く,逐次更新する
typedef char *sds;

struct __attribute__ ((__packed__)) sdshdr5 {
    usigned char flags;
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len;
    uint8_t alloc;
    unsigned char flags;
    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)
以上は、動的文字列の構造体宣言およびdefine宣言の関数です.動的文字列は全部で5種類あり、それぞれ異なる長さの文字列で実用的です.ここでは動的文字列のヘッダと呼びます.
sdshdr 5:長さ32未満の文字列
sdshdr 8:256未満の長さの文字列
sdshdr 16:長さが2^16未満の文字列
sdshdr 32:長さが2^32未満の文字列.ここでちょっと注意したいのは、機械のLONG_MAXbuはLLONGに等しくないMAXは、sdshdr 64タイプを返します.
sdshdr 64:他のすべての長さが実用的です.
sdshdr 5というタイプは他のタイプとは異なり、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の4つのクラスがある.
len:文字列の長さ.
alloc:文字列メモリの合計サイズ.allocはlenとは異なることに注意してください.lenは実際の文字列の長さであり、allocは実際に割り当てられたメモリサイズ(sdsヘッダと末尾の'0'のサイズを含まない)である.文字列コンテンツの増加時にメモリの再申請を繰り返すのを減らすために、redisでは使用のためにより多くのメモリが申請されます.文字列サイズが1 MB未満の場合、2倍のサイズのメモリ使用を申請し、文字列サイズが1 MB以上の場合、1 MBのメモリを多く申請して使用に備える.詳細な設定は、その後のsdsMakeRoomFor関数の解析を見ることができます.
flags:異なるタイプを区別するタグとして使用され、一時的に低3ビットのみがタグに使用され、高5ビットは一時的に使用されず、後で新しい機能を追加する場合にも使用されます.上のコードでdefineが宣言したSDS_TYPE_*タイプは、異なる文字列タイプを区別するために使用される対応するタグコンテンツです.例えばflagsがSDS_に等しいTYPE_8の場合、文字列の先頭から17バイト先、または文字列の先頭から8バイトのデータを取得して現在の文字列の実際の長さを取得できます.等しいSDS_TYPE_16の場合、文字列開始バイトから33バイト先、または文字列ヘッダから16自身が現在の文字列の実際の長さを取得する.flagsとヘッダ情報の組み合わせは,その後の関数解析に大量に現れる.
buf:実際に文字列の内容を格納する配列で、従来の配列と同様に、末尾に'0'文字が必要です.
sdshdrの声明では、__が表示されます.attribute__ ((__packed__)) キーワードです.これにより、この構造体はメモリ内で文字列の整列規則を守らず、メモリをコンパクトに並べます.したがって、buf[−1]のようなflags情報は、文字列位置から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ファイル内の静的関数で、これらの関数は動的文字列ヘッダの属性の取得と修正に対して、簡単で分かりやすい.
//         
static	inline	size_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;
}

//            
static inline size_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;
}

//       
static inline void 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;
	}
}

//      ,       
static inline void sdsinclen(sds s, size_t inc) {
	unsigned char flags = s[-1];//  flags
	switch(flags&SDS_TYPE_MASK) {
		//SDS_TYPE_5         ,    、  、  
		case SDS_TYPE_5:
			{
				unsigned char *fp = ((unsigned char*)s)-1;
				unsigned char newlen = SDS_TYPE_LEN(flags)+inc;
				*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
			}
			break;
		//         SDS_HDR  ,  len 
		case SDS_TYPE_8:
			SDS_HDR(8,s)->len += inc;
			break;
		case SDS_TYPE_16:
			SDS_HDR(16,s)->len += inc;
			break;
		case SDS_TYPE_32:
			SDS_HDR(32,s)->len += inc;
			break;
		case SDS_TYPE_64:
			SDS_HDR(64,s)->len += inc;
			break;
	}
}

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

//         
static inline size_t sdssetalloc(sds s, size_t newlen) {
	unsigned cahr flags = s[-1];//  flags
	switch(flags&SDS_TASK_MASK) {
		case SDS_TYPE_5:
			//    ,SDS_TYPE_5      
			/*Nothing to do, this type has no total allocation info. */
			break;
		//              alloc  
		case SDS_TYPE_8:
			SDS_HDR(8,s)->alloc = newlen;
			break;
		case SDS_TYPE_16:
			SDS_HDR(16,s)->alloc = newlen;
			break;
		case SDS_TYPE_32:
			SDS_HDR(32,s)->alloc = newlen;
			break;
		case SDS_TYPE_64:
			SDS_HDR(64,s)->alloc = newlen;
			break;
	}
}

上記のいくつかの関数は、
sdslen,
sdsavail,
sdssetlen,
sdsinclen,
sdsalloc,
sdssetalloc関数は、基本的なヘッダ属性操作関数です.コードの難易度も大きくなく、直感的に読むことができ、理解することができます.
次のsdsの関連apiは、数が少し多く、その後のdict、zskiplistも大量のapiがあり、一部のコードを選ぶことが多く、行ごとに理解する関数を記録、分析する必要がある.
sdsnewlen:
//      sds  
sds sdsnewlen(const void *init, size_t initlen) {
	void *sh;
	sds s;
	char type = sdsReqType(initlen);//                
	if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;//    0      SDS_TYPE_8  
	int hdrlen = sdsHdrSize(type);
	unsigned char *fp;
	
	sh = s_malloc(hdrlen+initlen+1);//       ,    +     +   
	if (!init)
		memset(sh, 0, hdrlen+initlen+1);
	if (sh == NULL) return NULL;
	s = (char*)sh+hdrlen;//         
	fp = ((unsigned char*)s)-1;//  flags  
	switch(type) {
		//SDS_TYPE_5       ,         ,       。。。
		case SDS_TYPE_5: {
			*fp = type | (initlen << SDS_TYPE_BITS);
			break;
		}
		//      SDS_HDR_VAR        ,      
		case SDS_TYPE_8: {
			SDS_HDR_VAR(8,s);
			sh->len = initlen;
			sh->alloc = initlen;
			*fp = type;
			break;
		}
		case SDS_TYPE_16: {
			SDS_HDR_VAR(16,s);
			sh->len = initlen;
			sh->alloc = initlen;
			*fp = type;
			break;
		}
		case SDS_TYPE_32: {
			SDS_HDR_VAR(32,s);
			sh->len = initlen;
			sh->alloc = initlen;
			*fp = type;
			break;
		}
		case SDS_TYPE_64: {
			SDS_HDR_VAR(64,s);
			sh->len = initlen;
			sh->alloc = initlen;
			*fp = type;
			break;
		}
	}
	//  init initlen,         
	if (initlen && init)
		memcpy(s, init, initlen);
	//     
	s[initlen] = '\0';
	return s;
}

sdsnewlenは、パラメータによって与えられたinit文字列とinitlenの初期長に基づいて、動的文字列を生成し、返します.コードに注釈がつけられているので、読むのはもう難しくありません.