Redis下位データ構造のジャンプテーブル
18349 ワード
Redis下位データ構造のジャンプテーブル
1.引用
ジャンプテーブルはWilliam Pughによって1990年に発明され、秩序あるデータ構造であり、バランスツリー、赤黒ツリーのようなデータ構造に類似しており、秩序あるリストを維持することができ、検索しやすい.ジャンプテーブルは平均O(logn)の時間複雑度ルックアップをサポートし,これは平衡ツリーにほぼ匹敵するが,なぜRedisの著者らはジャンプテーブルを選択して秩序化集合を実現したのか.以下の点から説明します.挿入と削除:バランシングツリーの調整操作はいずれもバランシングを引き起こすノードから上へ調整し、一連の左右回転操作でバランシングを達成する必要があるが、ジャンプテーブルは隣接ノードのポインタを変更するだけでよい点は後述の実装説明では に現れる.メモリ占有量:バランスツリーの各ノードはほぼ2つのポインタを占有するが、ジャンプテーブルのポインタ数はグローバルパラメータPに依存し、ポインタ数N=(1-p)/4、RedisはP=1/4を使用するため、平均1.33ポインタ を占有する.実現難易度:ジャンプテーブルの実現難易度はバランスツリーよりずっと簡単で、検索時間の複雑度はほぼ同等で、いずれもO(logn) である.
1.一般ジャンプ表の性質
まず、ジャンプテーブルの直感的な表現を示します.ジャンプテーブルは多くの層からなり、各層は秩序チェーンテーブル である.の最下層にはすべての要素が含まれており、1つの要素が現れるlevel iではlevel iの下のチェーンテーブルにも が表示されます.各ノードは、同じ層のチェーンテーブルを指す次の要素 を指す2つのポインタを含む.
通常のジャンプテーブル実装コード(注釈付き):
2.Redisにおけるジャンプテーブルの実現
zskiplistNodeの定義
zskiplist定義
図解:
性質の説明:複数のノードは同じscoreを含むことができるが、各ノードのメンバーオブジェクトは一意の である必要がある.ジャンプテーブルはscoreのサイズに従って小さいから大きいまで並べ替えられ、スコアが同じである場合、ノードはメンバーオブジェクトのサイズに従って 並べ替えられる.
具体的な実装コードはT_zset.cファイル中
まず、作成と初期化の操作です.
挿入関数zslInsertを見てください:
関数を削除する
基本的な削除と挿入操作に加えて、Redisジャンプテーブルには、指定ノードのテーブル内の順位zslGetRank関数を取得したり、一定の条件を満たす順位を取得したりするなど、多くの操作が提供されています.ここから、Redisジャンプテーブルにスパンフィールドを設定するメリットがあり、迅速に順位を計算できることがわかります.具体的な実現関数はここでは一つ一つ紹介しませんが、興味のある方はソースコードを参照して調べることができます.
1.引用
ジャンプテーブルはWilliam Pughによって1990年に発明され、秩序あるデータ構造であり、バランスツリー、赤黒ツリーのようなデータ構造に類似しており、秩序あるリストを維持することができ、検索しやすい.ジャンプテーブルは平均O(logn)の時間複雑度ルックアップをサポートし,これは平衡ツリーにほぼ匹敵するが,なぜRedisの著者らはジャンプテーブルを選択して秩序化集合を実現したのか.以下の点から説明します.
1.一般ジャンプ表の性質
まず、ジャンプテーブルの直感的な表現を示します.
通常のジャンプテーブル実装コード(注釈付き):
#include
#include
#define MAX_LEVEL 10
typedef struct skipNode* Node;
struct skipNode{
int key;
int value;
Node forward[1];
};
typedef struct skiplist{
Node header;
int level;
}skiplist;
Node createNode(int level,int key,int value){
// malloc level , key value skipNode
skipNode * node = (Node) malloc(sizeof(skipNode) + level * sizeof(Node));
node->key = key;
node->value = value;
return node;
}
skiplist * createSkiplist(){
skiplist *list = (skiplist*)malloc(sizeof(skiplist));
list->level = 0;
list->header = createNode(MAX_LEVEL,0,0);
/*
levelN forward[N] = NULL
levelN-1 forward[N-1] = NULL
levelN-2 forward[N-2] = NULL
.
level0 forward[0] = NULL
header------>
*/
for(int i = 0; i < MAX_LEVEL; i++){
list->header->forward[i] = NULL;
}
return list;
}
int randomLevel(){
int k=1;
while (rand()%2)
k++;
k=(kreturn k;
}
/*
*/
bool insertElement(skiplist* list , int key , int value){
// key
skipNode * update[MAX_LEVEL];
// key , level
int level = list->level;
skipNode* head = list->header;
skipNode* next;
for(int i = level - 1; i >= 0; i--){
while((next = head->forward[i])&& (next->key < key)){
head = next;
}
update[i] = head;
}
// key
if(next && next->key == key){
// value
head->value = value;
return false;
}
//
int nlevel = randomLevel();
if(nlevel > list->level){
nlevel = ++list->level;
update[level] = list->header;
}
//
Node newNode = createNode(nlevel,key,value);
//
for(int i = 0 ; i < nlevel ; i++){
newNode->forward[i] = update[i]->forward[i];
update[i]->forward[i] = newNode;
}
return true;
}
/*
key value
*/
skipNode* search(skiplist* list,int key)
{
skipNode *p,*q=NULL;
p = list->header;
//
int k = list->level;
for(int i = k-1; i >= 0; i--){
while((q = p->forward[i]) && (q->key <= key)) {
if(q->key == key){
return q;
}
p=q;
}
}
return NULL;
}
/**
**/
bool deleteElement(skiplist* list , int key, int value){
skipNode* update[MAX_LEVEL];
skipNode* x = list->header;
int level = list->level;
for(int i = level - 1 ; i >= 0 ; i--){
while(x->forward[i]->key < key){
x = x->forward[i];
}
update[i] = x;
}
x = x->forward[0];
if(x->key != key){
//
return false;
}else{
for(int i = 0 ; i < list->level ; i++){
if(update[i]->forward[i] == x){
update[i]->forward[i] = x->forward[i];
}
}
free(x);
for(int i = list->level-1 ; i >= 0 ; i--){
if(list->header->forward[i] == NULL){
list->level--;
}
}
}
return true;
}
2.Redisにおけるジャンプテーブルの実現
zskiplistNodeの定義
typedef struct zskiplistNode {
robj *obj;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned int span;
} level[];
} zskiplistNode;
zskiplist定義
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist;
図解:
性質の説明:
具体的な実装コードはT_zset.cファイル中
まず、作成と初期化の操作です.
//
zskiplistNode *zslCreateNode(int level, double score, robj *obj) {
zskiplistNode *zn = zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));
zn->score = score;
zn->obj = obj;
return zn;
}
zskiplist *zslCreate(void) {
int j;
zskiplist *zsl;
// zskiplist
zsl = zmalloc(sizeof(*zsl));
zsl->level = 1;
zsl->length = 0;
// header ,ZSKIPLIST_MAXLEVEL=32, 32
zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);
//
for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {
zsl->header->level[j].forward = NULL;
zsl->header->level[j].span = 0;
}
zsl->header->backward = NULL;
zsl->tail = NULL;
return zsl;
}
挿入関数zslInsertを見てください:
zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj) {
// Update
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
// rank
unsigned int rank[ZSKIPLIST_MAXLEVEL];
int i, level;
redisAssert(!isnan(score));
x = zsl->header;
//
for (i = zsl->level-1; i >= 0; i--) {
/* , */
rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];
/* score , ,
* , , update
*/
while (x->level[i].forward &&
(x->level[i].forward->score < score ||
(x->level[i].forward->score == score &&
compareStringObjects(x->level[i].forward->obj,obj) < 0))) {
/** rank **/
rank[i] += x->level[i].span;
x = x->level[i].forward;
}
update[i] = x;
}
//
level = zslRandomLevel();
// , header
if (level > zsl->level) {
for (i = zsl->level; i < level; i++) {
rank[i] = 0;
update[i] = zsl->header;
update[i]->level[i].span = zsl->length;
}
zsl->level = level;
}
//
x = zslCreateNode(level,score,obj);
//
for (i = 0; i < level; i++) {
x->level[i].forward = update[i]->level[i].forward;
update[i]->level[i].forward = x;
/* rank */
x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
update[i]->level[i].span = (rank[0] - rank[i]) + 1;
}
/* increment span for untouched levels */
for (i = level; i < zsl->level; i++) {
update[i]->level[i].span++;
}
//
x->backward = (update[0] == zsl->header) ? NULL : update[0];
if (x->level[0].forward)
x->level[0].forward->backward = x;
else
zsl->tail = x;
zsl->length++;
return x;
}
関数を削除する
/* Internal function used by zslDelete, zslDeleteByScore and zslDeleteByRank */
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) {
int i;
//
for (i = 0; i < zsl->level; i++) {
if (update[i]->level[i].forward == x) {
// ,
update[i]->level[i].span += x->level[i].span - 1;
// forward ,
update[i]->level[i].forward = x->level[i].forward;
} else {
update[i]->level[i].span -= 1;
}
}
// backward
if (x->level[0].forward) {
x->level[0].forward->backward = x->backward;
} else {
zsl->tail = x->backward;
}
// level
while(zsl->level > 1 && zsl->header->level[zsl->level-1].forward == NULL)
zsl->level--;
zsl->length--;
}
/* Delete an element with matching score/object from the skiplist. */
int zslDelete(zskiplist *zsl, double score, robj *obj) {
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
int i;
x = zsl->header;
// , , update
for (i = zsl->level-1; i >= 0; i--) {
while (x->level[i].forward &&
(x->level[i].forward->score < score ||
(x->level[i].forward->score == score &&
compareStringObjects(x->level[i].forward->obj,obj) < 0)))
x = x->level[i].forward;
update[i] = x;
}
/* We may have multiple elements with the same score, what we need
* is to find the element with both the right score and object. */
x = x->level[0].forward;
// x , ,
if (x && score == x->score && equalStringObjects(x->obj,obj)) {
zslDeleteNode(zsl, x, update);
zslFreeNode(x);
return 1;
}
return 0; /* not found */
}
基本的な削除と挿入操作に加えて、Redisジャンプテーブルには、指定ノードのテーブル内の順位zslGetRank関数を取得したり、一定の条件を満たす順位を取得したりするなど、多くの操作が提供されています.ここから、Redisジャンプテーブルにスパンフィールドを設定するメリットがあり、迅速に順位を計算できることがわかります.具体的な実現関数はここでは一つ一つ紹介しませんが、興味のある方はソースコードを参照して調べることができます.