The memtable in Leveldb is just a wrapper class, and its underlying implementation is a skip table.
A jump table is a balanced data structure based on random numbers. Other balanced data structures include red-black and AVL trees. But the principle of jumping tables is much simpler than they are. A jump table is a bit like a linked list, just that each node is a multi-layered structure that improves lookup efficiency by adding forward pointers to each node. For example, with:
In the/leveldb/db directory there is the implementation of the jump table skiplist.h and jump table test program skiplist_test.cc.
Keyclass Comparator>class SkipList {
Can see that the Leveldb skiplist is a template class, key is to jump table each node stored information class, the jump table is a sequential structure, comparator is the table key of the comparison device.
Member variables:
Private://Set the maximum number of levels of the hop table, the number of random layers of the new node cannot be greater than this valueenum{kmaxheight = A};//Immutable after construction //key's comparison deviceComparatorConstCompare_;//Memory poolarena*ConstArena_;//Arena used for allocations of nodes //Skip the head node of the tablenode*ConstHead_;//Modified only by Insert (). Read racily by readers, but stale //values are OK. The maximum number of layers for the//hop table, without the head node, the head node key is 0, less than whatever key, the number of layers is kmaxheight=12 //Atomicpointer is an atomic pointer defined by LEVELDB, which has a memory barrier for both read and write, guaranteeing that the read value is immediate, up -to-date //Here the int is converted directly to the pointer to save, because the address is not taken. So it works. Worthy of referencePort::atomicpointer Max_height_;//Height of the entire list
The member functions are:
//Insert key into the list.voidInsert (Constkey& key);//ReturnstrueIFF an entry-compares equal to key is inchThe list. BOOL Contains (Constkey& key)Const; node* NewNode (Constkey& key, int height); int Randomheight (); BOOL Equal (Constkey& A,Constkey& b)Const{return(Compare_ (A, b) = =0); }//Returntrue ifKey isGreater than the data storedinch "n"BOOL Keyisafternode (Constkey& key, node* N)Const;//Return The earliest node, comes atorAfter key.//Return NULLifThere is NoSuch node.// //If prev isNon-null, fills Prev[level] withPointer to Previous//Node at"Level" forEvery levelinch[0.. max_height_-1]. node* Findgreaterorequal (Constkey& key, node** prev)Const;//Return the latest node withA key < key.//Return Head_ifThere is NoSuch node. node* Findlessthan (Constkey& key)Const;//Return The last nodeinchThe list.//Return Head_ifList isEmpty. node* FindLast ()Const;
Believe that you can read the gaze of these function functions.
Nodes and iterators:
Skiplist node and iterator iterator are defined in the class Skiplist as nested classes
Class Iterator { Public://Initialize An iterator over the specified list. The returned iterator isNot valid.Explicit Iterator(Constskiplist* list);//Returns True iff the iterator is positioned at a valid node. BOOLValid ()Const;//Returns the key at the current position. //Requires:valid () Constkey& Key ()Const;//advances to the next position. //Requires:valid () voidNext ();//advances to the previous position. //Requires:valid () voidPrev ();//Advance to the first entry with a key >= target voidSeek (Constkey& target);//Position at the first entry in list. //Final State of Iterator was Valid () IFF list is not empty. voidSeektofirst ();//Position at the last entry in list. //Final State of Iterator was Valid () IFF list is not empty. voidSeektolast ();Private:Constskiplist* List_; node* Node_;//intentionally copyable};
The iterator simply has a list pointer (the pointer that holds the skiplist) and the node pointer (the point of the finger).
The main operation of an iterator is to move forward, back, and position the head node. The tail node, which encapsulates the operation of list and node. Example:
Template<TypeName Key, Class Comparator>inline voidSkiplist<Key,comparator>:: Iterator:: Next() {Assert (Valid ()); Node_=Node_ -Next (0);} Template<TypeName Key, Class Comparator>inline voidSkiplist<Key,comparator>:: Iterator::P Rev() {//Instead of using explicit "prev" links, we just search for the //Last node, that falls before key.ASSERT (Valid ()); Node_=List_ -Findlessthan (Node_ -Key);if(Node_==List_ -Head_) {Node_= NULL; }}
Note that next moves forward at the lowest level of the node, in fact Prev, which ensures that each node of the skiplist is traversed. In fact, the multi-level pointer structure of the jump table is designed to improve query efficiency.
Take a look at the definition of node nodes:
Template<TypeNameKey,classComparator>structSkiplist<key,comparator>::node {ExplicitNode (Constkey& k): Key (k) {}//The data that is carried, the memtable indicates that the instantiation version number is char* keyKeyConstKey//Accessors/mutators for links. Wrapped in methods so we can //Add the appropriate barriers as necessary. //Returns the next node pointer to the nth layer of this nodenode* Next (intN) {assert (n >=0);//Use the ' acquire load ' so that we observe a fully initialized //version of the returned Node. return reinterpret_cast<Node*> (Next_[n]. Acquire_load ()); }//Reset N-layer next pointer voidSetnext (intN, node* x) {assert (n >=0);//Use a ' release store ' so the anybody who reads through this //Pointer observes a fully initialized version of the inserted node.Next_[n]. Release_store (x); }//No-barrier variants that can is safely used in a few locations.node* Nobarrier_next (intN) {assert (n >=0);return reinterpret_cast<Node*> (Next_[n]. Nobarrier_load ()); }voidNobarrier_setnext (intN, node* x) {assert (n >=0); Next_[n]. Nobarrier_store (x); }Private://Array of length equal to the node height. NEXT_[0] is lowest level link. //N-level back pointer to this nodePort::atomicpointer next_[1];};
In the implementation of memtable, we see that the instantiation version number of skiplist is skiplist
template <typename Key, class comparator> typename Skiplist<key,comparator>::node*skiplist<key,comparator>::newnode (const key& Key, int height) {char * mem = arena_->allocatealigned (sizeof (Node) + sizeof
(port::atomicpointer) * (Height-1 )); return new (MEM) Node (key);}
Very many people may have wondered before why the N-level back pointer of each node was next_[1]. Just have a member? Since the height of the node needs to be generated by a random operator, which means that height cannot be predicted in advance for each node, it is natural that the next array size cannot be determined in the node definition. So how do you ensure that the next array is sufficient? NewNode shows us this kind of artifice. In fact, after determining the height, the new node applies a space of sizeof (Node) + sizeof (port::atomicpointer) * (height-1) size to make sure that the next pointer has enough space when it requests space to memory. Specify a space for the new node with placement new.
The structure has been well-paved. Now let's look at the implementation of the Skiplist member function.
//寻找关键字大于等于key值的近期节点。指针数组prev保存此节点每一层上訪问的前一个节点。
Template<TypeNameKey,classComparator>TypeNameskiplist<key,comparator>::node* Skiplist<key,comparator>::findgreaterorequal (Constkey& key, node** prev)Const{node* x = Head_;//First get the highest layer of the jump table, minus one is the array next maximum subscript intLevel = Getmaxheight ()-1;//Find operation starts while(true) {the//Jump table can be viewed as a multi-layered list. The higher the number of layers. The fewer nodes in a linked list. Lookup also starts with a list of high-level numbers //Assuming key continues to move after node //If it is less than this node, the front node pointer on the level layer of this node is recorded into the array prev and jumps to the first-level linked list //Repeat the above process. Until we reach the bottom .node* next = X->next (level);if(Keyisafternode (key, Next)) {//Keep searching in the This listx = Next; }Else{if(prev! = NULL) Prev[level] = x;if(Level = =0) {returnNext }Else{//Switch to Next listlevel--; } } }}
The jump table is actually similar to a multi-layered ordered list, and the higher-level list is less than the underlying list node. In higher-level lists, the entire list can be traversed more quickly, and jumping to the bottom of the list is more advantageous for precise positioning. The above is the essence of skiplist to use space in exchange for time. You want to start with the top layer of the Jump header node. The key value is greater than the node key value. Then go to the next node on the same level, or jump to the lower level of the node and record the last visited node of the previous layer. Until it comes to the first level (bottom). This is the result of the analysis of the following other operations. Put a slip on the table to help understand
Similar functions also have Findlessthan. FindLast, everyone understands and understands.
In fact, the array of forward node pointers returned by the Findgreaterorequal function is used to insert nodes into the Skip table. Think about the insert operation of the list. When you insert a key. Start by creating a new node (key). Point the Node->next to Prev-next, and then point Prev->next to node. The jump table is also, just need to operate multiple linked lists.
Skiplist::insert functions such as the following:
Template<TypeNameKey,classComparator>voidSkiplist<key,comparator>::insert (Constkey& key) {//TODO (OPT): We can use a Barrier-free variant of findgreaterorequal () //here since Insert () is externally synchronized.node* Prev[kmaxheight]; node* x = findgreaterorequal (key, prev);//Our data structure does isn't allow duplicate insertionASSERT (x = = NULL | |! Equal (key, X->key));intHeight = randomheight ();if(Height > getmaxheight ()) { for(inti = Getmaxheight (); i < height; i++) {Prev[i] = Head_; } max_height_. Nobarrier_store (reinterpret_cast<void*> (height)); } x = NewNode (key, height); for(inti =0; i < height; i++) {X->nobarrier_setnext (I, Prev[i]->nobarrier_next (i)); Prev[i]->setnext (i, x); }}
Summarize
All right. Looking at Leveldb's skiplist and memtable, it really benefited, not only learned the Skiplist implementation and memtable package, but also recognized the memory barrier and how to insert assembly statements in C + +.
But I think the most important thing to see LEVELDB is to learn the design ideas of others. In fact, it is not difficult to realize the realization of the jumping table, put aside the performance gap, perhaps I can achieve, but how to truly object-oriented, how to understand the requirements and design the most concise structure. How to make your code level clear, at a glance, this is really a big wisdom.
LEVELDB Study: Skiplist