30 |
#include <hurd/ihash.h> |
#include <hurd/ihash.h> |
31 |
|
|
32 |
|
|
33 |
/* The odd prime numbers greater than twice the last and less than |
/* The prime numbers of the form 4 * i + 3 for some i, all greater |
34 |
2^40 (nobody needs more than 640 GB of memory). */ |
than twice the previous one and smaller than 2^40 (for now). */ |
35 |
static const uint64_t ihash_sizes[] = |
static const uint64_t ihash_sizes[] = |
36 |
{ |
{ |
37 |
3, |
3, |
38 |
7, |
7, |
39 |
17, |
19, |
40 |
37, |
43, |
41 |
79, |
103, |
42 |
163, |
211, |
43 |
331, |
431, |
44 |
673, |
863, |
45 |
1361, |
1747, |
46 |
2729, |
3499, |
47 |
5471, |
7019, |
48 |
10949, |
14051, |
49 |
21911, |
28111, |
50 |
43853, |
56239, |
51 |
87719, |
112507, |
52 |
175447, |
225023, |
53 |
350899, |
450067, |
54 |
701819, |
900139, |
55 |
1403641, |
1800311, |
56 |
2807303, |
3600659, |
57 |
5614657, |
7201351, |
58 |
11229331, |
14402743, |
59 |
22458671, |
28805519, |
60 |
44917381, |
57611039, |
61 |
89834777, |
115222091, |
62 |
179669557, |
230444239, |
63 |
359339171, |
460888499, |
64 |
718678369, |
921777067, |
65 |
1437356741, |
1843554151, |
66 |
UINT64_C (2874713497), |
UINT64_C (3687108307), |
67 |
UINT64_C (5749427029), |
UINT64_C (7374216631), |
68 |
UINT64_C (11498854069), |
UINT64_C (14748433279), |
69 |
UINT64_C (22997708177), |
UINT64_C (29496866579), |
70 |
UINT64_C (45995416409), |
UINT64_C (58993733159), |
71 |
UINT64_C (91990832831), |
UINT64_C (117987466379), |
72 |
UINT64_C (183981665689), |
UINT64_C (235974932759), |
73 |
UINT64_C (367963331389), |
UINT64_C (471949865531), |
74 |
UINT64_C (735926662813) |
UINT64_C (943899731087) |
75 |
}; |
}; |
76 |
|
|
77 |
|
|
79 |
/ sizeof ihash_sizes[0]); |
/ sizeof ihash_sizes[0]); |
80 |
|
|
81 |
|
|
|
/* Return an initial index in the hash table HT for the key KEY, to |
|
|
search for an entry. */ |
|
|
#define HASH(ht, key) ((key) % (ht)->size) |
|
|
|
|
|
|
|
|
/* Return the next possible index in the hash table HT for the key |
|
|
KEY, given the previous one. */ |
|
|
#define REHASH(ht, key, idx) (((idx) + (key)) % (ht)->size) |
|
|
|
|
|
|
|
82 |
/* Return 1 if the slot with the index IDX in the hash table HT is |
/* Return 1 if the slot with the index IDX in the hash table HT is |
83 |
empty, and 0 otherwise. */ |
empty, and 0 otherwise. */ |
84 |
static inline int |
static inline int |
85 |
index_empty (hurd_ihash_t ht, unsigned int idx) |
index_empty (hurd_ihash_t ht, unsigned int idx) |
86 |
{ |
{ |
87 |
return ht->table[idx] == _HURD_IHASH_EMPTY |
return ht->items[idx].value == _HURD_IHASH_EMPTY |
88 |
|| ht->table[idx] == _HURD_IHASH_DELETED; |
|| ht->items[idx].value == _HURD_IHASH_DELETED; |
89 |
} |
} |
90 |
|
|
91 |
|
|
94 |
static inline int |
static inline int |
95 |
index_valid (hurd_ihash_t ht, unsigned int idx, hurd_ihash_key_t key) |
index_valid (hurd_ihash_t ht, unsigned int idx, hurd_ihash_key_t key) |
96 |
{ |
{ |
97 |
return !index_empty (ht, idx) && ht->keys[idx] == key; |
return !index_empty (ht, idx) && ht->items[idx].key == key; |
98 |
} |
} |
99 |
|
|
100 |
|
|
105 |
find_index (hurd_ihash_t ht, hurd_ihash_key_t key) |
find_index (hurd_ihash_t ht, hurd_ihash_key_t key) |
106 |
{ |
{ |
107 |
unsigned int idx; |
unsigned int idx; |
108 |
unsigned int first_idx; |
unsigned int i; |
109 |
|
unsigned int up_idx; |
110 |
first_idx = HASH (ht, key); |
unsigned int down_idx; |
111 |
idx = first_idx; |
|
112 |
|
idx = key % ht->size; |
113 |
|
|
114 |
|
if (ht->items[idx].value == _HURD_IHASH_EMPTY || ht->items[idx].key == key) |
115 |
|
return idx; |
116 |
|
|
117 |
|
/* Instead of calculating idx + 1, idx + 4, idx + 9, ..., idx + i^2, |
118 |
|
we add 1, 3, 5, 7, etc to the previous index. We do this in both |
119 |
|
directions separately. */ |
120 |
|
i = 1; |
121 |
|
up_idx = idx; |
122 |
|
down_idx = idx; |
123 |
|
|
124 |
while (ht->table[idx] != _HURD_IHASH_EMPTY && ht->keys[idx] != key) |
do |
125 |
{ |
{ |
126 |
idx = REHASH (ht, key, idx); |
up_idx = (up_idx + i) % ht->size; |
127 |
if (idx == first_idx) |
if (ht->items[up_idx].value == _HURD_IHASH_EMPTY |
128 |
break; |
|| ht->items[up_idx].key == key) |
129 |
|
return up_idx; |
130 |
|
|
131 |
|
if (down_idx < i) |
132 |
|
down_idx += ht->size; |
133 |
|
down_idx = (down_idx - i) % ht->size; |
134 |
|
if (ht->items[down_idx].value == _HURD_IHASH_EMPTY |
135 |
|
|| ht->items[down_idx].key == key) |
136 |
|
return down_idx; |
137 |
|
|
138 |
|
/* After (ht->size - 1) / 2 iterations, this will be 0. */ |
139 |
|
i = (i + 2) % ht->size; |
140 |
} |
} |
141 |
|
while (i); |
142 |
|
|
143 |
|
/* If we end up here, the item could not be found. Return any |
144 |
|
invalid index. */ |
145 |
return idx; |
return idx; |
146 |
} |
} |
147 |
|
|
148 |
|
|
149 |
|
/* Remove the entry pointed to by the location pointer LOCP from the |
150 |
|
hashtable HT. LOCP is the location pointer of which the address |
151 |
|
was provided to hurd_ihash_add(). */ |
152 |
|
static inline void |
153 |
|
locp_remove (hurd_ihash_t ht, hurd_ihash_locp_t locp) |
154 |
|
{ |
155 |
|
if (ht->cleanup) |
156 |
|
(*ht->cleanup) (*locp, ht->cleanup_data); |
157 |
|
*locp = _HURD_IHASH_DELETED; |
158 |
|
ht->nr_items--; |
159 |
|
} |
160 |
|
|
161 |
|
|
162 |
/* Construction and destruction of hash tables. */ |
/* Construction and destruction of hash tables. */ |
163 |
|
|
164 |
/* Initialize the hash table at address HT. */ |
/* Initialize the hash table at address HT. */ |
165 |
void |
void |
166 |
hurd_ihash_init (hurd_ihash_t ht) |
hurd_ihash_init (hurd_ihash_t ht, off_t locp_offs) |
167 |
{ |
{ |
168 |
|
ht->nr_items = 0; |
169 |
ht->size = 0; |
ht->size = 0; |
170 |
|
ht->locp_offset = locp_offs; |
171 |
|
ht->max_load = HURD_IHASH_MAX_LOAD_DEFAULT; |
172 |
ht->cleanup = 0; |
ht->cleanup = 0; |
173 |
} |
} |
174 |
|
|
189 |
} |
} |
190 |
|
|
191 |
if (ht->size > 0) |
if (ht->size > 0) |
192 |
{ |
free (ht->items); |
|
free (ht->table); |
|
|
free (ht->keys); |
|
|
free (ht->locps); |
|
|
} |
|
193 |
} |
} |
194 |
|
|
195 |
|
|
196 |
/* Create a hash table, initialize it and return it in HT. If a |
/* Create a hash table, initialize it and return it in HT. If a |
197 |
memory allocation error occurs, ENOMEM is returned, otherwise 0. */ |
memory allocation error occurs, ENOMEM is returned, otherwise 0. */ |
198 |
error_t |
error_t |
199 |
hurd_ihash_create (hurd_ihash_t *ht) |
hurd_ihash_create (hurd_ihash_t *ht, off_t locp_offs) |
200 |
{ |
{ |
201 |
*ht = malloc (sizeof (struct hurd_ihash)); |
*ht = malloc (sizeof (struct hurd_ihash)); |
202 |
if (*ht == NULL) |
if (*ht == NULL) |
203 |
return ENOMEM; |
return ENOMEM; |
204 |
|
|
205 |
hurd_ihash_init (*ht); |
hurd_ihash_init (*ht, locp_offs); |
206 |
|
|
207 |
return 0; |
return 0; |
208 |
} |
} |
230 |
} |
} |
231 |
|
|
232 |
|
|
233 |
|
/* Set the maximum load factor in percent to MAX_LOAD, which |
234 |
|
should be between 1 and 100. The default is |
235 |
|
HURD_IHASH_MAX_LOAD_DEFAULT. New elements are only added to the |
236 |
|
hash table while the number of hashed elements is that much percent |
237 |
|
of the total size of the hash table. If more elements are added, |
238 |
|
the hash table is first expanded and reorganized. A MAX_LOAD of |
239 |
|
100 will always fill the whole table before enlarging it, but note |
240 |
|
that this will increase the cost of operations significantly when |
241 |
|
the table is almost full. |
242 |
|
|
243 |
|
If the value is set to a smaller value than the current load |
244 |
|
factor, the next reorganization will happen when a new item is |
245 |
|
added to the hash table. */ |
246 |
|
void |
247 |
|
hurd_ihash_set_max_load (hurd_ihash_t ht, unsigned int max_load) |
248 |
|
{ |
249 |
|
ht->max_load = max_load; |
250 |
|
} |
251 |
|
|
252 |
|
|
253 |
/* Helper function for hurd_ihash_add. Return 1 if the item was |
/* Helper function for hurd_ihash_add. Return 1 if the item was |
254 |
added, and 0 if it could not be added because no empty slot was |
added, and 0 if it could not be added because no empty slot was |
255 |
found. The arguments are identical to hurd_ihash_add. */ |
found. The arguments are identical to hurd_ihash_add. |
256 |
|
|
257 |
|
We are using open address hashing. As the hash function we use the |
258 |
|
division method with quadratic probe. This is guaranteed to try |
259 |
|
all slots in the hash table if the prime number is 3 mod 4. */ |
260 |
static inline int |
static inline int |
261 |
add_one (hurd_ihash_t ht, hurd_ihash_key_t key, |
add_one (hurd_ihash_t ht, hurd_ihash_key_t key, hurd_ihash_value_t value) |
|
hurd_ihash_value_t item, hurd_ihash_locp_t *locp) |
|
262 |
{ |
{ |
263 |
unsigned int idx; |
unsigned int idx; |
|
unsigned int first_idx; |
|
264 |
unsigned int first_free; |
unsigned int first_free; |
265 |
|
|
266 |
first_idx = HASH (ht, key); |
idx = key % ht->size; |
267 |
idx = first_idx; |
first_free = idx; |
|
first_free = first_idx; |
|
|
|
|
|
/* Search for for an empty or deleted space. Even if a deleted |
|
|
space is found, keep going until we know if the same key |
|
|
already exists. */ |
|
|
while (ht->table[idx] != _HURD_IHASH_EMPTY |
|
|
&& ht->keys[idx] != key) |
|
|
{ |
|
|
if (ht->table[idx] == _HURD_IHASH_DELETED) |
|
|
first_free = idx; |
|
|
|
|
|
idx = REHASH (ht, key, idx); |
|
|
if (idx == first_idx) |
|
|
break; |
|
|
} |
|
268 |
|
|
269 |
if (ht->keys[idx] == key) |
if (ht->items[idx].value != _HURD_IHASH_EMPTY && ht->items[idx].key != key) |
270 |
{ |
{ |
271 |
if (ht->cleanup) |
/* Instead of calculating idx + 1, idx + 4, idx + 9, ..., idx + |
272 |
ht->cleanup (ht->table[idx], ht->cleanup_data); |
i^2, we add 1, 3, 5, 7, ... 2 * i - 1 to the previous index. |
273 |
ht->table[idx] = _HURD_IHASH_DELETED; |
We do this in both directions separately. */ |
274 |
|
unsigned int i = 1; |
275 |
|
unsigned int up_idx = idx; |
276 |
|
unsigned int down_idx = idx; |
277 |
|
|
278 |
|
do |
279 |
|
{ |
280 |
|
up_idx = (up_idx + i) % ht->size; |
281 |
|
if (ht->items[up_idx].value == _HURD_IHASH_EMPTY |
282 |
|
|| ht->items[up_idx].key == key) |
283 |
|
{ |
284 |
|
idx = up_idx; |
285 |
|
break; |
286 |
|
} |
287 |
|
if (first_free == idx |
288 |
|
&& ht->items[up_idx].value == _HURD_IHASH_DELETED) |
289 |
|
first_free = up_idx; |
290 |
|
|
291 |
|
if (down_idx < i) |
292 |
|
down_idx += ht->size; |
293 |
|
down_idx = (down_idx - i) % ht->size; |
294 |
|
if (down_idx < 0) |
295 |
|
down_idx += ht->size; |
296 |
|
else |
297 |
|
down_idx %= ht->size; |
298 |
|
if (ht->items[down_idx].value == _HURD_IHASH_EMPTY |
299 |
|
|| ht->items[down_idx].key == key) |
300 |
|
{ |
301 |
|
idx = down_idx; |
302 |
|
break; |
303 |
|
} |
304 |
|
if (first_free == idx |
305 |
|
&& ht->items[down_idx].value == _HURD_IHASH_DELETED) |
306 |
|
first_free = down_idx; |
307 |
|
|
308 |
|
/* After (ht->size - 1) / 2 iterations, this will be 0. */ |
309 |
|
i = (i + 2) % ht->size; |
310 |
|
} |
311 |
|
while (i); |
312 |
} |
} |
313 |
|
|
314 |
|
/* Remove the old entry for this key if necessary. */ |
315 |
|
if (index_valid (ht, idx, key)) |
316 |
|
locp_remove (ht, &ht->items[idx].value); |
317 |
|
|
318 |
/* If we have not found an empty slot, maybe the last one we |
/* If we have not found an empty slot, maybe the last one we |
319 |
looked at was empty (or just got deleted). */ |
looked at was empty (or just got deleted). */ |
320 |
if (!index_empty (ht, first_free)) |
if (!index_empty (ht, first_free)) |
322 |
|
|
323 |
if (index_empty (ht, first_free)) |
if (index_empty (ht, first_free)) |
324 |
{ |
{ |
325 |
ht->table[first_free] = item; |
ht->nr_items++; |
326 |
ht->keys[first_free] = key; |
ht->items[first_free].value = value; |
327 |
ht->locps[first_free] = locp; |
ht->items[first_free].key = key; |
328 |
|
|
329 |
if (locp) |
if (ht->locp_offset != HURD_IHASH_NO_LOCP) |
330 |
*locp = &ht->table[first_free]; |
*((hurd_ihash_locp_t) (((char *) value) + ht->locp_offset)) |
331 |
|
= &ht->items[first_free].value; |
332 |
|
|
333 |
return 1; |
return 1; |
334 |
} |
} |
335 |
|
|
337 |
} |
} |
338 |
|
|
339 |
|
|
340 |
/* Add ITEM to the hash table HT under the key KEY. LOCP is the |
/* Add ITEM to the hash table HT under the key KEY. If there already |
341 |
address of a location pointer in ITEM; If non-NULL, it will be |
is an item under this key, call the cleanup function (if any) for |
342 |
filled with a pointer that may be used as an argument to |
it before overriding the value. If a memory allocation error |
343 |
hurd_ihash_locp_remove(). (The variable pointed to by LOCP may be |
occurs, ENOMEM is returned, otherwise 0. */ |
|
written to subsequently between this call and when the element is |
|
|
deleted). If there already is an item under this key, call the |
|
|
cleanup function (if any) for it before overriding the value. If a |
|
|
memory allocation error occurs, ENOMEM is returned, otherwise 0. */ |
|
344 |
error_t |
error_t |
345 |
hurd_ihash_add (hurd_ihash_t ht, hurd_ihash_key_t key, |
hurd_ihash_add (hurd_ihash_t ht, hurd_ihash_key_t key, hurd_ihash_value_t item) |
|
hurd_ihash_value_t item, hurd_ihash_locp_t *locp) |
|
346 |
{ |
{ |
347 |
struct hurd_ihash old_ht = *ht; |
struct hurd_ihash old_ht = *ht; |
348 |
int was_added; |
int was_added; |
349 |
int i; |
int i; |
350 |
|
|
351 |
if (ht->size && add_one (ht, key, item, locp)) |
if (ht->size) |
352 |
return 0; |
{ |
353 |
|
/* Only fill the hash table up to its maximum load factor. */ |
354 |
|
if (ht->nr_items * 100 / ht->size <= ht->max_load) |
355 |
|
if (add_one (ht, key, item)) |
356 |
|
return 0; |
357 |
|
} |
358 |
|
|
359 |
/* The hash table is too small, and we have to increase it. */ |
/* The hash table is too small, and we have to increase it. */ |
360 |
for (i = 0; i < ihash_nsizes; i++) |
for (i = 0; i < ihash_nsizes; i++) |
361 |
if (ihash_sizes[i] > old_ht.size) |
if (ihash_sizes[i] > old_ht.size) |
362 |
break; |
break; |
363 |
if (i == ihash_nsizes |
if (i == ihash_nsizes |
364 |
|| ihash_sizes[i] > SIZE_MAX / sizeof (hurd_ihash_value_t) |
|| ihash_sizes[i] > SIZE_MAX / sizeof (struct _hurd_ihash_item)) |
|
|| ihash_sizes[i] > SIZE_MAX / sizeof (hurd_ihash_key_t) |
|
|
|| ihash_sizes[i] > SIZE_MAX / sizeof (hurd_ihash_locp_t *)) |
|
365 |
return ENOMEM; /* Surely will be true momentarily. */ |
return ENOMEM; /* Surely will be true momentarily. */ |
366 |
|
|
367 |
|
ht->nr_items = 0; |
368 |
ht->size = ihash_sizes[i]; |
ht->size = ihash_sizes[i]; |
369 |
ht->table = malloc (ht->size * sizeof (hurd_ihash_value_t)); |
/* calloc() will initialize all values to _HURD_IHASH_EMPTY implicitely. */ |
370 |
ht->keys = malloc (ht->size * sizeof (hurd_ihash_key_t)); |
ht->items = calloc (ht->size, sizeof (struct _hurd_ihash_item)); |
|
ht->locps = malloc (ht->size * sizeof (hurd_ihash_locp_t *)); |
|
371 |
|
|
372 |
if (ht->table == NULL || ht->locps == NULL || ht->keys == NULL) |
if (ht->items == NULL) |
373 |
{ |
{ |
374 |
if (ht->table) |
if (ht->items) |
375 |
free (ht->table); |
free(ht->items); |
|
if (ht->keys) |
|
|
free(ht->keys); |
|
|
if (ht->locps) |
|
|
free (ht->locps); |
|
376 |
|
|
377 |
*ht = old_ht; |
*ht = old_ht; |
378 |
return ENOMEM; |
return ENOMEM; |
379 |
} |
} |
380 |
|
|
|
for (i = 0; i < ht->size; i++) |
|
|
ht->table[i] = _HURD_IHASH_EMPTY; |
|
|
|
|
381 |
/* We have to rehash the old entries. */ |
/* We have to rehash the old entries. */ |
382 |
for (i = 0; i < old_ht.size; i++) |
for (i = 0; i < old_ht.size; i++) |
383 |
if (!index_empty (&old_ht, i)) |
if (!index_empty (&old_ht, i)) |
384 |
{ |
{ |
385 |
was_added = add_one (ht, old_ht.keys[i], old_ht.table[i], |
was_added = add_one (ht, old_ht.items[i].key, old_ht.items[i].value); |
|
old_ht.locps[i]); |
|
386 |
assert (was_added); |
assert (was_added); |
387 |
} |
} |
388 |
|
|
389 |
/* Finally add the new element! */ |
/* Finally add the new element! */ |
390 |
was_added = add_one (ht, key, item, locp); |
was_added = add_one (ht, key, item); |
391 |
assert (was_added); |
assert (was_added); |
392 |
|
|
393 |
if (old_ht.size > 0) |
if (old_ht.size > 0) |
394 |
{ |
free (old_ht.items); |
|
free (old_ht.table); |
|
|
free (old_ht.keys); |
|
|
free (old_ht.locps); |
|
|
} |
|
395 |
|
|
396 |
return 0; |
return 0; |
397 |
} |
} |
407 |
else |
else |
408 |
{ |
{ |
409 |
int idx = find_index (ht, key); |
int idx = find_index (ht, key); |
410 |
return index_valid (ht, idx, key) ? ht->table[idx] : NULL; |
return index_valid (ht, idx, key) ? ht->items[idx].value : NULL; |
411 |
} |
} |
412 |
} |
} |
413 |
|
|
421 |
|
|
422 |
if (index_valid (ht, idx, key)) |
if (index_valid (ht, idx, key)) |
423 |
{ |
{ |
424 |
hurd_ihash_locp_remove (ht, &ht->table[idx]); |
locp_remove (ht, &ht->items[idx].value); |
425 |
return 1; |
return 1; |
426 |
} |
} |
427 |
else |
else |
432 |
/* Remove the entry pointed to by the location pointer LOCP from the |
/* Remove the entry pointed to by the location pointer LOCP from the |
433 |
hashtable HT. LOCP is the location pointer of which the address |
hashtable HT. LOCP is the location pointer of which the address |
434 |
was provided to hurd_ihash_add(). This call is faster than |
was provided to hurd_ihash_add(). This call is faster than |
435 |
hurd_ihash_remove(). HT can be NULL, in which case the call still |
hurd_ihash_remove(). */ |
|
succeeds, but the cleanup function (if any) will not be invoked in |
|
|
this case. */ |
|
436 |
void |
void |
437 |
hurd_ihash_locp_remove (hurd_ihash_t ht, hurd_ihash_locp_t locp) |
hurd_ihash_locp_remove (hurd_ihash_t ht, hurd_ihash_locp_t locp) |
438 |
{ |
{ |
439 |
if (ht && ht->cleanup) |
locp_remove (ht, locp); |
|
(*ht->cleanup) (*locp, ht->cleanup_data); |
|
|
*locp = _HURD_IHASH_DELETED; |
|
440 |
} |
} |