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
|
/*
* Copyright (c) 2009 Marko Kreen
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/** @file
* Simple customizable hashtable implementation.
*
* - Fixed-size hash table, open-addressed
* - Extended by linking several together
* - Resizable by copying.
* - Can be lockless in multi-reader, one-writer situation if
* mempory barrier macros are defined. This also requires that
* HashItem must not be split across cachelines.
*/
#include <usual/cxalloc.h>
#include <string.h>
#ifndef HTAB_KEY_T
/** Overridable type for key */
#define HTAB_KEY_T unsigned long
#endif
#ifndef HTAB_VAL_T
/** Overridable type for value */
#define HTAB_VAL_T void *
#endif
#ifndef HTAB_RMB
#define HTAB_RMB
#endif
#ifndef HTAB_WMB
#define HTAB_WMB
#endif
/** Typedef for key */
typedef HTAB_KEY_T htab_key_t;
/** Typedef for value */
typedef HTAB_VAL_T htab_val_t;
#ifndef HTAB_ITEM
#define HTAB_ITEM
/** HashTab slot */
struct HashItem {
htab_key_t key;
htab_val_t value;
};
#endif
/** Signature for comparision function */
typedef bool (*hash_cmp_fn)(const htab_val_t curval, const void *arg);
#ifndef HTAB_MAX_FILL
/** Max fill percentage */
#define HTAB_MAX_FILL 75
#endif
#define MASK(h) ((h)->size - 1)
#define CALC_POS(h, key) ((key) & MASK(h))
#define NEXT_POS(h, pos) (((pos) * 5 + 1) & MASK(h))
#define MAX_USED(h) ((h)->size * HTAB_MAX_FILL / 100)
/** Single HashTab segment */
struct HashTab {
struct HashTab *next;
hash_cmp_fn cmp_fn;
CxMem *ca;
unsigned size;
unsigned used;
struct HashItem tab[FLEX_ARRAY];
};
/** Initialize HashTab */
static struct HashTab *hashtab_create(unsigned size, hash_cmp_fn cmp_fn, CxMem *ca)
{
struct HashTab *h;
unsigned len = size * sizeof(struct HashItem) + offsetof(struct HashTab, tab);
h = cx_alloc0(ca, len);
if (h) {
h->size = size;
h->cmp_fn = cmp_fn;
h->ca = ca;
}
return h;
}
/** Free HashTab */
static void hashtab_destroy(struct HashTab *h)
{
struct HashTab *tmp;
while (h) {
tmp = h->next;
cx_free(h->ca, h);
h = tmp;
}
}
/** Element lookup, optionally inserting new slot */
static htab_val_t *hashtab_lookup(struct HashTab *h, htab_key_t key, bool do_insert, const void *arg)
{
unsigned pos;
struct HashItem *i;
loop:
/* find key, starting from pos */
pos = CALC_POS(h, key);
while (h->tab[pos].value) {
i = &h->tab[pos];
HTAB_RMB;
if (i->key == key) {
if (arg && h->cmp_fn(i->value, arg))
return &i->value;
}
pos = NEXT_POS(h, pos);
}
/* not found in this one, check chained tables */
if (h->next) {
h = h->next;
goto loop;
}
/* just lookup? */
if (!do_insert)
return NULL;
/* insert */
if (h->used >= MAX_USED(h)) {
struct HashTab *tmp;
tmp = hashtab_create(h->size, h->cmp_fn, h->ca);
if (!tmp)
return NULL;
h->next = tmp;
h = tmp;
pos = CALC_POS(h, key);
}
h->used++;
h->tab[pos].key = key;
HTAB_WMB;
return &h->tab[pos].value;
}
/* if proper pos is between src and dst, cannot move */
static bool _hashtab_slot_can_move(struct HashTab *h, unsigned dstpos, unsigned srcpos)
{
htab_key_t key = h->tab[srcpos].key;
unsigned pos, kpos = CALC_POS(h, key);
if (kpos == srcpos)
return false;
if (kpos == dstpos)
return true;
for (pos = NEXT_POS(h, dstpos); pos != srcpos; pos = NEXT_POS(h, pos)) {
if (pos == kpos)
return false;
}
return true;
}
/** Delete an element */
static void hashtab_delete(struct HashTab *h, htab_key_t key, void *arg)
{
htab_val_t *vptr;
struct HashItem *hd;
unsigned pos, dstpos;
/* find it */
vptr = hashtab_lookup(h, key, false, arg);
if (!vptr)
return;
/* find right tab */
hd = container_of(vptr, struct HashItem, value);
while (h && ((hd < h->tab) || (hd >= h->tab + h->size)))
h = h->next;
/* calculate index */
dstpos = hd - h->tab;
loop:
/* move slot */
for (pos = NEXT_POS(h, dstpos); h->tab[pos].value; pos = NEXT_POS(h, pos)) {
if (_hashtab_slot_can_move(h, dstpos, pos)) {
h->tab[dstpos].key = h->tab[pos].key;
h->tab[dstpos].value = h->tab[pos].value;
dstpos = pos;
goto loop;
}
}
h->tab[dstpos].value = 0;
HTAB_WMB;
h->tab[dstpos].key = 0;
h->used--;
}
/** Count elements and fragments */
static void hashtab_stats(struct HashTab *h, unsigned *nitem_p, unsigned *ntab_p)
{
unsigned n = 0, l = 0;
while (h) {
l++;
n += h->used;
h = h->next;
}
*nitem_p = n;
*ntab_p = l;
}
/** Copy elements to new hashtab, perhaps with different size */
static struct HashTab *hashtab_copy(struct HashTab *h_old, unsigned newsize)
{
struct HashTab *h_new;
unsigned i;
h_new = hashtab_create(newsize, h_old->cmp_fn, h_old->ca);
for (; h_old; h_old = h_old->next) {
for (i = 0; i < h_old->size; i++) {
struct HashItem *s = &h_old->tab[i];
htab_val_t *new_pos;
if (s->value) {
new_pos = hashtab_lookup(h_new, s->key, true, NULL);
if (!new_pos)
goto err;
*new_pos = s->value;
}
}
}
return h_new;
err:
hashtab_destroy(h_new);
return NULL;
}
/* example, and avoid "unused" warnings */
static inline void _hashtab_example(void)
{
unsigned nitem, nlink;
struct HashTab *h, *h2;
h = hashtab_create(1024, NULL, NULL);
hashtab_lookup(h, 123, true, NULL);
hashtab_stats(h, &nitem, &nlink);
h2 = hashtab_copy(h, 2048);
hashtab_delete(h, 123, NULL);
hashtab_destroy(h);
hashtab_destroy(h2);
}
|