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