From f8748f5e19d818c072be01036ed18ab20ba968b2 Mon Sep 17 00:00:00 2001 From: Miroslav Drbal Date: Thu, 26 Jan 2017 23:44:50 +0100 Subject: [PATCH 1/3] * Improvements in HashMap: - hashTable is just array of buckets now - do not use expensive % operation * Improvements in HashBucket: - there is no dummy head node - head node now can take data - improved algorithms working with linked list --- inc/HashMap.h | 54 ++++++++++---------------- inc/HashNode.h | 102 ++++++++++++++++++++++++------------------------- 2 files changed, 72 insertions(+), 84 deletions(-) diff --git a/inc/HashMap.h b/inc/HashMap.h index 96c1111..56a704a 100644 --- a/inc/HashMap.h +++ b/inc/HashMap.h @@ -7,7 +7,8 @@ #include #include "HashNode.h" -constexpr size_t HASH_SIZE_DEFAULT = 1031; // A prime number as hash size gives a better distribution of values in buckets +constexpr size_t HASH_SIZE_DEFAULT = 2048; + namespace CTSL //Concurrent Thread Safe Library { //The class represting the hash map. @@ -18,76 +19,63 @@ namespace CTSL //Concurrent Thread Safe Library //Each hash bucket is implemented as singly linked list with the head as a dummy node created //during the creation of the bucket. All the hash buckets are created during the construction of the map. //Locks are taken per bucket, hence multiple threads can write simultaneously in different buckets in the hash map - template > + template > class HashMap { public: HashMap(size_t hashSize_ = HASH_SIZE_DEFAULT) : hashSize(hashSize_) { - hashTable = new HashBucket * [hashSize](); //create the hash table as an array of hash buckets - for(size_t i = 0; i < hashSize; i++) - { - hashTable[i] = new HashBucket(); //create the hash buckets - } + hashTable = new HashBucket[hashSize]; //create the hash table as an array of hash buckets } ~HashMap() { - for(size_t i = 0; i < hashSize; i++) - { - delete hashTable[i]; //delete all the hash buckets - } - - delete []hashTable; + delete [] hashTable; } + //Copy and Move of the HashMap are not supported at this moment HashMap(const HashMap&) = delete; HashMap(HashMap&&) = delete; - HashMap& operator=(const HashMap&) = delete; + HashMap& operator=(const HashMap&) = delete; HashMap& operator=(HashMap&&) = delete; - + //Function to find an entry in the hash map matching the key. //If key is found, the corresponding value is copied into the parameter "value" and function returns true. //If key is not found, function returns false. - bool find(const K &key, V &value) const + bool find(const K &key, V &value) const { - size_t hashValue = hashFn(key) % hashSize ; - HashBucket * bucket = hashTable[hashValue]; - return bucket->find(key, value); + const size_t hashValue = HASH()(key) & (hashSize - 1); // Only for hash size power of 2 + return hashTable[hashValue].find(key, value); } //Function to insert into the hash map. //If key already exists, update the value, else insert a new node in the bucket with the pair. void insert(const K &key, const V &value) { - size_t hashValue = hashFn(key) % hashSize ; - HashBucket * bucket = hashTable[hashValue]; - bucket->insert(key, value); + const size_t hashValue = HASH()(key) & (hashSize - 1); // Only for hash size power of 2 + hashTable[hashValue].insert(key, value); } //Function to remove an entry from the bucket, if found - void erase(const K &key) + void erase(const K &key) { - size_t hashValue = hashFn(key) % hashSize ; - HashBucket * bucket = hashTable[hashValue]; - bucket->erase(key); - } + const size_t hashValue = HASH()(key) & (hashSize - 1); // Only for hash size power of 2 + hashTable[hashValue].erase(key); + } //Function to clean up the hasp map, i.e., remove all entries from it void clear() { for(size_t i = 0; i < hashSize; i++) { - (hashTable[i])->clear(); + hashTable[i].clear(); } - } + } private: - - HashBucket ** hashTable; - F hashFn; - size_t hashSize; + HashBucket* hashTable; + const size_t hashSize; }; } #endif diff --git a/inc/HashNode.h b/inc/HashNode.h index 1e3bcd2..1be3c4d 100644 --- a/inc/HashNode.h +++ b/inc/HashNode.h @@ -9,11 +9,16 @@ namespace CTSL //Concurrent Thread Safe Library class HashNode { public: - HashNode() : next(nullptr) - {}; - HashNode(K key_, V value_) : next(nullptr), key(key_), value(value_) - {}; - ~HashNode() {next = nullptr;} + HashNode() : next(nullptr) { + } + + HashNode(K key_, V value_) : next(nullptr), key(key_), value(value_) { + } + + ~HashNode() + { + next = nullptr; + } const K& getKey() const {return key;} void setValue(V value_) {value = value_;} @@ -32,23 +37,22 @@ namespace CTSL //Concurrent Thread Safe Library class HashBucket { public: - HashBucket() + HashBucket() : head(nullptr) { - head = new HashNode();//A bucket is created with its head as a dummy hash node. } ~HashBucket() //delete the bucket { std::unique_lock lock(mutex_); //take a lock before removing the nodes - HashNode * prev = nullptr; - HashNode * node = head; - while(node != nullptr) + + while(head) { - prev = node; - node = node->next; - delete prev; + HashNode* next = head->next; + delete head; + + head = next; } - } + } //Function to find an entry in the bucket matching the key //If key is found, the corresponding value is copied into the parameter "value" and function returns true. @@ -56,10 +60,10 @@ namespace CTSL //Concurrent Thread Safe Library bool find(const K &key, V &value) const { // A shared mutex is used to enable mutiple concurrent reads - std::shared_lock lock(mutex_); - HashNode * node = head->next; //The head node is dummy, no need to look there + std::shared_lock lock(mutex_); + HashNode * node = head; - while (node != nullptr) + while (node) { if (node->getKey() == key) { @@ -77,24 +81,20 @@ namespace CTSL //Concurrent Thread Safe Library { //Exclusive lock to enable single write in the bucket std::unique_lock lock(mutex_); - HashNode * prev = head; - HashNode * node = head->next; //The head node is dummy, no need to look there + HashNode** node = &head; - while (node != nullptr && node->getKey() != key) + while (*node) { - prev = node; - node = node->next; + if ((*node)->getKey() == key) + { + (*node)->setValue(value); //Key found in bucket, update the value + return; + } + node = &((*node)->next); } - if (nullptr == node) - { - prev->next = new HashNode(key, value); //New entry, create a node and add to bucket - } - else - { - node->setValue(value); //Key found in bucket, update the value - } - + // now node points to lasts element ->next or head so we can construct the new object unconditionaly here :) + *node = new HashNode(key, value); } //Function to remove an entry from the bucket, if found @@ -102,23 +102,23 @@ namespace CTSL //Concurrent Thread Safe Library { //Exclusive lock to enable single write in the bucket std::unique_lock lock(mutex_); - HashNode *prev = head; - HashNode * node = head->next; - while (node != nullptr && node->getKey() != key) - { - prev = node; - node = node->next; - } + HashNode* node = head; + HashNode** prev = &head; - if (nullptr == node) //Key not found, nothing to be done + while (node) { - return; - } - else - { - prev->next = node->next; //Remove the node from the bucket - delete node; //Free up the memory + if (node->getKey() == key) + { + *prev = node->next; + delete node; + return; + } + else + { + prev = &node; + node = node->next; + } } } @@ -129,13 +129,13 @@ namespace CTSL //Concurrent Thread Safe Library { //Exclusive lock to enable single write in the bucket std::unique_lock lock(mutex_); - HashNode * prev = head; - HashNode * node = head->next; //The dummy head node will not be removed - while(node != nullptr) + + while(head) { - prev->next = node->next; - delete node; - node = prev->next; + HashNode* toDel = head; + delete toDel; + + head = head->next; } } From 6fd44decbf3a2a20d73cd1483503752c534cba7f Mon Sep 17 00:00:00 2001 From: Miroslav Drbal Date: Thu, 26 Jan 2017 23:45:30 +0100 Subject: [PATCH 2/3] * Just some constants --- src/HastTest.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/HastTest.cpp b/src/HastTest.cpp index c28b411..ad44532 100644 --- a/src/HastTest.cpp +++ b/src/HastTest.cpp @@ -4,7 +4,7 @@ void testSingleThreadStringKey() { - CTSL::HashMap stringMap(1001); + CTSL::HashMap stringMap(2048); stringMap.insert("test1", 200); stringMap.insert("test2", 670); @@ -37,7 +37,7 @@ void testSingleThreadStringKey() { std::cout << "Did not find value for key \"test1\"" << std::endl; } - + stringMap.erase("test1"); if(stringMap.find("test1", value)) { @@ -72,7 +72,7 @@ void testSingleThreadStringKey() void testSingleThreadIntegerKey() { - CTSL::HashMap integerMap(29); + CTSL::HashMap integerMap(32); integerMap.insert(10, 200); integerMap.insert(20, 670); @@ -105,7 +105,7 @@ void testSingleThreadIntegerKey() { std::cout << "Did not find value for key 20" << std::endl; } - + integerMap.erase(20); if(integerMap.find(20, value)) { @@ -179,7 +179,7 @@ void testMultiThreadIntegerKey_Func1(CTSL::HashMap &integerMap) { std::cout << "Thread 1: Did not find value for key 20 in HashMap " << &integerMap << std::endl; } - + integerMap.erase(20); std::this_thread::sleep_for(std::chrono::seconds(1)); if(integerMap.find(20, value)) @@ -249,7 +249,7 @@ void testMultiThreadIntegerKey_Func2(CTSL::HashMap &integerMap) { std::cout << "Thread 2: Did not find value for key 20 in HashMap " << &integerMap << std::endl; } - + integerMap.erase(20); if(integerMap.find(20, value)) { @@ -284,7 +284,7 @@ void testMultiThreadIntegerKey_Func2(CTSL::HashMap &integerMap) int main() { testSingleThread(); //Single threaded test - + //Multi threaded test with two threads CTSL::HashMap integerMap; std::thread firstThread(testMultiThreadIntegerKey_Func1, ref(integerMap)); From 0bc309e5524595c8b29e4d61cdfaf226f640e6d9 Mon Sep 17 00:00:00 2001 From: Miroslav Drbal Date: Thu, 26 Jan 2017 23:48:30 +0100 Subject: [PATCH 3/3] * Added assert to check hash size --- inc/HashMap.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/inc/HashMap.h b/inc/HashMap.h index 56a704a..ac522c2 100644 --- a/inc/HashMap.h +++ b/inc/HashMap.h @@ -1,11 +1,12 @@ #ifndef HASH_MAP_H_ #define HASH_MAP_H_ -#include -#include +#include +#include +#include #include -#include -#include "HashNode.h" +#include +#include "HashNode.h" constexpr size_t HASH_SIZE_DEFAULT = 2048; @@ -25,6 +26,9 @@ namespace CTSL //Concurrent Thread Safe Library public: HashMap(size_t hashSize_ = HASH_SIZE_DEFAULT) : hashSize(hashSize_) { + assert(hashSize_ > 0 && "Hash size muste be > 0"); + assert((hashSize_ & (hashSize_ - 1)) == 0 && "Hash size must be power of 2"); + hashTable = new HashBucket[hashSize]; //create the hash table as an array of hash buckets }