Leveldb learning: Cache and leveldb learning cache

Source: Internet
Author: User

Leveldb learning: Cache and leveldb learning cache

Leveldb implements the cache buffer substitution algorithm by itself. For details, see the code cache. h and cache. c files. In leveldb, table_cache is implemented based on class cache as the underlying layer.
In cache. h, we can see that the cache class is an abstract class that declares functions such as lookup, insert, release, value, and erase and declares a global function.

extern Cache* NewLRUCache(size_t capacity);

Used to construct a cache derived class object and return a pointer to the derived class. So what is the cache derived class? It is easy to find the ShardedLRUCache class in cache. cc, inherited from the cache, which is the default implementation of the leveldb buffer algorithm.

ShardedLRUCache member variable static const int kNumShardBits = 4; static const int kNumShards = 1 <kNumShardBits; private: LRUCache shard _ [kNumShards]; // unknown port: Mutex id_mutex _; // mutex lock uint64_t last_id _; // unknown

We do not know the implementation of LRUCache for the time being, but it seems that ShardedLRUCache should be an encapsulation class. The actual cache is LRUCache and there are 16. First look at the ShardedLRUCache function:

// Return the key's hash value static inline uint32_t HashSlice (const Slice & s) {return Hash (s. data (), s. size (), 0);} // obtain the first four static uint32_t Shard (uint32_t hash) of hash {return hash >>( 32-kNumShardBits);} public: // construct the ShardedLRUCache object, initialize the LRUCache member variable // set the capacity, and the capacity is 16 aligned with the explicit it ShardedLRUCache (size_t capacity): last_id _ (0) {const size_t per_shard = (capacity + (kNumShards-1)/kNumShards; for (int s = 0; s <KNumShards; s ++) {shard _ [s]. SetCapacity (per_shard) ;}} virtual ~ ShardedLRUCache () {}// insert operation // obtain the hash value of the key HashSlice (key) first. hash is worth the first four digits (Shard (hash )) determines the LRUCache array of the key. // inserts the key into the shard _ [Shard (hash)] virtual Handle * Insert (const Slice & key, void * value, size_t charge, void (* deleter) (const Slice & key, void * value) {const uint32_t hash = HashSlice (key); return shard _ [Shard (hash)]. insert (key, hash, value, charge, deleter) ;}// query operation, same as the operation logic of the Insert Process virtual Handle * Lookup (const Slice & key) {const uint32_t hash = HashSlice (key); return shard _ [Shard (hash)]. lookup (key, hash);} virtual void Release (Handle * handle) {LRUHandle * h = reinterpret_cast <LRUHandle *> (handle ); shard _ [Shard (h-> hash)]. release (handle);} virtual void Erase (const Slice & key) {const uint32_t hash = HashSlice (key); shard _ [Shard (hash)]. erase (key, hash);} virtual void * Value (Handle * handle) {return reinterpret_cast <LRUHandle *> (handle)-> value;} virtual uint64_t NewId () {MutexLock l (& id_mutex _); return ++ (last_id _);}

From ShardedLRUCache's member functions, we still get a lot of information about ShardedLRUCache. ShardedLRUCache is an encapsulation class, and the real cache is an LRUCache array. ShardedLRUCache completes the operation of calculating the hash value of the key, and determines the LRUCache array where the key is located using the hash value, then, call the LRUCache function to complete the cache operation.

LRUCache: // Initialized before use. size_t capacity _; // capacity // mutex _ protects the following state. port: Mutex mutex _; // Mutex size_t usage _; // usage // Dummy head of LRU list. // lru. prev is newest entry, lru. next is oldest entry. LRUHandle lru _; // unknown HandleTable table _; // unknown LRUCache member variable as above. Then let's look at the LRUCache function // delete e node void LRUCache: LRU_Remove (LRUHandle * e) {e-> next-> prev = e-> prev; e-> prev-> next = e-> next ;} // Add e node void LRUCache: LRU_Append (LRUHandle * e) {// Make "e" newest entry by inserting just before lru _ // Add a new node before lru _ e-> next = & lru _; e-> prev = lru _. prev; e-> prev-> next = e; e-> next-> prev = e ;} // search operation // table _ stores the pointer information of the handle where the key is located. // after searching, the handle must be mentioned at the beginning of the linked list. It is a Cache policy for efficient searching :: handle * LRUCache: Lookup (const Slice & key, uint32_t hash) {MutexLock l (& mutex _); LRUHandle * e = table _. lookup (key, hash); if (e! = NULL) {e-> refs ++; LRU_Remove (e); LRU_Append (e);} return reinterpret_cast <Cache: Handle *> (e );}

It can be determined that LRUCache encapsulates the information of an LRUHandle linked list. lru _ is the head node of this linked list, and table _ is the structure that assists in locating each LRUHandle node in the linked list.

LRUHandle struct:
Now we finally come to the bottom layer of the cache. The LRUHandle structure truly contains the buffered data.

Struct LRUHandle {// value data void * value; // delete function pointer void (* deleter) (const Slice &, void * value ); // The following is the implementation of the LRUHandle linked list. // you can see the key-specific hash value. // the key-specific data is LRUHandle * next_hash; LRUHandle * next; LRUHandle * prev; size_t charge; // TODO (opt): Only allow uint32_t? Size_t key_length; uint32_t refs; uint32_t hash; // Hash of key (); used for fast sharding and comparisons char key_data [1]; // Beginning of key // fetch the buffered data Slice key () const {// For cheaper lookups, we allow a temporary Handle object // to store a pointer to a key in "value ". if (next = this) {return * (reinterpret_cast <Slice *> (value);} else {return Slice (key_data, key_length );}}};

After reading the definition of the node, we still need to go back to the upper layer of LRUCache for the linked list operation.

In ShardedLRUCache, we know that inserting a key determines the LRUCache array where the key is located by using the hash value, and then handing the key to the corresponding LRUCache object in the array, which calls
LRUCache: Insert Function

Cache: Handle * LRUCache: Insert (const Slice & key, uint32_t hash, void * value, size_t charge, void (* deleter) (const Slice & key, void * value) {// insert MutexLock l (& mutex _) to be locked _); // construct a new LRUHandle node LRUHandle * e = reinterpret_cast <LRUHandle *> (malloc (sizeof (LRUHandle)-1 + key. size (); // specify the information of the new node // value e-> value = value; // key, value: delete function, you can customize e-> deleter = deleter; e-> charge = charge; // key hash, Length e-> key_length = key. size (); e-> hash = hash; e-> refs = 2; // One from LRUCache, one for the returned handle memcpy (e-> key_data, key. data (), key. size (); // append the new node to the linked list LRU_Append (e); usage _ + = charge; // transmits the information added to the new linked list to the table, register the information of the new node in table LRUHandle * old = table _. insert (e); if (old! = NULL) {LRU_Remove (old); Unref (old) ;}// after adding a new node, if the capacity exceeds the configured capacity of LRUCache, delete the oldest node. // all the new nodes are in the while (usage _> capacity _ & lru _. next! = & Lru _) {LRUHandle * old = lru _. next; LRU_Remove (old); table _. remove (old-> key (), old-> hash); Unref (old);} return reinterpret_cast <Cache: Handle *> (e );}

Note: Cache: Handle is an empty structure without any Members and will not be instantiated because it is meaningless. It only serves as a pointer and is the return type of many functions in LRUCache.
The structure and implementation of the entire cache have basically been completed, and only one HandleTable with LRUHandle node information and secondary search is not introduced, but this does not prevent us from drawing the cache structure, (image source from network)

The Cache class is an abstract class. Calling the global function NewLRUCache returns an object of the SharedLRUCache derived class. SharedLRUCache contains an LRUCache array because levelDB is multithreading, each thread locks the buffer when accessing the buffer. To speed up multi-threaded access and reduce the lock overhead, ShardedLRUCache has 16 lrucaches, so that the 16 cache zones can be accessed at the same time. LRUCache maintains a two-way linked list. The node of the linked list is LRUHandle, And the LRUHandle contains key-value data.

HandleTable:

Private: // The table consists of an array of buckets where each bucket is // a linked list of cache entries that hash into the bucket. uint32_t length _; // Number of header nodes uint32_t elems _; // number of elements in the hash table LRUHandle ** list _; // pointer linked list

HandleTable is an array-implemented hash table. The LRUHandle pointer is placed in the array to locate the position of the key in the hash table based on the hash value of the key and the remainder of the hash table size, leveldb uses linked lists to solve competition problems. The nodes in each chain table are the nodes in LRUCache, but here, the backward pointer of the chain table is the next_hash member of each LRUHandle object, that is, leveldb re-arranges the nodes written into LRUCache. This policy is quite clever. It uses only one pointer array to add a backward pointer member to the node to complete the hash table that helps to quickly search.

LRUHandle ** FindPointer (const Slice & key, uint32_t hash) {// calculate the remainder LRUHandle for hash ** ptr = & list _ [hash & (length _-1)]; // use the next_hash pointer to facilitate this linked list and compare the hash value, key while (* ptr! = NULL & (* ptr)-> hash! = Hash | key! = (* Ptr)-> key () {ptr = & (* ptr)-> next_hash;} return ptr ;}

This is a function that uses handletable to find the key.
Insert operation:

LRUHandle * Insert (LRUHandle * h) {// Insert operation // search for the key in the handletable. // if no key exists, a new node is added, if yes, the old node will be replaced. // The old node will be deleted in the upper LRUCache: Insert to complete LRUHandle ** ptr = FindPointer (h-> key (), h-> hash ); LRUHandle * old = * ptr; h-> next_hash = (old = NULL? NULL: old-> next_hash); * ptr = h; // The element count elems _ update // too many elements. You need to resize the hash table, add a new linked list if (old = NULL) {++ elems _; if (elems _> length _) {// Since each cache entry is fairly large, we aim for a small // average linked list length (<= 1 ). resize () ;}} return old ;}

When the hash table is too large, you must perform the resize operation on the hash table:

Void Resize () {// reselect the hash table size, that is, the number of linked lists uint32_t new_length = 4; while (new_length <elems _) {new_length * = 2 ;} // apply for the array LRUHandle ** new_list = new LRUHandle * [new_length]; memset (new_list, 0, sizeof (new_list [0]) * new_length ); // because the number of linked lists has changed, the hash & (new_length-1) Result of the linked list is changed based on the hash value. // The Position of the original header node in the array has changed to uint32_t count = 0; for (uint32_t I = 0; I <length _; I ++) {LRUHandle * h = list _ [I]; while (h! = NULL) {LRUHandle * next = h-> next_hash; uint32_t hash = h-> hash; LRUHandle ** ptr = & new_list [hash & (new_length-1)]; h-> next_hash = * ptr; * ptr = h; h = next; count ++ ;}} // delete the original linked list header node pointer array // update the handletable data assert (elems _ = count); delete [] list _; list _ = new_list; length _ = new_length ;}

If the number of linked lists in the table remains the same, the length of each linked list will gradually increase as the number of cached keys increases, which will lead to the bottom of the search efficiency. It is necessary to create a new hash table with more elements. resize means that all the original nodes must be interrupted in the old linked list to create a new connection.

Copyright Disclaimer: This article is an original article by the blogger and cannot be reproduced without the permission of the blogger.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.