-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathz-hash-table.cpp
259 lines (214 loc) · 8 KB
/
z-hash-table.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
#include <iostream>
#include <map>
#include <vector>
#include <fstream>
#include <iostream>
#include <chrono>
#include <list>
#include <unordered_map>
#include <iostream>
#include <map>
#include <string>
#include <memory>
#include <sstream>
#include <limits>
#include <thread>
#include <queue>
#include <optional>
#include <mutex>
#include <shared_mutex>
class HashIndex2 {
private:
struct HashEntry {
int key;
int value;
int position; // Final position within the array
bool exists; // Flag to check if entry exists
// Default constructor
HashEntry() : key(0), value(0), position(-1), exists(false) {}
// Constructor for initializing with key, value, and exists flag
HashEntry(int k, int v, int pos) : key(k), value(v), position(pos), exists(true) {}
};
static const size_t capacity = 10000; // Hard-coded capacity
HashEntry hashTable[capacity]; // Static-sized array
// A vector of unique mutexes for fine-grained locking
std::vector<std::unique_ptr<std::mutex>> mutexes;
size_t hashFunction(int key) const {
return key % capacity; // Simple modulo hash function
}
public:
HashIndex2() : mutexes(capacity) {
// Initialize all entries as non-existing by default
for (size_t i = 0; i < capacity; ++i) {
hashTable[i] = HashEntry();
mutexes[i] = std::make_unique<std::mutex>();
}
}
void insertOrUpdate(int key, int value) {
size_t index = hashFunction(key);
size_t originalIndex = index;
bool inserted = false;
int i = 0; // Attempt counter
do {
// Lock only the specific slot's mutex
std::lock_guard<std::mutex> lock(*mutexes[index]);
if (!hashTable[index].exists) {
hashTable[index] = HashEntry(key, value, true);
hashTable[index].position = index;
inserted = true;
break;
} else if (hashTable[index].key == key) {
hashTable[index].value += value;
hashTable[index].position = index;
inserted = true;
break;
}
i++;
index = (originalIndex + i*i) % capacity; // Quadratic probing
} while (index != originalIndex && !inserted);
if (!inserted) {
std::cerr << "HashTable is full or cannot insert key: " << key << std::endl;
}
}
int getValue(int key) const {
size_t index = hashFunction(key);
size_t originalIndex = index;
do {
// Lock only the specific slot's mutex
std::lock_guard<std::mutex> lock(*mutexes[index]);
if (hashTable[index].exists && hashTable[index].key == key) {
return hashTable[index].value;
}
if (!hashTable[index].exists) {
break; // Stop if we find a slot that has never been used
}
index = (index + 1) % capacity;
} while (index != originalIndex);
return -1; // Key not found
}
void print() const {
for (size_t i = 0; i < capacity; ++i) {
// Lock only the specific slot's mutex
std::lock_guard<std::mutex> lock(*mutexes[i]);
if (hashTable[i].exists) {
std::cout << "Position: " << hashTable[i].position <<
", Key: " << hashTable[i].key <<
", Value: " << hashTable[i].value << std::endl;
}
}
}
};
class HashIndex3 {
private:
struct HashEntry {
int key;
int value;
int position; // Final position within the array
bool exists; // Flag to check if entry exists
// Default constructor
HashEntry() : key(0), value(0), position(-1), exists(false) {}
// Constructor for initializing with key, value, and exists flag
HashEntry(int k, int v, int pos) : key(k), value(v), position(pos), exists(true) {}
};
static const size_t capacity = 10000; // Hard-coded capacity
HashEntry hashTable[capacity]; // Static-sized array
mutable std::shared_mutex mutexes[capacity]; // Array of shared_mutex for fine-grained locking
size_t hashFunction(int key) const {
return key % capacity; // Simple modulo hash function
}
public:
HashIndex3() {
// Initialize all entries as non-existing by default
for (size_t i = 0; i < capacity; ++i) {
hashTable[i] = HashEntry();
}
}
void insertOrUpdate(int key, int value) {
size_t index = hashFunction(key);
size_t originalIndex = index;
bool inserted = false;
int i = 0; // Attempt counter
do {
// Exclusive lock for writing
std::unique_lock<std::shared_mutex> lock(mutexes[index]);
if (!hashTable[index].exists) {
hashTable[index] = HashEntry(key, value, true);
hashTable[index].position = index;
inserted = true;
break;
} else if (hashTable[index].key == key) {
hashTable[index].value += value;
hashTable[index].position = index;
inserted = true;
break;
}
i++;
index = (originalIndex + i*i) % capacity; // Quadratic probing
} while (index != originalIndex && !inserted);
if (!inserted) {
std::cerr << "HashTable is full or cannot insert key: " << key << std::endl;
}
}
int getValue(int key) const {
size_t index = hashFunction(key);
size_t originalIndex = index;
do {
// Shared lock for reading
std::shared_lock<std::shared_mutex> lock(mutexes[index]);
if (hashTable[index].exists && hashTable[index].key == key) {
return hashTable[index].value;
}
if (!hashTable[index].exists) {
break; // Stop if we find a slot that has never been used
}
index = (index + 1) % capacity;
} while (index != originalIndex);
return -1; // Key not found
}
void print() const {
for (size_t i = 0; i < capacity; ++i) {
// Shared lock for reading
std::shared_lock<std::shared_mutex> lock(mutexes[i]);
if (hashTable[i].exists) {
std::cout << "Position: " << hashTable[i].position <<
", Key: " << hashTable[i].key <<
", Value: " << hashTable[i].value << std::endl;
}
}
}
};
template<typename HashTableType>
void hashTableOperations(HashTableType& hashTable, int startKey, int endKey, int threadId) {
for (int key = startKey; key <= endKey; ++key) {
int value = key + 1000 * threadId;
hashTable.insertOrUpdate(key, value);
}
}
template<typename HashTableType>
void benchmarkHashTable(HashTableType& hashTable, const std::string& tableName){
auto start = std::chrono::high_resolution_clock::now();
// Assuming a larger range of keys, e.g., 1000 keys per thread
const int keysPerThread = 1000;
const int numThreads = 4; // Keeping the number of threads to 4 for this example
std::vector<std::thread> threads;
for (int i = 0; i < numThreads; ++i) {
int startKey = i * keysPerThread + 1;
int endKey = (i + 1) * keysPerThread;
threads.emplace_back(hashTableOperations<HashTableType>, std::ref(hashTable), startKey, endKey, i + 1);
}
for (auto& t : threads) {
t.join();
}
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed = end - start;
std::cout << tableName << ": Elapsed time: " << elapsed.count() << " seconds\n";
}
int main() {
HashIndex2 hashTable2;
HashIndex3 hashTable3;
std::cout << "Starting stress test for HashIndex2.\n";
benchmarkHashTable(hashTable2, "HashTable2");
std::cout << "Starting stress test for HashIndex3.\n";
benchmarkHashTable(hashTable3, "HashTable3");
return 0;
}