/src/nspr/lib/ds/plhash.c
Line | Count | Source |
1 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
4 | | |
5 | | /* |
6 | | * PL hash table package. |
7 | | */ |
8 | | #include "plhash.h" |
9 | | #include "prbit.h" |
10 | | #include "prlog.h" |
11 | | #include "prmem.h" |
12 | | #include "prtypes.h" |
13 | | #include <stdlib.h> |
14 | | #include <string.h> |
15 | | |
16 | | /* Compute the number of buckets in ht */ |
17 | 792k | #define NBUCKETS(ht) (1 << (PL_HASH_BITS - (ht)->shift)) |
18 | | |
19 | | /* The smallest table has 16 buckets */ |
20 | 392k | #define MINBUCKETSLOG2 4 |
21 | 392k | #define MINBUCKETS (1 << MINBUCKETSLOG2) |
22 | | |
23 | | /* Compute the maximum entries given n buckets that we will tolerate, ~90% */ |
24 | 399k | #define OVERLOADED(n) ((n) - ((n) >> 3)) |
25 | | |
26 | | /* Compute the number of entries below which we shrink the table by half */ |
27 | 392k | #define UNDERLOADED(n) (((n) > MINBUCKETS) ? ((n) >> 2) : 0) |
28 | | |
29 | | /* |
30 | | ** Stubs for default hash allocator ops. |
31 | | */ |
32 | 468 | static void* PR_CALLBACK DefaultAllocTable(void* pool, PRSize size) { |
33 | 468 | return PR_MALLOC(size); |
34 | 468 | } |
35 | | |
36 | 428 | static void PR_CALLBACK DefaultFreeTable(void* pool, void* item) { |
37 | 428 | PR_Free(item); |
38 | 428 | } |
39 | | |
40 | 213k | static PLHashEntry* PR_CALLBACK DefaultAllocEntry(void* pool, const void* key) { |
41 | 213k | return PR_NEW(PLHashEntry); |
42 | 213k | } |
43 | | |
44 | | static void PR_CALLBACK DefaultFreeEntry(void* pool, PLHashEntry* he, |
45 | 213k | PRUintn flag) { |
46 | 213k | if (flag == HT_FREE_ENTRY) { |
47 | 211k | PR_Free(he); |
48 | 211k | } |
49 | 213k | } |
50 | | |
51 | | static PLHashAllocOps defaultHashAllocOps = { |
52 | | DefaultAllocTable, DefaultFreeTable, DefaultAllocEntry, DefaultFreeEntry}; |
53 | | |
54 | | PR_IMPLEMENT(PLHashTable*) |
55 | | PL_NewHashTable(PRUint32 n, PLHashFunction keyHash, PLHashComparator keyCompare, |
56 | | PLHashComparator valueCompare, const PLHashAllocOps* allocOps, |
57 | 274 | void* allocPriv) { |
58 | 274 | PLHashTable* ht; |
59 | 274 | PRSize nb; |
60 | | |
61 | 274 | if (n <= MINBUCKETS) { |
62 | 162 | n = MINBUCKETSLOG2; |
63 | 162 | } else { |
64 | 112 | n = PR_CeilingLog2(n); |
65 | 112 | if ((PRInt32)n < 0) { |
66 | 0 | return 0; |
67 | 0 | } |
68 | 112 | } |
69 | | |
70 | 274 | if (!allocOps) { |
71 | 146 | allocOps = &defaultHashAllocOps; |
72 | 146 | } |
73 | | |
74 | 274 | ht = (PLHashTable*)((*allocOps->allocTable)(allocPriv, sizeof *ht)); |
75 | 274 | if (!ht) { |
76 | 0 | return 0; |
77 | 0 | } |
78 | 274 | memset(ht, 0, sizeof *ht); |
79 | 274 | ht->shift = PL_HASH_BITS - n; |
80 | 274 | n = 1 << n; |
81 | 274 | nb = n * sizeof(PLHashEntry*); |
82 | 274 | ht->buckets = (PLHashEntry**)((*allocOps->allocTable)(allocPriv, nb)); |
83 | 274 | if (!ht->buckets) { |
84 | 0 | (*allocOps->freeTable)(allocPriv, ht); |
85 | 0 | return 0; |
86 | 0 | } |
87 | 274 | memset(ht->buckets, 0, nb); |
88 | | |
89 | 274 | ht->keyHash = keyHash; |
90 | 274 | ht->keyCompare = keyCompare; |
91 | 274 | ht->valueCompare = valueCompare; |
92 | 274 | ht->allocOps = allocOps; |
93 | 274 | ht->allocPriv = allocPriv; |
94 | 274 | return ht; |
95 | 274 | } |
96 | | |
97 | | PR_IMPLEMENT(void) |
98 | 238 | PL_HashTableDestroy(PLHashTable* ht) { |
99 | 238 | PRUint32 i, n; |
100 | 238 | PLHashEntry *he, *next; |
101 | 238 | const PLHashAllocOps* allocOps = ht->allocOps; |
102 | 238 | void* allocPriv = ht->allocPriv; |
103 | | |
104 | 238 | n = NBUCKETS(ht); |
105 | 14.7k | for (i = 0; i < n; i++) { |
106 | 20.6k | for (he = ht->buckets[i]; he; he = next) { |
107 | 6.05k | next = he->next; |
108 | 6.05k | (*allocOps->freeEntry)(allocPriv, he, HT_FREE_ENTRY); |
109 | 6.05k | } |
110 | 14.5k | } |
111 | 238 | #ifdef DEBUG |
112 | 238 | memset(ht->buckets, 0xDB, n * sizeof ht->buckets[0]); |
113 | 238 | #endif |
114 | 238 | (*allocOps->freeTable)(allocPriv, ht->buckets); |
115 | 238 | #ifdef DEBUG |
116 | 238 | memset(ht, 0xDB, sizeof *ht); |
117 | 238 | #endif |
118 | 238 | (*allocOps->freeTable)(allocPriv, ht); |
119 | 238 | } |
120 | | |
121 | | /* |
122 | | ** Multiplicative hash, from Knuth 6.4. |
123 | | */ |
124 | 38.8M | #define GOLDEN_RATIO 0x9E3779B9U /* 2/(1+sqrt(5))*(2^32) */ |
125 | | |
126 | | PR_IMPLEMENT(PLHashEntry**) |
127 | 2.23M | PL_HashTableRawLookup(PLHashTable* ht, PLHashNumber keyHash, const void* key) { |
128 | 2.23M | PLHashEntry *he, **hep, **hep0; |
129 | 2.23M | PLHashNumber h; |
130 | | |
131 | | #ifdef HASHMETER |
132 | | ht->nlookups++; |
133 | | #endif |
134 | 2.23M | h = keyHash * GOLDEN_RATIO; |
135 | 2.23M | h >>= ht->shift; |
136 | 2.23M | hep = hep0 = &ht->buckets[h]; |
137 | 2.44M | while ((he = *hep) != 0) { |
138 | 1.35M | if (he->keyHash == keyHash && (*ht->keyCompare)(key, he->key)) { |
139 | | /* Move to front of chain if not already there */ |
140 | 1.14M | if (hep != hep0) { |
141 | 54.2k | *hep = he->next; |
142 | 54.2k | he->next = *hep0; |
143 | 54.2k | *hep0 = he; |
144 | 54.2k | } |
145 | 1.14M | return hep0; |
146 | 1.14M | } |
147 | 206k | hep = &he->next; |
148 | | #ifdef HASHMETER |
149 | | ht->nsteps++; |
150 | | #endif |
151 | 206k | } |
152 | 1.08M | return hep; |
153 | 2.23M | } |
154 | | |
155 | | /* |
156 | | ** Same as PL_HashTableRawLookup but doesn't reorder the hash entries. |
157 | | */ |
158 | | PR_IMPLEMENT(PLHashEntry**) |
159 | | PL_HashTableRawLookupConst(PLHashTable* ht, PLHashNumber keyHash, |
160 | 36.5M | const void* key) { |
161 | 36.5M | PLHashEntry *he, **hep; |
162 | 36.5M | PLHashNumber h; |
163 | | |
164 | | #ifdef HASHMETER |
165 | | ht->nlookups++; |
166 | | #endif |
167 | 36.5M | h = keyHash * GOLDEN_RATIO; |
168 | 36.5M | h >>= ht->shift; |
169 | 36.5M | hep = &ht->buckets[h]; |
170 | 39.0M | while ((he = *hep) != 0) { |
171 | 37.9M | if (he->keyHash == keyHash && (*ht->keyCompare)(key, he->key)) { |
172 | 35.5M | break; |
173 | 35.5M | } |
174 | 2.43M | hep = &he->next; |
175 | | #ifdef HASHMETER |
176 | | ht->nsteps++; |
177 | | #endif |
178 | 2.43M | } |
179 | 36.5M | return hep; |
180 | 36.5M | } |
181 | | |
182 | | PR_IMPLEMENT(PLHashEntry*) |
183 | | PL_HashTableRawAdd(PLHashTable* ht, PLHashEntry** hep, PLHashNumber keyHash, |
184 | 399k | const void* key, void* value) { |
185 | 399k | PRUint32 i, n; |
186 | 399k | PLHashEntry *he, *next, **oldbuckets; |
187 | 399k | PRSize nb; |
188 | | |
189 | | /* Grow the table if it is overloaded */ |
190 | 399k | n = NBUCKETS(ht); |
191 | 399k | if (ht->nentries >= OVERLOADED(n)) { |
192 | 142 | oldbuckets = ht->buckets; |
193 | 142 | nb = 2 * n * sizeof(PLHashEntry*); |
194 | 142 | ht->buckets = |
195 | 142 | (PLHashEntry**)((*ht->allocOps->allocTable)(ht->allocPriv, nb)); |
196 | 142 | if (!ht->buckets) { |
197 | 0 | ht->buckets = oldbuckets; |
198 | 0 | return 0; |
199 | 0 | } |
200 | 142 | memset(ht->buckets, 0, nb); |
201 | | #ifdef HASHMETER |
202 | | ht->ngrows++; |
203 | | #endif |
204 | 142 | ht->shift--; |
205 | | |
206 | 10.5k | for (i = 0; i < n; i++) { |
207 | 19.5k | for (he = oldbuckets[i]; he; he = next) { |
208 | 9.12k | next = he->next; |
209 | 9.12k | hep = PL_HashTableRawLookup(ht, he->keyHash, he->key); |
210 | 9.12k | PR_ASSERT(*hep == 0); |
211 | 9.12k | he->next = 0; |
212 | 9.12k | *hep = he; |
213 | 9.12k | } |
214 | 10.4k | } |
215 | 142 | #ifdef DEBUG |
216 | 142 | memset(oldbuckets, 0xDB, n * sizeof oldbuckets[0]); |
217 | 142 | #endif |
218 | 142 | (*ht->allocOps->freeTable)(ht->allocPriv, oldbuckets); |
219 | 142 | hep = PL_HashTableRawLookup(ht, keyHash, key); |
220 | 142 | } |
221 | | |
222 | | /* Make a new key value entry */ |
223 | 399k | he = (*ht->allocOps->allocEntry)(ht->allocPriv, key); |
224 | 399k | if (!he) { |
225 | 0 | return 0; |
226 | 0 | } |
227 | 399k | he->keyHash = keyHash; |
228 | 399k | he->key = key; |
229 | 399k | he->value = value; |
230 | 399k | he->next = *hep; |
231 | 399k | *hep = he; |
232 | 399k | ht->nentries++; |
233 | 399k | return he; |
234 | 399k | } |
235 | | |
236 | | PR_IMPLEMENT(PLHashEntry*) |
237 | 400k | PL_HashTableAdd(PLHashTable* ht, const void* key, void* value) { |
238 | 400k | PLHashNumber keyHash; |
239 | 400k | PLHashEntry *he, **hep; |
240 | | |
241 | 400k | keyHash = (*ht->keyHash)(key); |
242 | 400k | hep = PL_HashTableRawLookup(ht, keyHash, key); |
243 | 400k | if ((he = *hep) != 0) { |
244 | | /* Hit; see if values match */ |
245 | 1.27k | if ((*ht->valueCompare)(he->value, value)) { |
246 | | /* key,value pair is already present in table */ |
247 | 0 | return he; |
248 | 0 | } |
249 | 1.27k | if (he->value) { |
250 | 1.27k | (*ht->allocOps->freeEntry)(ht->allocPriv, he, HT_FREE_VALUE); |
251 | 1.27k | } |
252 | 1.27k | he->value = value; |
253 | 1.27k | return he; |
254 | 1.27k | } |
255 | 399k | return PL_HashTableRawAdd(ht, hep, keyHash, key, value); |
256 | 400k | } |
257 | | |
258 | | PR_IMPLEMENT(void) |
259 | 392k | PL_HashTableRawRemove(PLHashTable* ht, PLHashEntry** hep, PLHashEntry* he) { |
260 | 392k | PRUint32 i, n; |
261 | 392k | PLHashEntry *next, **oldbuckets; |
262 | 392k | PRSize nb; |
263 | | |
264 | 392k | *hep = he->next; |
265 | 392k | (*ht->allocOps->freeEntry)(ht->allocPriv, he, HT_FREE_ENTRY); |
266 | | |
267 | | /* Shrink table if it's underloaded */ |
268 | 392k | n = NBUCKETS(ht); |
269 | 392k | if (--ht->nentries < UNDERLOADED(n)) { |
270 | 34 | oldbuckets = ht->buckets; |
271 | 34 | nb = n * sizeof(PLHashEntry*) / 2; |
272 | 34 | ht->buckets = |
273 | 34 | (PLHashEntry**)((*ht->allocOps->allocTable)(ht->allocPriv, nb)); |
274 | 34 | if (!ht->buckets) { |
275 | 0 | ht->buckets = oldbuckets; |
276 | 0 | return; |
277 | 0 | } |
278 | 34 | memset(ht->buckets, 0, nb); |
279 | | #ifdef HASHMETER |
280 | | ht->nshrinks++; |
281 | | #endif |
282 | 34 | ht->shift++; |
283 | | |
284 | 1.57k | for (i = 0; i < n; i++) { |
285 | 1.59k | for (he = oldbuckets[i]; he; he = next) { |
286 | 56 | next = he->next; |
287 | 56 | hep = PL_HashTableRawLookup(ht, he->keyHash, he->key); |
288 | 56 | PR_ASSERT(*hep == 0); |
289 | 56 | he->next = 0; |
290 | 56 | *hep = he; |
291 | 56 | } |
292 | 1.53k | } |
293 | 34 | #ifdef DEBUG |
294 | 34 | memset(oldbuckets, 0xDB, n * sizeof oldbuckets[0]); |
295 | 34 | #endif |
296 | 34 | (*ht->allocOps->freeTable)(ht->allocPriv, oldbuckets); |
297 | 34 | } |
298 | 392k | } |
299 | | |
300 | | PR_IMPLEMENT(PRBool) |
301 | 392k | PL_HashTableRemove(PLHashTable* ht, const void* key) { |
302 | 392k | PLHashNumber keyHash; |
303 | 392k | PLHashEntry *he, **hep; |
304 | | |
305 | 392k | keyHash = (*ht->keyHash)(key); |
306 | 392k | hep = PL_HashTableRawLookup(ht, keyHash, key); |
307 | 392k | if ((he = *hep) == 0) { |
308 | 0 | return PR_FALSE; |
309 | 0 | } |
310 | | |
311 | | /* Hit; remove element */ |
312 | 392k | PL_HashTableRawRemove(ht, hep, he); |
313 | 392k | return PR_TRUE; |
314 | 392k | } |
315 | | |
316 | | PR_IMPLEMENT(void*) |
317 | 1.43M | PL_HashTableLookup(PLHashTable* ht, const void* key) { |
318 | 1.43M | PLHashNumber keyHash; |
319 | 1.43M | PLHashEntry *he, **hep; |
320 | | |
321 | 1.43M | keyHash = (*ht->keyHash)(key); |
322 | 1.43M | hep = PL_HashTableRawLookup(ht, keyHash, key); |
323 | 1.43M | if ((he = *hep) != 0) { |
324 | 756k | return he->value; |
325 | 756k | } |
326 | 680k | return 0; |
327 | 1.43M | } |
328 | | |
329 | | /* |
330 | | ** Same as PL_HashTableLookup but doesn't reorder the hash entries. |
331 | | */ |
332 | | PR_IMPLEMENT(void*) |
333 | 36.5M | PL_HashTableLookupConst(PLHashTable* ht, const void* key) { |
334 | 36.5M | PLHashNumber keyHash; |
335 | 36.5M | PLHashEntry *he, **hep; |
336 | | |
337 | 36.5M | keyHash = (*ht->keyHash)(key); |
338 | 36.5M | hep = PL_HashTableRawLookupConst(ht, keyHash, key); |
339 | 36.5M | if ((he = *hep) != 0) { |
340 | 35.5M | return he->value; |
341 | 35.5M | } |
342 | 1.04M | return 0; |
343 | 36.5M | } |
344 | | |
345 | | /* |
346 | | ** Iterate over the entries in the hash table calling func for each |
347 | | ** entry found. Stop if "f" says to (return value & PR_ENUMERATE_STOP). |
348 | | ** Return a count of the number of elements scanned. |
349 | | */ |
350 | | PR_IMPLEMENT(int) |
351 | 61 | PL_HashTableEnumerateEntries(PLHashTable* ht, PLHashEnumerator f, void* arg) { |
352 | 61 | PLHashEntry *he, **hep; |
353 | 61 | PRUint32 i, nbuckets; |
354 | 61 | int rv, n = 0; |
355 | 61 | PLHashEntry* todo = 0; |
356 | | |
357 | 61 | nbuckets = NBUCKETS(ht); |
358 | 2.46k | for (i = 0; i < nbuckets; i++) { |
359 | 2.40k | hep = &ht->buckets[i]; |
360 | 2.40k | while ((he = *hep) != 0) { |
361 | 8 | rv = (*f)(he, n, arg); |
362 | 8 | n++; |
363 | 8 | if (rv & (HT_ENUMERATE_REMOVE | HT_ENUMERATE_UNHASH)) { |
364 | 0 | *hep = he->next; |
365 | 0 | if (rv & HT_ENUMERATE_REMOVE) { |
366 | 0 | he->next = todo; |
367 | 0 | todo = he; |
368 | 0 | } |
369 | 8 | } else { |
370 | 8 | hep = &he->next; |
371 | 8 | } |
372 | 8 | if (rv & HT_ENUMERATE_STOP) { |
373 | 0 | goto out; |
374 | 0 | } |
375 | 8 | } |
376 | 2.40k | } |
377 | | |
378 | 61 | out: |
379 | 61 | hep = &todo; |
380 | 61 | while ((he = *hep) != 0) { |
381 | 0 | PL_HashTableRawRemove(ht, hep, he); |
382 | 0 | } |
383 | 61 | return n; |
384 | 61 | } |
385 | | |
386 | | #ifdef HASHMETER |
387 | | # include <math.h> |
388 | | # include <stdio.h> |
389 | | |
390 | | PR_IMPLEMENT(void) |
391 | | PL_HashTableDumpMeter(PLHashTable* ht, PLHashEnumerator dump, FILE* fp) { |
392 | | double mean, variance; |
393 | | PRUint32 nchains, nbuckets; |
394 | | PRUint32 i, n, maxChain, maxChainLen; |
395 | | PLHashEntry* he; |
396 | | |
397 | | variance = 0; |
398 | | nchains = 0; |
399 | | maxChainLen = 0; |
400 | | nbuckets = NBUCKETS(ht); |
401 | | for (i = 0; i < nbuckets; i++) { |
402 | | he = ht->buckets[i]; |
403 | | if (!he) { |
404 | | continue; |
405 | | } |
406 | | nchains++; |
407 | | for (n = 0; he; he = he->next) { |
408 | | n++; |
409 | | } |
410 | | variance += n * n; |
411 | | if (n > maxChainLen) { |
412 | | maxChainLen = n; |
413 | | maxChain = i; |
414 | | } |
415 | | } |
416 | | mean = (double)ht->nentries / nchains; |
417 | | variance = fabs(variance / nchains - mean * mean); |
418 | | |
419 | | fprintf(fp, "\nHash table statistics:\n"); |
420 | | fprintf(fp, " number of lookups: %u\n", ht->nlookups); |
421 | | fprintf(fp, " number of entries: %u\n", ht->nentries); |
422 | | fprintf(fp, " number of grows: %u\n", ht->ngrows); |
423 | | fprintf(fp, " number of shrinks: %u\n", ht->nshrinks); |
424 | | fprintf(fp, " mean steps per hash: %g\n", |
425 | | (double)ht->nsteps / ht->nlookups); |
426 | | fprintf(fp, "mean hash chain length: %g\n", mean); |
427 | | fprintf(fp, " standard deviation: %g\n", sqrt(variance)); |
428 | | fprintf(fp, " max hash chain length: %u\n", maxChainLen); |
429 | | fprintf(fp, " max hash chain: [%u]\n", maxChain); |
430 | | |
431 | | for (he = ht->buckets[maxChain], i = 0; he; he = he->next, i++) |
432 | | if ((*dump)(he, i, fp) != HT_ENUMERATE_NEXT) { |
433 | | break; |
434 | | } |
435 | | } |
436 | | #endif /* HASHMETER */ |
437 | | |
438 | | PR_IMPLEMENT(int) |
439 | 0 | PL_HashTableDump(PLHashTable* ht, PLHashEnumerator dump, FILE* fp) { |
440 | 0 | int count; |
441 | |
|
442 | 0 | count = PL_HashTableEnumerateEntries(ht, dump, fp); |
443 | | #ifdef HASHMETER |
444 | | PL_HashTableDumpMeter(ht, dump, fp); |
445 | | #endif |
446 | 0 | return count; |
447 | 0 | } |
448 | | |
449 | | PR_IMPLEMENT(PLHashNumber) |
450 | 0 | PL_HashString(const void* key) { |
451 | 0 | PLHashNumber h; |
452 | 0 | const PRUint8* s; |
453 | |
|
454 | 0 | h = 0; |
455 | 0 | for (s = (const PRUint8*)key; *s; s++) { |
456 | 0 | h = PR_ROTATE_LEFT32(h, 4) ^ *s; |
457 | 0 | } |
458 | 0 | return h; |
459 | 0 | } |
460 | | |
461 | | PR_IMPLEMENT(int) |
462 | 0 | PL_CompareStrings(const void* v1, const void* v2) { |
463 | 0 | return strcmp((const char*)v1, (const char*)v2) == 0; |
464 | 0 | } |
465 | | |
466 | | PR_IMPLEMENT(int) |
467 | 33.2M | PL_CompareValues(const void* v1, const void* v2) { return v1 == v2; } |