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