-
Notifications
You must be signed in to change notification settings - Fork 94
/
st.c
2384 lines (2182 loc) · 65.6 KB
/
st.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
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* This is a public domain general purpose hash table package
originally written by Peter Moore @ UCB.
The hash table data structures were redesigned and the package was
rewritten by Vladimir Makarov <[email protected]>. */
/* The original package implemented classic bucket-based hash tables
with entries doubly linked for an access by their insertion order.
To decrease pointer chasing and as a consequence to improve a data
locality the current implementation is based on storing entries in
an array and using hash tables with open addressing. The current
entries are more compact in comparison with the original ones and
this also improves the data locality.
The hash table has two arrays called *bins* and *entries*.
bins:
-------
| | entries array:
|-------| --------------------------------
| index | | | entry: | | |
|-------| | | | | |
| ... | | ... | hash | ... | ... |
|-------| | | key | | |
| empty | | | record | | |
|-------| --------------------------------
| ... | ^ ^
|-------| |_ entries start |_ entries bound
|deleted|
-------
o The entry array contains table entries in the same order as they
were inserted.
When the first entry is deleted, a variable containing index of
the current first entry (*entries start*) is changed. In all
other cases of the deletion, we just mark the entry as deleted by
using a reserved hash value.
Such organization of the entry storage makes operations of the
table shift and the entries traversal very fast.
o The bins provide access to the entries by their keys. The
key hash is mapped to a bin containing *index* of the
corresponding entry in the entry array.
The bin array size is always power of two, it makes mapping very
fast by using the corresponding lower bits of the hash.
Generally it is not a good idea to ignore some part of the hash.
But alternative approach is worse. For example, we could use a
modulo operation for mapping and a prime number for the size of
the bin array. Unfortunately, the modulo operation for big
64-bit numbers are extremely slow (it takes more than 100 cycles
on modern Intel CPUs).
Still other bits of the hash value are used when the mapping
results in a collision. In this case we use a secondary hash
value which is a result of a function of the collision bin
index and the original hash value. The function choice
guarantees that we can traverse all bins and finally find the
corresponding bin as after several iterations the function
becomes a full cycle linear congruential generator because it
satisfies requirements of the Hull-Dobell theorem.
When an entry is removed from the table besides marking the
hash in the corresponding entry described above, we also mark
the bin by a special value in order to find entries which had
a collision with the removed entries.
There are two reserved values for the bins. One denotes an
empty bin, another one denotes a bin for a deleted entry.
o The length of the bin array is at least two times more than the
entry array length. This keeps the table load factor healthy.
The trigger of rebuilding the table is always a case when we can
not insert an entry anymore at the entries bound. We could
change the entries bound too in case of deletion but than we need
a special code to count bins with corresponding deleted entries
and reset the bin values when there are too many bins
corresponding deleted entries
Table rebuilding is done by creation of a new entry array and
bins of an appropriate size. We also try to reuse the arrays
in some cases by compacting the array and removing deleted
entries.
o To save memory very small tables have no allocated arrays
bins. We use a linear search for an access by a key.
o To save more memory we use 8-, 16-, 32- and 64- bit indexes in
bins depending on the current hash table size.
o The implementation takes into account that the table can be
rebuilt during hashing or comparison functions. It can happen if
the functions are implemented in Ruby and a thread switch occurs
during their execution.
This implementation speeds up the Ruby hash table benchmarks in
average by more 40% on Intel Haswell CPU.
*/
#ifdef RUBY
#include "internal.h"
#else
#include "regint.h"
#include "st.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#ifdef __GNUC__
#define PREFETCH(addr, write_p) __builtin_prefetch(addr, write_p)
#define EXPECT(expr, val) __builtin_expect(expr, val)
#define ATTRIBUTE_UNUSED __attribute__((unused))
#else
#define PREFETCH(addr, write_p)
#define EXPECT(expr, val) (expr)
#define ATTRIBUTE_UNUSED
#endif
#ifdef ST_DEBUG
#define st_assert assert
#else
#define st_assert(cond) ((void)(0 && (cond)))
#endif
/* The type of hashes. */
typedef st_index_t st_hash_t;
struct st_table_entry {
st_hash_t hash;
st_data_t key;
st_data_t record;
};
#ifdef RUBY
#define type_numhash st_hashtype_num
static const struct st_hash_type st_hashtype_num = {
st_numcmp,
st_numhash,
};
/* extern int strcmp(const char *, const char *); */
static st_index_t strhash(st_data_t);
static const struct st_hash_type type_strhash = {
strcmp,
strhash,
};
static st_index_t strcasehash(st_data_t);
static const struct st_hash_type type_strcasehash = {
st_locale_insensitive_strcasecmp,
strcasehash,
};
#endif /* RUBY */
/* Value used to catch uninitialized entries/bins during debugging.
There is a possibility for a false alarm, but its probability is
extremely small. */
#define ST_INIT_VAL 0xafafafafafafafaf
#define ST_INIT_VAL_BYTE 0xafa
#ifdef RUBY
#undef malloc
#undef realloc
#undef calloc
#undef free
#define malloc ruby_xmalloc
#define calloc ruby_xcalloc
#define realloc ruby_xrealloc
#define free ruby_xfree
#else /* RUBY */
#define MEMCPY(p1,p2,type,n) memcpy((p1), (p2), sizeof(type)*(n))
#endif /* RUBY */
#define EQUAL(tab,x,y) ((x) == (y) || (*(tab)->type->compare)((x),(y)) == 0)
#define PTR_EQUAL(tab, ptr, hash_val, key_) \
((ptr)->hash == (hash_val) && EQUAL((tab), (key_), (ptr)->key))
/* As PRT_EQUAL only its result is returned in RES. REBUILT_P is set
up to TRUE if the table is rebuilt during the comparison. */
#define DO_PTR_EQUAL_CHECK(tab, ptr, hash_val, key, res, rebuilt_p) \
do { \
unsigned int _old_rebuilds_num = (tab)->rebuilds_num; \
res = PTR_EQUAL(tab, ptr, hash_val, key); \
rebuilt_p = _old_rebuilds_num != (tab)->rebuilds_num; \
} while (FALSE)
/* Features of a table. */
struct st_features {
/* Power of 2 used for number of allocated entries. */
unsigned char entry_power;
/* Power of 2 used for number of allocated bins. Depending on the
table size, the number of bins is 2-4 times more than the
number of entries. */
unsigned char bin_power;
/* Enumeration of sizes of bins (8-bit, 16-bit etc). */
unsigned char size_ind;
/* Bins are packed in words of type st_index_t. The following is
a size of bins counted by words. */
st_index_t bins_words;
};
/* Features of all possible size tables. */
#if SIZEOF_ST_INDEX_T == 8
#define MAX_POWER2 62
static const struct st_features features[] = {
{0, 1, 0, 0x0},
{1, 2, 0, 0x1},
{2, 3, 0, 0x1},
{3, 4, 0, 0x2},
{4, 5, 0, 0x4},
{5, 6, 0, 0x8},
{6, 7, 0, 0x10},
{7, 8, 0, 0x20},
{8, 9, 1, 0x80},
{9, 10, 1, 0x100},
{10, 11, 1, 0x200},
{11, 12, 1, 0x400},
{12, 13, 1, 0x800},
{13, 14, 1, 0x1000},
{14, 15, 1, 0x2000},
{15, 16, 1, 0x4000},
{16, 17, 2, 0x10000},
{17, 18, 2, 0x20000},
{18, 19, 2, 0x40000},
{19, 20, 2, 0x80000},
{20, 21, 2, 0x100000},
{21, 22, 2, 0x200000},
{22, 23, 2, 0x400000},
{23, 24, 2, 0x800000},
{24, 25, 2, 0x1000000},
{25, 26, 2, 0x2000000},
{26, 27, 2, 0x4000000},
{27, 28, 2, 0x8000000},
{28, 29, 2, 0x10000000},
{29, 30, 2, 0x20000000},
{30, 31, 2, 0x40000000},
{31, 32, 2, 0x80000000},
{32, 33, 3, 0x200000000},
{33, 34, 3, 0x400000000},
{34, 35, 3, 0x800000000},
{35, 36, 3, 0x1000000000},
{36, 37, 3, 0x2000000000},
{37, 38, 3, 0x4000000000},
{38, 39, 3, 0x8000000000},
{39, 40, 3, 0x10000000000},
{40, 41, 3, 0x20000000000},
{41, 42, 3, 0x40000000000},
{42, 43, 3, 0x80000000000},
{43, 44, 3, 0x100000000000},
{44, 45, 3, 0x200000000000},
{45, 46, 3, 0x400000000000},
{46, 47, 3, 0x800000000000},
{47, 48, 3, 0x1000000000000},
{48, 49, 3, 0x2000000000000},
{49, 50, 3, 0x4000000000000},
{50, 51, 3, 0x8000000000000},
{51, 52, 3, 0x10000000000000},
{52, 53, 3, 0x20000000000000},
{53, 54, 3, 0x40000000000000},
{54, 55, 3, 0x80000000000000},
{55, 56, 3, 0x100000000000000},
{56, 57, 3, 0x200000000000000},
{57, 58, 3, 0x400000000000000},
{58, 59, 3, 0x800000000000000},
{59, 60, 3, 0x1000000000000000},
{60, 61, 3, 0x2000000000000000},
{61, 62, 3, 0x4000000000000000},
{62, 63, 3, 0x8000000000000000},
};
#else
#define MAX_POWER2 30
static const struct st_features features[] = {
{0, 1, 0, 0x1},
{1, 2, 0, 0x1},
{2, 3, 0, 0x2},
{3, 4, 0, 0x4},
{4, 5, 0, 0x8},
{5, 6, 0, 0x10},
{6, 7, 0, 0x20},
{7, 8, 0, 0x40},
{8, 9, 1, 0x100},
{9, 10, 1, 0x200},
{10, 11, 1, 0x400},
{11, 12, 1, 0x800},
{12, 13, 1, 0x1000},
{13, 14, 1, 0x2000},
{14, 15, 1, 0x4000},
{15, 16, 1, 0x8000},
{16, 17, 2, 0x20000},
{17, 18, 2, 0x40000},
{18, 19, 2, 0x80000},
{19, 20, 2, 0x100000},
{20, 21, 2, 0x200000},
{21, 22, 2, 0x400000},
{22, 23, 2, 0x800000},
{23, 24, 2, 0x1000000},
{24, 25, 2, 0x2000000},
{25, 26, 2, 0x4000000},
{26, 27, 2, 0x8000000},
{27, 28, 2, 0x10000000},
{28, 29, 2, 0x20000000},
{29, 30, 2, 0x40000000},
{30, 31, 2, 0x80000000},
};
#endif
/* The reserved hash value and its substitution. */
#define RESERVED_HASH_VAL (~(st_hash_t) 0)
#define RESERVED_HASH_SUBSTITUTION_VAL ((st_hash_t) 0)
const st_hash_t st_reserved_hash_val = RESERVED_HASH_VAL;
const st_hash_t st_reserved_hash_substitution_val = RESERVED_HASH_SUBSTITUTION_VAL;
/* Return hash value of KEY for table TAB. */
static inline st_hash_t
do_hash(st_data_t key, st_table *tab)
{
st_hash_t hash = (st_hash_t)(tab->type->hash)(key);
/* RESERVED_HASH_VAL is used for a deleted entry. Map it into
another value. Such mapping should be extremely rare. */
return hash == RESERVED_HASH_VAL ? RESERVED_HASH_SUBSTITUTION_VAL : hash;
}
/* Power of 2 defining the minimal number of allocated entries. */
#define MINIMAL_POWER2 2
#if MINIMAL_POWER2 < 2
#error "MINIMAL_POWER2 should be >= 2"
#endif
/* If the power2 of the allocated `entries` is less than the following
value, don't allocate bins and use a linear search. */
#define MAX_POWER2_FOR_TABLES_WITHOUT_BINS 4
/* Return smallest n >= MINIMAL_POWER2 such 2^n > SIZE. */
static int
get_power2(st_index_t size)
{
unsigned int n;
for (n = 0; size != 0; n++)
size >>= 1;
if (n <= MAX_POWER2)
return n < MINIMAL_POWER2 ? MINIMAL_POWER2 : n;
#ifdef RUBY
/* Ran out of the table entries */
rb_raise(rb_eRuntimeError, "st_table too big");
#endif
/* should raise exception */
return -1;
}
/* Return value of N-th bin in array BINS of table with bins size
index S. */
static inline st_index_t
get_bin(st_index_t *bins, int s, st_index_t n)
{
return (s == 0 ? ((unsigned char *) bins)[n]
: s == 1 ? ((unsigned short *) bins)[n]
: s == 2 ? ((unsigned int *) bins)[n]
: ((st_index_t *) bins)[n]);
}
/* Set up N-th bin in array BINS of table with bins size index S to
value V. */
static inline void
set_bin(st_index_t *bins, int s, st_index_t n, st_index_t v)
{
if (s == 0) ((unsigned char *) bins)[n] = (unsigned char) v;
else if (s == 1) ((unsigned short *) bins)[n] = (unsigned short) v;
else if (s == 2) ((unsigned int *) bins)[n] = (unsigned int) v;
else ((st_index_t *) bins)[n] = v;
}
/* These macros define reserved values for empty table bin and table
bin which contains a deleted entry. We will never use such values
for an entry index in bins. */
#define EMPTY_BIN 0
#define DELETED_BIN 1
/* Base of a real entry index in the bins. */
#define ENTRY_BASE 2
/* Mark I-th bin of table TAB as empty, in other words not
corresponding to any entry. */
#define MARK_BIN_EMPTY(tab, i) (set_bin((tab)->bins, get_size_ind(tab), i, EMPTY_BIN))
/* Values used for not found entry and bin with given
characteristics. */
#define UNDEFINED_ENTRY_IND (~(st_index_t) 0)
#define UNDEFINED_BIN_IND (~(st_index_t) 0)
/* Entry and bin values returned when we found a table rebuild during
the search. */
#define REBUILT_TABLE_ENTRY_IND (~(st_index_t) 1)
#define REBUILT_TABLE_BIN_IND (~(st_index_t) 1)
/* Mark I-th bin of table TAB as corresponding to a deleted table
entry. Update number of entries in the table and number of bins
corresponding to deleted entries. */
#define MARK_BIN_DELETED(tab, i) \
do { \
st_assert(i != UNDEFINED_BIN_IND); \
st_assert(! IND_EMPTY_OR_DELETED_BIN_P(tab, i)); \
set_bin((tab)->bins, get_size_ind(tab), i, DELETED_BIN); \
} while (0)
/* Macros to check that value B is used empty bins and bins
corresponding deleted entries. */
#define EMPTY_BIN_P(b) ((b) == EMPTY_BIN)
#define DELETED_BIN_P(b) ((b) == DELETED_BIN)
#define EMPTY_OR_DELETED_BIN_P(b) ((b) <= DELETED_BIN)
/* Macros to check empty bins and bins corresponding to deleted
entries. Bins are given by their index I in table TAB. */
#define IND_EMPTY_BIN_P(tab, i) (EMPTY_BIN_P(get_bin((tab)->bins, get_size_ind(tab), i)))
#define IND_DELETED_BIN_P(tab, i) (DELETED_BIN_P(get_bin((tab)->bins, get_size_ind(tab), i)))
#define IND_EMPTY_OR_DELETED_BIN_P(tab, i) (EMPTY_OR_DELETED_BIN_P(get_bin((tab)->bins, get_size_ind(tab), i)))
/* Macros for marking and checking deleted entries given by their
pointer E_PTR. */
#define MARK_ENTRY_DELETED(e_ptr) ((e_ptr)->hash = RESERVED_HASH_VAL)
#define DELETED_ENTRY_P(e_ptr) ((e_ptr)->hash == RESERVED_HASH_VAL)
/* Return bin size index of table TAB. */
static inline unsigned int
get_size_ind(const st_table *tab)
{
return tab->size_ind;
}
/* Return the number of allocated bins of table TAB. */
static inline st_index_t
get_bins_num(const st_table *tab)
{
return ((st_index_t) 1)<<tab->bin_power;
}
/* Return mask for a bin index in table TAB. */
static inline st_index_t
bins_mask(const st_table *tab)
{
return get_bins_num(tab) - 1;
}
/* Return the index of table TAB bin corresponding to
HASH_VALUE. */
static inline st_index_t
hash_bin(st_hash_t hash_value, st_table *tab)
{
return hash_value & bins_mask(tab);
}
/* Return the number of allocated entries of table TAB. */
static inline st_index_t
get_allocated_entries(const st_table *tab)
{
return ((st_index_t) 1)<<tab->entry_power;
}
/* Return size of the allocated bins of table TAB. */
static inline st_index_t
bins_size(const st_table *tab)
{
return features[tab->entry_power].bins_words * sizeof (st_index_t);
}
/* Mark all bins of table TAB as empty. */
static void
initialize_bins(st_table *tab)
{
memset(tab->bins, 0, bins_size(tab));
}
/* Make table TAB empty. */
static void
make_tab_empty(st_table *tab)
{
tab->num_entries = 0;
tab->entries_start = tab->entries_bound = 0;
if (tab->bins != NULL)
initialize_bins(tab);
}
#ifdef ST_DEBUG
#define st_assert_notinitial(ent) \
do { \
st_assert(ent.hash != (st_hash_t) ST_INIT_VAL); \
st_assert(ent.key != ST_INIT_VAL); \
st_assert(ent.record != ST_INIT_VAL); \
} while (0)
/* Check the table T consistency. It can be extremely slow. So use
it only for debugging. */
static void
st_check(st_table *tab)
{
st_index_t d, e, i, n, p;
for (p = get_allocated_entries(tab), i = 0; p > 1; i++, p>>=1)
;
p = i;
st_assert(p >= MINIMAL_POWER2);
st_assert(tab->entries_bound <= get_allocated_entries(tab));
st_assert(tab->entries_start <= tab->entries_bound);
n = 0;
return;
if (tab->entries_bound != 0)
for (i = tab->entries_start; i < tab->entries_bound; i++) {
st_assert_notinitial(tab->entries[i]);
if (! DELETED_ENTRY_P(&tab->entries[i]))
n++;
}
st_assert(n == tab->num_entries);
if (tab->bins == NULL)
st_assert(p <= MAX_POWER2_FOR_TABLES_WITHOUT_BINS);
else {
st_assert(p > MAX_POWER2_FOR_TABLES_WITHOUT_BINS);
for (n = d = i = 0; i < get_bins_num(tab); i++) {
st_assert(get_bin(tab->bins, tab->size_ind, i) != ST_INIT_VAL);
if (IND_DELETED_BIN_P(tab, i)) {
d++;
continue;
}
else if (IND_EMPTY_BIN_P(tab, i))
continue;
n++;
e = get_bin(tab->bins, tab->size_ind, i) - ENTRY_BASE;
st_assert(tab->entries_start <= e && e < tab->entries_bound);
st_assert(! DELETED_ENTRY_P(&tab->entries[e]));
st_assert_notinitial(tab->entries[e]);
}
st_assert(n == tab->num_entries);
st_assert(n + d < get_bins_num(tab));
}
}
#endif
#ifdef HASH_LOG
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
static struct {
int all, total, num, str, strcase;
} collision;
/* Flag switching off output of package statistics at the end of
program. */
static int init_st = 0;
/* Output overall number of table searches and collisions into a
temporary file. */
static void
stat_col(void)
{
char fname[10+sizeof(long)*3];
FILE *f;
if (!collision.total) return;
f = fopen((snprintf(fname, sizeof(fname), "/tmp/col%ld", (long)getpid()), fname), "w");
if (f == NULL)
return;
fprintf(f, "collision: %d / %d (%6.2f)\n", collision.all, collision.total,
((double)collision.all / (collision.total)) * 100);
fprintf(f, "num: %d, str: %d, strcase: %d\n", collision.num, collision.str, collision.strcase);
fclose(f);
}
#endif
/* Create and return table with TYPE which can hold at least SIZE
entries. The real number of entries which the table can hold is
the nearest power of two for SIZE. */
st_table *
st_init_table_with_size(const struct st_hash_type *type, st_index_t size)
{
st_table *tab;
int n;
#ifdef HASH_LOG
#if HASH_LOG+0 < 0
{
const char *e = getenv("ST_HASH_LOG");
if (!e || !*e) init_st = 1;
}
#endif
if (init_st == 0) {
init_st = 1;
atexit(stat_col);
}
#endif
n = get_power2(size);
#ifndef RUBY
if (n < 0)
return NULL;
#endif
tab = (st_table *) malloc(sizeof (st_table));
#ifndef RUBY
if (tab == NULL)
return NULL;
#endif
tab->type = type;
tab->entry_power = n;
tab->bin_power = features[n].bin_power;
tab->size_ind = features[n].size_ind;
if (n <= MAX_POWER2_FOR_TABLES_WITHOUT_BINS)
tab->bins = NULL;
else {
tab->bins = (st_index_t *) malloc(bins_size(tab));
#ifndef RUBY
if (tab->bins == NULL) {
free(tab);
return NULL;
}
#endif
}
tab->entries = (st_table_entry *) malloc(get_allocated_entries(tab)
* sizeof(st_table_entry));
#ifndef RUBY
if (tab->entries == NULL) {
st_free_table(tab);
return NULL;
}
#endif
#ifdef ST_DEBUG
memset(tab->entries, ST_INIT_VAL_BYTE,
get_allocated_entries(tab) * sizeof(st_table_entry));
if (tab->bins != NULL)
memset(tab->bins, ST_INIT_VAL_BYTE, bins_size(tab));
#endif
make_tab_empty(tab);
tab->rebuilds_num = 0;
#ifdef ST_DEBUG
st_check(tab);
#endif
return tab;
}
#ifdef RUBY
/* Create and return table with TYPE which can hold a minimal number
of entries (see comments for get_power2). */
st_table *
st_init_table(const struct st_hash_type *type)
{
return st_init_table_with_size(type, 0);
}
/* Create and return table which can hold a minimal number of
numbers. */
st_table *
st_init_numtable(void)
{
return st_init_table(&type_numhash);
}
/* Create and return table which can hold SIZE numbers. */
st_table *
st_init_numtable_with_size(st_index_t size)
{
return st_init_table_with_size(&type_numhash, size);
}
/* Create and return table which can hold a minimal number of
strings. */
st_table *
st_init_strtable(void)
{
return st_init_table(&type_strhash);
}
/* Create and return table which can hold SIZE strings. */
st_table *
st_init_strtable_with_size(st_index_t size)
{
return st_init_table_with_size(&type_strhash, size);
}
/* Create and return table which can hold a minimal number of strings
whose character case is ignored. */
st_table *
st_init_strcasetable(void)
{
return st_init_table(&type_strcasehash);
}
/* Create and return table which can hold SIZE strings whose character
case is ignored. */
st_table *
st_init_strcasetable_with_size(st_index_t size)
{
return st_init_table_with_size(&type_strcasehash, size);
}
/* Make table TAB empty. */
void
st_clear(st_table *tab)
{
make_tab_empty(tab);
tab->rebuilds_num++;
#ifdef ST_DEBUG
st_check(tab);
#endif
}
#endif /* RUBY */
/* Free table TAB space. */
void
st_free_table(st_table *tab)
{
if (tab->bins != NULL)
free(tab->bins);
free(tab->entries);
free(tab);
}
#ifdef RUBY
/* Return byte size of memory allocated for table TAB. */
size_t
st_memsize(const st_table *tab)
{
return(sizeof(st_table)
+ (tab->bins == NULL ? 0 : bins_size(tab))
+ get_allocated_entries(tab) * sizeof(st_table_entry));
}
#endif /* RUBY */
static st_index_t
find_table_entry_ind(st_table *tab, st_hash_t hash_value, st_data_t key);
static st_index_t
find_table_bin_ind(st_table *tab, st_hash_t hash_value, st_data_t key);
static st_index_t
find_table_bin_ind_direct(st_table *table, st_hash_t hash_value, st_data_t key);
static st_index_t
find_table_bin_ptr_and_reserve(st_table *tab, st_hash_t *hash_value,
st_data_t key, st_index_t *bin_ind);
#ifdef HASH_LOG
static void
count_collision(const struct st_hash_type *type)
{
collision.all++;
if (type == &type_numhash) {
collision.num++;
}
else if (type == &type_strhash) {
collision.strcase++;
}
else if (type == &type_strcasehash) {
collision.str++;
}
}
#define COLLISION (collision_check ? count_collision(tab->type) : (void)0)
#define FOUND_BIN (collision_check ? collision.total++ : (void)0)
#define collision_check 0
#else
#define COLLISION
#define FOUND_BIN
#endif
/* If the number of entries in the table is at least REBUILD_THRESHOLD
times less than the entry array length, decrease the table
size. */
#define REBUILD_THRESHOLD 4
#if REBUILD_THRESHOLD < 2
#error "REBUILD_THRESHOLD should be >= 2"
#endif
/* Rebuild table TAB. Rebuilding removes all deleted bins and entries
and can change size of the table entries and bins arrays.
Rebuilding is implemented by creation of a new table or by
compaction of the existing one. */
static void
rebuild_table(st_table *tab)
{
st_index_t i, ni, bound;
unsigned int size_ind;
st_table *new_tab;
st_table_entry *entries, *new_entries;
st_table_entry *curr_entry_ptr;
st_index_t *bins;
st_index_t bin_ind;
st_assert(tab != NULL);
bound = tab->entries_bound;
entries = tab->entries;
if ((2 * tab->num_entries <= get_allocated_entries(tab)
&& REBUILD_THRESHOLD * tab->num_entries > get_allocated_entries(tab))
|| tab->num_entries < (1 << MINIMAL_POWER2)) {
/* Compaction: */
tab->num_entries = 0;
if (tab->bins != NULL)
initialize_bins(tab);
new_tab = tab;
new_entries = entries;
}
else {
new_tab = st_init_table_with_size(tab->type,
2 * tab->num_entries - 1);
new_entries = new_tab->entries;
}
ni = 0;
bins = new_tab->bins;
size_ind = get_size_ind(new_tab);
for (i = tab->entries_start; i < bound; i++) {
curr_entry_ptr = &entries[i];
PREFETCH(entries + i + 1, 0);
if (EXPECT(DELETED_ENTRY_P(curr_entry_ptr), 0))
continue;
if (&new_entries[ni] != curr_entry_ptr)
new_entries[ni] = *curr_entry_ptr;
if (EXPECT(bins != NULL, 1)) {
bin_ind = find_table_bin_ind_direct(new_tab, curr_entry_ptr->hash,
curr_entry_ptr->key);
st_assert(bin_ind != UNDEFINED_BIN_IND);
st_assert(tab == new_tab || new_tab->rebuilds_num == 0);
st_assert(IND_EMPTY_BIN_P(new_tab, bin_ind));
set_bin(bins, size_ind, bin_ind, ni + ENTRY_BASE);
}
new_tab->num_entries++;
ni++;
}
if (new_tab != tab) {
tab->entry_power = new_tab->entry_power;
tab->bin_power = new_tab->bin_power;
tab->size_ind = new_tab->size_ind;
st_assert(tab->num_entries == ni);
st_assert(new_tab->num_entries == ni);
if (tab->bins != NULL)
free(tab->bins);
tab->bins = new_tab->bins;
free(tab->entries);
tab->entries = new_tab->entries;
free(new_tab);
}
tab->entries_start = 0;
tab->entries_bound = tab->num_entries;
tab->rebuilds_num++;
#ifdef ST_DEBUG
st_check(tab);
#endif
}
/* Return the next secondary hash index for table TAB using previous
index IND and PERTERB. Finally modulo of the function becomes a
full *cycle linear congruential generator*, in other words it
guarantees traversing all table bins in extreme case.
According the Hull-Dobell theorem a generator
"Xnext = (a*Xprev + c) mod m" is a full cycle generator iff
o m and c are relatively prime
o a-1 is divisible by all prime factors of m
o a-1 is divisible by 4 if m is divisible by 4.
For our case a is 5, c is 1, and m is a power of two. */
static inline st_index_t
secondary_hash(st_index_t ind, st_table *tab, st_index_t *perterb)
{
*perterb >>= 11;
ind = (ind << 2) + ind + *perterb + 1;
return hash_bin(ind, tab);
}
/* Find an entry with HASH_VALUE and KEY in TABLE using a linear
search. Return the index of the found entry in array `entries`.
If it is not found, return UNDEFINED_ENTRY_IND. If the table was
rebuilt during the search, return REBUILT_TABLE_ENTRY_IND. */
static inline st_index_t
find_entry(st_table *tab, st_hash_t hash_value, st_data_t key)
{
int eq_p, rebuilt_p;
st_index_t i, bound;
st_table_entry *entries;
bound = tab->entries_bound;
entries = tab->entries;
for (i = tab->entries_start; i < bound; i++) {
DO_PTR_EQUAL_CHECK(tab, &entries[i], hash_value, key, eq_p, rebuilt_p);
if (EXPECT(rebuilt_p, 0))
return REBUILT_TABLE_ENTRY_IND;
if (eq_p)
return i;
}
return UNDEFINED_ENTRY_IND;
}
/* Use the quadratic probing. The method has a better data locality
but more collisions than the current approach. In average it
results in a bit slower search. */
/*#define QUADRATIC_PROBE*/
/* Return index of entry with HASH_VALUE and KEY in table TAB. If
there is no such entry, return UNDEFINED_ENTRY_IND. If the table
was rebuilt during the search, return REBUILT_TABLE_ENTRY_IND. */
static st_index_t
find_table_entry_ind(st_table *tab, st_hash_t hash_value, st_data_t key)
{
int eq_p, rebuilt_p;
st_index_t ind;
#ifdef QUADRATIC_PROBE
st_index_t d;
#else
st_index_t peterb;
#endif
st_index_t bin;
st_table_entry *entries = tab->entries;
st_assert(tab != NULL);
st_assert(tab->bins != NULL);
ind = hash_bin(hash_value, tab);
#ifdef QUADRATIC_PROBE
d = 1;
#else
peterb = hash_value;
#endif
FOUND_BIN;
for (;;) {
bin = get_bin(tab->bins, get_size_ind(tab), ind);
if (! EMPTY_OR_DELETED_BIN_P(bin)) {
DO_PTR_EQUAL_CHECK(tab, &entries[bin - ENTRY_BASE], hash_value, key, eq_p, rebuilt_p);
if (EXPECT(rebuilt_p, 0))
return REBUILT_TABLE_ENTRY_IND;
if (eq_p)
break;
} else if (EMPTY_BIN_P(bin))
return UNDEFINED_ENTRY_IND;
#ifdef QUADRATIC_PROBE
ind = hash_bin(ind + d, tab);
d++;
#else
ind = secondary_hash(ind, tab, &peterb);
#endif
COLLISION;
}
return bin;
}
/* Find and return index of table TAB bin corresponding to an entry
with HASH_VALUE and KEY. If there is no such bin, return
UNDEFINED_BIN_IND. If the table was rebuilt during the search,
return REBUILT_TABLE_BIN_IND. */
static st_index_t
find_table_bin_ind(st_table *tab, st_hash_t hash_value, st_data_t key)
{
int eq_p, rebuilt_p;
st_index_t ind;
#ifdef QUADRATIC_PROBE
st_index_t d;
#else
st_index_t peterb;
#endif
st_index_t bin;
st_table_entry *entries = tab->entries;
st_assert(tab != NULL);
st_assert(tab->bins != NULL);
ind = hash_bin(hash_value, tab);
#ifdef QUADRATIC_PROBE
d = 1;
#else
peterb = hash_value;
#endif
FOUND_BIN;
for (;;) {
bin = get_bin(tab->bins, get_size_ind(tab), ind);
if (! EMPTY_OR_DELETED_BIN_P(bin)) {
DO_PTR_EQUAL_CHECK(tab, &entries[bin - ENTRY_BASE], hash_value, key, eq_p, rebuilt_p);
if (EXPECT(rebuilt_p, 0))
return REBUILT_TABLE_BIN_IND;
if (eq_p)
break;
} else if (EMPTY_BIN_P(bin))
return UNDEFINED_BIN_IND;
#ifdef QUADRATIC_PROBE
ind = hash_bin(ind + d, tab);
d++;
#else
ind = secondary_hash(ind, tab, &peterb);
#endif
COLLISION;
}
return ind;
}
/* Find and return index of table TAB bin corresponding to an entry
with HASH_VALUE and KEY. The entry should be in the table
already. */
static st_index_t
find_table_bin_ind_direct(st_table *tab, st_hash_t hash_value, st_data_t key)