-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathhashtable.c
673 lines (562 loc) · 18.6 KB
/
hashtable.c
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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
#include "hashtable.h"
//////////////////////////////////////////////////////////////////////
//hash function defined here
uint32_t murmur3_32(const uint8_t* key, size_t len, uint32_t seed){
uint32_t h = seed;
if (len > 3) {
const uint32_t* key_x4 = (const uint32_t*) key;
size_t i = len >> 2;
do {
uint32_t k = *key_x4++;
k *= 0xcc9e2d51;
k = (k << 15) | (k >> 17);
k *= 0x1b873593;
h ^= k;
h = (h << 13) | (h >> 19);
h = h * 5 + 0xe6546b64;
} while (--i);
key = (const uint8_t*) key_x4;
}
if (len & 3) {
size_t i = len & 3;
uint32_t k = 0;
key = &key[i - 1];
do {
k <<= 8;
k |= *key--;
} while (--i);
k *= 0xcc9e2d51;
k = (k << 15) | (k >> 17);
k *= 0x1b873593;
h ^= k;
}
h ^= len;
h ^= h >> 16;
h *= 0x85ebca6b;
h ^= h >> 13;
h *= 0xc2b2ae35;
h ^= h >> 16;
return h;
}
uint16_t genTag(key_type t){
t ^= t>>32;
t ^= t>>16;
return t&0xffff;
}
//helper function that returns sum of array up to size
static int
sumArr(int* arr, uint32_t size){
uint32_t sum=0;
size<<=log_int_ca;
for(uint32_t i =0;i<size;i+=int_ca){
sum+=arr[i];
}
return sum;
}
//creates short to store in high bits of node * with info on next slot locations
static uint16_t
createNHMask(uint32_t hv, uint32_t ltsize){
hv >>= ltsize;
uint32_t nbits = MIN(32-ltsize, slot_bits);
hv &= ((1<<nbits)-1);
//idea here is that if less than 12 bits need to have them start at
//offset and change so that can use lower counter value (because
//which bit to use is calculated inversely)
hv <<= (slot_bits - nbits);
uint16_t hb = nbits;
hb |= (hv<<counter_bits);
return hb;
}
//creates a subtable
static subTable *
createST(hashTable * head, uint32_t tsize){
subTable * ht=(subTable *)myacalloc(cache_line_size, 1,sizeof(subTable));
//calloc better for large allocations and not important for this one to be aligned
ht->innerTable=(node **)calloc(tsize,sizeof(node *));
ht->threadCopy=(uint32_t *)calloc(DEFAULT_SPREAD,cache_line_size);
ht->logTableSize = ulog2(tsize);
ht->tableSize=tsize;
return ht;
}
//frees a given subtable that was created for adddrop (that failed)
static void
freeST(subTable * table){
free(table->innerTable);
free(table->threadCopy);
free(table);
}
//returns whether an node is found in a given subtable/overall hashtable. notIn means not
//in the hashtable, s means in the hashtable and was found (return s so can get the value
//of the item). unk means unknown if in table.
static uint32_t
lookupQuery(subTable * ht, key_type key, uint32_t slot){
//if find null slot know item is not in hashtable as would have been added there otherwise
if(getNodePtr(ht->innerTable[slot])==NULL){
#ifdef mark_null
if(getCopy(ht->innerTable[slot])){
return unknown;
}
#endif //mark_null
return not_in;
}
//values equal return index so can access later
else if(compare_keys(getKey(ht->innerTable[slot]), key)){
return slot;
}
//go another value, try next hash function
return unknown;
}
//returns whether an node is found in a given subtable/overall hashtable. notIn means not
//in the hashtable, s means in the hashtable and was found (return s so can get the value
//of the item). unk means unknown if in table.
static uint32_t
lookupDelete(subTable * ht, key_type key, uint32_t slot){
//if find null slot know item is not in hashtable as would have been added there otherwise
if(getNodePtr(ht->innerTable[slot])==NULL){
#ifdef mark_null
if(getCopy(ht->innerTable[slot])){
return unknown;
}
#endif //mark_null
return not_in;
}
//values equal return index so can access later
else if(compare_keys(getKey(ht->innerTable[slot]), key)){
return slot;
}
//go another value, try next hash function
return unknown;
}
//check if node for a given hashing vector is in a table. Returns in if node is already
//in the table, s if the value is not in the table, and unk to try the next hash function
static uint32_t
lookup(hashTable * head, subTable * ht, node * n, uint32_t slot, uint32_t tid
#ifdef lazy_resize
, uint32_t doCopy, uint32_t start
#endif //lazy_resize //
){
//if found null slot return index so insert can try and put the node in the index
if(getNodePtr(ht->innerTable[slot])==NULL){
#if defined mark_null && defined lazy_resize
if(doCopy){
if(setCopy(ht->innerTable[slot])){
resize_node(head, ht, slot, tid);
}
}
#endif //mark_null && lazy_resize
return slot;
}
//if values equal case
else if(compare_keys(getKey(ht->innerTable[slot]), getKey(n))){
return in;
}
#ifdef lazy_resize
//neither know if value is in or not, first check if this is smallest subtable and
//resizing is take place. If so move current item at subtable to next one.
if(doCopy){
if(setCopy(ht->innerTable[slot])){
//succesfully set by this thread
resize_node(head, ht, slot, tid);
}
}
#endif //lazy_resize
return unknown;
}
//function to add new subtable to hashtable if dont find open slot
static void
addDrop(hashTable * head, subTable * toadd, uint64_t addSlot, node * n, uint32_t tid, uint32_t start){
//make the array circular so add/delete continuous will always have space. Assumed that
//resizer will keep up with insertion rate (this is probably provable as when resize is active
//(which occurs when num subtables > threshhold each insert brings an old item with it). Also
//if the diff between max/min subtables exceed a certain bound will start doubling again
//irrelivant of delete/insert ratio
//try and add new preallocated table (CAS so only one added)
subTable * expected=NULL;
uint32_t res = __atomic_compare_exchange(&head->tableArray[addSlot&(max_tables-1)],
&expected,
&toadd,
1, __ATOMIC_RELAXED, __ATOMIC_RELAXED);
if(res){
//if succeeded try and update new max then insert item
uint64_t newSize=addSlot+1;
__atomic_compare_exchange(&head->end,
&addSlot,
&newSize,
1,__ATOMIC_RELAXED, __ATOMIC_RELAXED);
}
else{
//if failed free subtable then try and update new max then insert item
freeST(toadd);
uint64_t newSize=addSlot+1;
__atomic_compare_exchange(&head->end,
&addSlot,
&newSize,
1, __ATOMIC_RELAXED, __ATOMIC_RELAXED);
}
}
//wrapper fro resizing node, will call add resize if necessary and
//possible increment head's table index
static void
resize_node(hashTable * head, subTable * ht, uint32_t slot, uint32_t tid){
//exstart/newstart for CAS that might take place
uint64_t exStart=head->start;
uint64_t newStart=exStart+1;
//if item is not deleted copy up (this is how deleted items are culled as
//they will not be copied)
#ifdef mark_null
if(getNodePtr(ht->innerTable[slot])) {
#endif //mark_null
addNode_resize(head,
head->start+1,
lowBitsGetPtr(ht->innerTable[slot]),
tid,
getKeyTag(getKey(ht->innerTable[slot]))
#ifdef next_hash
,slot
#endif //next_hash
);
}
//increment thread index
incr(ht->threadCopy, tid, 1);
//if all slots have been copied increment min subtable
if(ht->tableSize==sumArr(ht->threadCopy, DEFAULT_SPREAD)){
if(__atomic_compare_exchange(&head->start,&exStart, &newStart,
1, __ATOMIC_RELAXED, __ATOMIC_RELAXED)){
//this is the potential race condition. We use to arrays,
//the toFree array will store old version that must be out
//of use by the time it will be freed. we know this because
//before addrop if table diff > max_tables>>1 will throw
//error let me know if you have a more elegant way of
//handling it (we obviously can just have it spin there, but
//is a SUPER SUPER SUPER unlikely event. I.e a thread would
//have to be dead for the time it takes the hashtable to
//inserts >2^32 * initsize entries.
}
}
}
//addnode resize we have different set of conditions that we can
// optimize around i.e should never find self deleted, dont need to
// recompute tag, and later will remove need for rehashing
uint32_t
addNode_resize(hashTable * head, uint32_t start, node * n, uint32_t tid, uint16_t tag
#ifdef next_hash
, uint32_t from_slot
#endif //next_hash
){
const uint32_t logReadsPerLine=DEFAULT_LOG_READS_PER_LINE;
const uint32_t uBound=DEFAULT_READS_PER_LINE;
const uint32_t ha=DEFAULT_HASH_ATTEMPTS;
uint32_t localEnd=head->end;
subTable * ht=NULL;
//////////////////////////////////////////////////////////////////////
//next hash optimization start
#ifdef next_hash
from_slot >>= logReadsPerLine;
from_slot <<= logReadsPerLine;
uint32_t amount_next_bits = getCounter(n);
uint32_t hb_index = slot_bits - amount_next_bits;
amount_next_bits += start;
subTable * prev = NULL;
ht=head->tableArray[(start-1)&(max_tables-1)];
for(uint32_t j=start;j<amount_next_bits;j++){
//prev for checking if new slot bit is needed
prev = ht;
ht=head->tableArray[j];
//tables wont grow in size if delete ratio is high enough this
//means just reused old slot
if(ht->tableSize != prev->tableSize){
// fprintf(stderr, "%d:: %x |= ((%x>>%d (%x) )&0x1) << %d -> ", j, from_slot, getNH(n, 0), hb_index, getNH(n, hb_index), (ht->logTableSize-1));
from_slot |= (getNH(n, hb_index)&0x1)<<(ht->logTableSize-1);
hb_index++;
}
//iterate through hash functions
for(uint32_t i =0; i<ha; i++) {
#ifdef kway
uint32_t slot=from_slot;
#endif //kway
for(uint32_t c=tag;c<uBound+tag;c++){
uint32_t res=lookup(head, ht, n, slot+(c&(uBound-1)), tid
#ifdef lazy_resize
, 0, j
#endif //lazy_resize
);
//lookup value in sub table
if(res==unknown){ //unkown if in our not
continue;
}
else if(res==in){ //is in
return 0;
}
else{
//if return was null (is available slot in sub table) try and add with CAS.
//if succeed return 1, else if value it lost to is item itself return. If neither
//continue trying to add
//update counter based on bits used
setCounter(n, slot_bits - hb_index);
node * expected=NULL;
uint32_t cmp= __atomic_compare_exchange((ht->innerTable+res),
&expected,
&n,
1, __ATOMIC_RELAXED, __ATOMIC_RELAXED);
if(cmp){
//if we win CAS increment insert count and return 1
return 1;
}
else{
//else check if value that was lost to is same, if not keep going, if is
//turn 0
if(compare_keys(getKey(ht->innerTable[res]),getKey(n))){
return 0;
}
}
}
}
}
//update locacur
localEnd=head->end;
//could just drop through here but imo
//should keep trying to reused bits
if(j>=(head->end-1)){
//if found no available slot in hashtable create new subtable and add it to hashtable
//then try insertion again
//next table size defaults to same size
uint32_t nextTableSize = head->tableArray[(localEnd-1)&(max_tables-1)]->tableSize << 1;
//create next subtables
subTable * new_table=createST(head, nextTableSize);
addDrop(head, new_table, localEnd, n, tid, start+1);
}
}
#endif //next_hash
//////////////////////////////////////////////////////////////////////
//next hash optimization end
#ifdef kway
uint32_t buckets[DEFAULT_HASH_ATTEMPTS];
for(uint32_t i =0;i<ha;i++){
buckets[i]=hashFun(getKey(n), head->seeds[i]);
}
#endif //kway
while(1){
//iterate through subtables
//again is mod max_subtables
for(uint32_t iter_j=start;iter_j<head->end;iter_j++){
uint32_t j = iter_j&(max_tables-1);
ht=head->tableArray[j];
uint32_t tsizeMask=((ht->tableSize-1)>>logReadsPerLine)<<logReadsPerLine;
//iterate through hash functions
for(uint32_t i =0; i<ha; i++) {
#ifdef kway
uint32_t slot=(buckets[i]&tsizeMask);
#endif //kway
for(uint32_t c=tag;c<uBound+tag;c++){
uint32_t res=lookup(head, ht, n, slot+(c&(uBound-1)), tid
#ifdef lazy_resize
, 0, j
#endif //lazy_resize
);
//lookup value in sub table
if(res==unknown){ //unkown if in our not
continue;
}
else if(res==in){ //is in
return 0;
}
else{
//if return was null (is available slot in sub table) try and add with CAS.
//if succeed return 1, else if value it lost to is item itself return. If neither
//continue trying to add
#ifdef next_hash
#ifdef kway
uint16_t next_bits = createNHMask(buckets[0], ht->logTableSize);
#endif //kway
setNH(n, next_bits);
#endif //next_hash
node * expected=NULL;
uint32_t cmp= __atomic_compare_exchange((ht->innerTable+res),
&expected,
&n,
1, __ATOMIC_RELAXED, __ATOMIC_RELAXED);
if(cmp){
//if we win CAS increment insert count and return 1
return 1;
}
else{
//else check if value that was lost to is same, if not keep going, if is
//turn 0
if(compare_keys(getKey(ht->innerTable[res]),getKey(n))){
return 0;
}
}
}
} //this is from the loop in ubound
}
//update locacur
localEnd=head->end;
}
//if found no available slot in hashtable create new subtable and add it to hashtable
//then try insertion again
//next table size defaults to same size
uint32_t nextTableSize = head->tableArray[(localEnd-1)&(max_tables-1)]->tableSize << 1;
//create next subtables
subTable * new_table=createST(head, nextTableSize);
addDrop(head, new_table, localEnd, n, tid, start+1);
start=localEnd;
}
}
//////////////////////////////////////////////////////////////////////
//end helpers
//api function user calls to query the table for a given node. Returns
//1 if found, 0 otherwise.
node *
findNode(hashTable * head, key_type key, uint32_t tid){
subTable * ht=NULL;
const uint32_t logReadsPerLine=DEFAULT_LOG_READS_PER_LINE;
const uint32_t uBound=DEFAULT_READS_PER_LINE;
const uint32_t ha=DEFAULT_HASH_ATTEMPTS;
#ifdef kway
uint32_t buckets[DEFAULT_HASH_ATTEMPTS];
for(uint32_t i =0;i<ha;i++){
buckets[i]=hashFun(key, head->seeds[i]);
}
#endif //kway
uint16_t tag = hashTag(key);
for(uint32_t iter_j=head->start;iter_j<head->end;iter_j++){
uint32_t j = iter_j&(max_tables-1);
ht=head->tableArray[j];
uint32_t tsizeMask=((ht->tableSize-1)>>logReadsPerLine)<<logReadsPerLine;
for(uint32_t i =0; i<ha; i++) {
#ifdef kway
uint32_t slot=(buckets[i]&tsizeMask);
#endif //kway
for(uint32_t c=tag;c<uBound+tag;c++){
uint32_t res=lookupQuery(
ht, key, slot+(c&(uBound-1)));
if(res==unknown){ //unkown if in our not
continue;
}
if(res==not_in){
return NULL; /* indicate not found */
}
return getNodePtr(ht->innerTable[res]);
}
}
}
//was never found
return NULL;
}
//insert a new node into the table. Returns 0 if node is already present, 1 otherwise.
node *
addNode(hashTable * head, node * n, uint32_t tid){
//use local max for adddroping subtables (otherwise race condition where 2 go to add
//to slot n, one completes addition and increments head->end and then second one goes
//and double adds. Won't affect correctness but best not have that happen.
const uint32_t logReadsPerLine=DEFAULT_LOG_READS_PER_LINE;
const uint32_t uBound=DEFAULT_READS_PER_LINE;
const uint32_t ha=DEFAULT_HASH_ATTEMPTS;
#ifdef kway
uint32_t buckets[DEFAULT_HASH_ATTEMPTS];
for(uint32_t i =0;i<ha;i++){
buckets[i]=hashFun(getKey(n), head->seeds[i]);
}
#endif //kway
uint16_t tag = hashTag(getKey(n));
uint32_t localEnd=head->end;
uint32_t start = head->start;
subTable * ht=NULL;
while(1){
//iterate through subtables
//again is mod max_subtables
for(uint32_t j=start;j<head->end;j++){
ht=head->tableArray[j];
uint32_t tsizeMask=((ht->tableSize-1)>>logReadsPerLine)<<logReadsPerLine;
//do copy if there is a new bigger subtable and currently in smallest subtable
uint32_t doCopy=(j==(head->start&(max_tables-1)))&&(head->end-head->start>RESIZE_THRESHOLD);
//iterate through hash functions
for(uint32_t i =0; i<ha; i++) {
#ifdef kway
uint32_t slot=(buckets[i]&tsizeMask);
#endif //kway
for(uint32_t c=tag;c<uBound+tag;c++){
uint32_t res=lookup(head, ht, n, slot+(c&(uBound-1)), tid
#ifdef lazy_resize
, doCopy, j
#endif //lazy_resize
);
//lookup value in sub table
if(res==unknown){ //unkown if in our not
continue;
}
else if(res==in){ //is in
return set_return(ht->innerTable[slot+(c&(uBound-1))], 1);
}
else{
//start with absolutely known place for safe next bit
//calculation note that has to be before CAS otherwise can
//create race condition (can for example cause setCopy to
//fail spuriously)
//next hash stuff here
#ifdef next_hash
#ifdef kway
uint16_t next_bits = createNHMask(buckets[0], ht->logTableSize);
#endif //kway
setNH(n, next_bits);
#endif //next_hash
//if return was null (is available slot in sub table) try and add with CAS.
//if succeed return 1, else if value it lost to is item itself return. If neither
//continue trying to add
node * expected=NULL;
uint32_t cmp= __atomic_compare_exchange((ht->innerTable+res),
&expected,
&n,
1, __ATOMIC_RELAXED, __ATOMIC_RELAXED);
if(cmp){
//if we win CAS increment insert count and return 1
return set_return(ht->innerTable[res], 1);
}
else{
//else check if value that was lost to is same, if not keep going, if is
//turn 0
if(
#ifdef mark_null
getNodePtr(ht->innerTable[res]) &&
#endif //mark_null
compare_keys(getKey(ht->innerTable[res]),getKey(n))){
return set_return(ht->innerTable[res], 0);
}
}
}
} //this is from the loop in ubound
}
//update locacur
localEnd=head->end;
}
//if found no available slot in hashtable create new subtable and add it to hashtable
//then try insertion again
//next table size defaults to same size
uint32_t nextTableSize = head->tableArray[(localEnd-1)&(max_tables-1)]->tableSize << 1;
//create next subtables
subTable * new_table=createST(head, nextTableSize);
addDrop(head, new_table, localEnd, n, tid, start+1);
start=localEnd;
}
}
//initial hashtable. First table head will be null, after that will
//just reinitialize first table returns a pointer to the hashtable
hashTable *
initTable() {
hashTable * head=(hashTable *)myacalloc(cache_line_size, 1,sizeof(hashTable));
head->tableArray=(subTable **)myacalloc(cache_line_size, max_tables,sizeof(subTable *));
head->tableArray[0]=createST(head, DEFAULT_INIT_SIZE);
head->end=1;
head->start=0;
for(uint32_t i=0; i<DEFAULT_HASH_ATTEMPTS; i++) {
uint32_t rand_var = rand();
memcpy(((void *)head) + offsetof(hashTable, seeds) + (i * sizeof(uint32_t)),
&rand_var,
sizeof(rand_var));
}
return head;
}
uint64_t
getStart(hashTable * head){
return head->start;
}