1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/. */
7
* Double hashing implementation.
9
* Try to keep this file in sync with xpcom/glue/pldhash.cpp.
20
# if defined MOZILLA_CLIENT && defined DEBUG_XXXbrendan
21
# include "nsTraceMalloc.h"
25
# define METER(x) /* nothing */
29
* The following DEBUG-only code is used to assert that calls to one of
30
* table->ops or to an enumerator do not cause re-entry into a call that
31
* can mutate the table. The recursion level is stored in additional
32
* space allocated at the end of the entry store to avoid changing
33
* JSDHashTable, which could cause issues when mixing DEBUG and
34
* non-DEBUG components.
38
#define JSDHASH_ONELINE_ASSERT JS_ASSERT
39
#define RECURSION_LEVEL(table_) (*(uint32_t*)(table_->entryStore + \
40
JS_DHASH_TABLE_SIZE(table_) * \
43
* Most callers that assert about the recursion level don't care about
44
* this magical value because they are asserting that mutation is
45
* allowed (and therefore the level is 0 or 1, depending on whether they
48
* Only PL_DHashTableFinish needs to allow this special value.
50
#define IMMUTABLE_RECURSION_LEVEL UINT32_MAX
52
#define RECURSION_LEVEL_SAFE_TO_FINISH(table_) \
53
(RECURSION_LEVEL(table_) == 0 || \
54
RECURSION_LEVEL(table_) == IMMUTABLE_RECURSION_LEVEL)
56
#define ENTRY_STORE_EXTRA sizeof(uint32_t)
57
#define INCREMENT_RECURSION_LEVEL(table_) \
59
if (RECURSION_LEVEL(table_) != IMMUTABLE_RECURSION_LEVEL) \
60
++RECURSION_LEVEL(table_); \
62
#define DECREMENT_RECURSION_LEVEL(table_) \
64
if (RECURSION_LEVEL(table_) != IMMUTABLE_RECURSION_LEVEL) { \
65
JS_ASSERT(RECURSION_LEVEL(table_) > 0); \
66
--RECURSION_LEVEL(table_); \
72
#define ENTRY_STORE_EXTRA 0
73
#define INCREMENT_RECURSION_LEVEL(table_) JS_BEGIN_MACRO JS_END_MACRO
74
#define DECREMENT_RECURSION_LEVEL(table_) JS_BEGIN_MACRO JS_END_MACRO
76
#endif /* defined(DEBUG) */
79
JS_DHashAllocTable(JSDHashTable *table, uint32_t nbytes)
81
return OffTheBooks::malloc_(nbytes);
85
JS_DHashFreeTable(JSDHashTable *table, void *ptr)
87
UnwantedForeground::free_(ptr);
90
JS_PUBLIC_API(JSDHashNumber)
91
JS_DHashStringKey(JSDHashTable *table, const void *key)
94
const unsigned char *s;
97
for (s = (const unsigned char *) key; *s != '\0'; s++)
98
h = JS_ROTATE_LEFT32(h, 4) ^ *s;
102
JS_PUBLIC_API(JSDHashNumber)
103
JS_DHashVoidPtrKeyStub(JSDHashTable *table, const void *key)
105
return (JSDHashNumber)(uintptr_t)key >> 2;
108
JS_PUBLIC_API(JSBool)
109
JS_DHashMatchEntryStub(JSDHashTable *table,
110
const JSDHashEntryHdr *entry,
113
const JSDHashEntryStub *stub = (const JSDHashEntryStub *)entry;
115
return stub->key == key;
118
JS_PUBLIC_API(JSBool)
119
JS_DHashMatchStringKey(JSDHashTable *table,
120
const JSDHashEntryHdr *entry,
123
const JSDHashEntryStub *stub = (const JSDHashEntryStub *)entry;
125
/* XXX tolerate null keys on account of sloppy Mozilla callers. */
126
return stub->key == key ||
128
strcmp((const char *) stub->key, (const char *) key) == 0);
132
JS_DHashMoveEntryStub(JSDHashTable *table,
133
const JSDHashEntryHdr *from,
136
js_memcpy(to, from, table->entrySize);
140
JS_DHashClearEntryStub(JSDHashTable *table, JSDHashEntryHdr *entry)
142
memset(entry, 0, table->entrySize);
146
JS_DHashFreeStringKey(JSDHashTable *table, JSDHashEntryHdr *entry)
148
const JSDHashEntryStub *stub = (const JSDHashEntryStub *)entry;
150
UnwantedForeground::free_((void *) stub->key);
151
memset(entry, 0, table->entrySize);
155
JS_DHashFinalizeStub(JSDHashTable *table)
159
static const JSDHashTableOps stub_ops = {
162
JS_DHashVoidPtrKeyStub,
163
JS_DHashMatchEntryStub,
164
JS_DHashMoveEntryStub,
165
JS_DHashClearEntryStub,
166
JS_DHashFinalizeStub,
170
JS_PUBLIC_API(const JSDHashTableOps *)
171
JS_DHashGetStubOps(void)
176
JS_PUBLIC_API(JSDHashTable *)
177
JS_NewDHashTable(const JSDHashTableOps *ops, void *data, uint32_t entrySize,
182
table = (JSDHashTable *) OffTheBooks::malloc_(sizeof *table);
185
if (!JS_DHashTableInit(table, ops, data, entrySize, capacity)) {
186
Foreground::free_(table);
193
JS_DHashTableDestroy(JSDHashTable *table)
195
JS_DHashTableFinish(table);
196
UnwantedForeground::free_(table);
199
JS_PUBLIC_API(JSBool)
200
JS_DHashTableInit(JSDHashTable *table, const JSDHashTableOps *ops, void *data,
201
uint32_t entrySize, uint32_t capacity)
207
if (entrySize > 10 * sizeof(void *)) {
209
"jsdhash: for the table at address %p, the given entrySize"
210
" of %lu %s favors chaining over double hashing.\n",
212
(unsigned long) entrySize,
213
(entrySize > 16 * sizeof(void*)) ? "definitely" : "probably");
219
if (capacity < JS_DHASH_MIN_SIZE)
220
capacity = JS_DHASH_MIN_SIZE;
222
JS_CEILING_LOG2(log2, capacity);
224
capacity = JS_BIT(log2);
225
if (capacity >= JS_DHASH_SIZE_LIMIT)
227
table->hashShift = JS_DHASH_BITS - log2;
228
table->maxAlphaFrac = (uint8_t)(0x100 * JS_DHASH_DEFAULT_MAX_ALPHA);
229
table->minAlphaFrac = (uint8_t)(0x100 * JS_DHASH_DEFAULT_MIN_ALPHA);
230
table->entrySize = entrySize;
231
table->entryCount = table->removedCount = 0;
232
table->generation = 0;
233
nbytes = capacity * entrySize;
235
table->entryStore = (char *) ops->allocTable(table,
236
nbytes + ENTRY_STORE_EXTRA);
237
if (!table->entryStore)
239
memset(table->entryStore, 0, nbytes);
240
METER(memset(&table->stats, 0, sizeof table->stats));
243
RECURSION_LEVEL(table) = 0;
250
* Compute max and min load numbers (entry counts) from table params.
252
#define MAX_LOAD(table, size) (((table)->maxAlphaFrac * (size)) >> 8)
253
#define MIN_LOAD(table, size) (((table)->minAlphaFrac * (size)) >> 8)
256
JS_DHashTableSetAlphaBounds(JSDHashTable *table,
263
* Reject obviously insane bounds, rather than trying to guess what the
264
* buggy caller intended.
266
JS_ASSERT(0.5 <= maxAlpha && maxAlpha < 1 && 0 <= minAlpha);
267
if (maxAlpha < 0.5 || 1 <= maxAlpha || minAlpha < 0)
271
* Ensure that at least one entry will always be free. If maxAlpha at
272
* minimum size leaves no entries free, reduce maxAlpha based on minimum
273
* size and the precision limit of maxAlphaFrac's fixed point format.
275
JS_ASSERT(JS_DHASH_MIN_SIZE - (maxAlpha * JS_DHASH_MIN_SIZE) >= 1);
276
if (JS_DHASH_MIN_SIZE - (maxAlpha * JS_DHASH_MIN_SIZE) < 1) {
278
(JS_DHASH_MIN_SIZE - Max(JS_DHASH_MIN_SIZE / 256, 1))
283
* Ensure that minAlpha is strictly less than half maxAlpha. Take care
284
* not to truncate an entry's worth of alpha when storing in minAlphaFrac
285
* (8-bit fixed point format).
287
JS_ASSERT(minAlpha < maxAlpha / 2);
288
if (minAlpha >= maxAlpha / 2) {
289
size = JS_DHASH_TABLE_SIZE(table);
290
minAlpha = (size * maxAlpha - Max(size / 256, 1U)) / (2 * size);
293
table->maxAlphaFrac = (uint8_t)(maxAlpha * 256);
294
table->minAlphaFrac = (uint8_t)(minAlpha * 256);
298
* Double hashing needs the second hash code to be relatively prime to table
299
* size, so we simply make hash2 odd.
301
#define HASH1(hash0, shift) ((hash0) >> (shift))
302
#define HASH2(hash0,log2,shift) ((((hash0) << (log2)) >> (shift)) | 1)
305
* Reserve keyHash 0 for free entries and 1 for removed-entry sentinels. Note
306
* that a removed-entry sentinel need be stored only if the removed entry had
307
* a colliding entry added after it. Therefore we can use 1 as the collision
308
* flag in addition to the removed-entry sentinel value. Multiplicative hash
309
* uses the high order bits of keyHash, so this least-significant reservation
310
* should not hurt the hash function's effectiveness much.
312
* If you change any of these magic numbers, also update JS_DHASH_ENTRY_IS_LIVE
313
* in jsdhash.h. It used to be private to jsdhash.c, but then became public to
314
* assist iterator writers who inspect table->entryStore directly.
316
#define COLLISION_FLAG ((JSDHashNumber) 1)
317
#define MARK_ENTRY_FREE(entry) ((entry)->keyHash = 0)
318
#define MARK_ENTRY_REMOVED(entry) ((entry)->keyHash = 1)
319
#define ENTRY_IS_REMOVED(entry) ((entry)->keyHash == 1)
320
#define ENTRY_IS_LIVE(entry) JS_DHASH_ENTRY_IS_LIVE(entry)
321
#define ENSURE_LIVE_KEYHASH(hash0) if (hash0 < 2) hash0 -= 2; else (void)0
323
/* Match an entry's keyHash against an unstored one computed from a key. */
324
#define MATCH_ENTRY_KEYHASH(entry,hash0) \
325
(((entry)->keyHash & ~COLLISION_FLAG) == (hash0))
327
/* Compute the address of the indexed entry in table. */
328
#define ADDRESS_ENTRY(table, index) \
329
((JSDHashEntryHdr *)((table)->entryStore + (index) * (table)->entrySize))
332
JS_DHashTableFinish(JSDHashTable *table)
334
char *entryAddr, *entryLimit;
336
JSDHashEntryHdr *entry;
338
#ifdef DEBUG_XXXbrendan
339
static FILE *dumpfp = NULL;
340
if (!dumpfp) dumpfp = fopen("/tmp/jsdhash.bigdump", "w");
342
#ifdef MOZILLA_CLIENT
343
NS_TraceStack(1, dumpfp);
345
JS_DHashTableDumpMeter(table, NULL, dumpfp);
350
INCREMENT_RECURSION_LEVEL(table);
352
/* Call finalize before clearing entries, so it can enumerate them. */
353
table->ops->finalize(table);
355
/* Clear any remaining live entries. */
356
entryAddr = table->entryStore;
357
entrySize = table->entrySize;
358
entryLimit = entryAddr + JS_DHASH_TABLE_SIZE(table) * entrySize;
359
while (entryAddr < entryLimit) {
360
entry = (JSDHashEntryHdr *)entryAddr;
361
if (ENTRY_IS_LIVE(entry)) {
362
METER(table->stats.removeEnums++);
363
table->ops->clearEntry(table, entry);
365
entryAddr += entrySize;
368
DECREMENT_RECURSION_LEVEL(table);
369
JS_ASSERT(RECURSION_LEVEL_SAFE_TO_FINISH(table));
371
/* Free entry storage last. */
372
table->ops->freeTable(table, table->entryStore);
375
static JSDHashEntryHdr * JS_DHASH_FASTCALL
376
SearchTable(JSDHashTable *table, const void *key, JSDHashNumber keyHash,
379
JSDHashNumber hash1, hash2;
380
int hashShift, sizeLog2;
381
JSDHashEntryHdr *entry, *firstRemoved;
382
JSDHashMatchEntry matchEntry;
385
METER(table->stats.searches++);
386
JS_ASSERT(!(keyHash & COLLISION_FLAG));
388
/* Compute the primary hash address. */
389
hashShift = table->hashShift;
390
hash1 = HASH1(keyHash, hashShift);
391
entry = ADDRESS_ENTRY(table, hash1);
393
/* Miss: return space for a new entry. */
394
if (JS_DHASH_ENTRY_IS_FREE(entry)) {
395
METER(table->stats.misses++);
399
/* Hit: return entry. */
400
matchEntry = table->ops->matchEntry;
401
if (MATCH_ENTRY_KEYHASH(entry, keyHash) && matchEntry(table, entry, key)) {
402
METER(table->stats.hits++);
406
/* Collision: double hash. */
407
sizeLog2 = JS_DHASH_BITS - table->hashShift;
408
hash2 = HASH2(keyHash, sizeLog2, hashShift);
409
sizeMask = JS_BITMASK(sizeLog2);
411
/* Save the first removed entry pointer so JS_DHASH_ADD can recycle it. */
415
if (JS_UNLIKELY(ENTRY_IS_REMOVED(entry))) {
417
firstRemoved = entry;
419
if (op == JS_DHASH_ADD)
420
entry->keyHash |= COLLISION_FLAG;
423
METER(table->stats.steps++);
427
entry = ADDRESS_ENTRY(table, hash1);
428
if (JS_DHASH_ENTRY_IS_FREE(entry)) {
429
METER(table->stats.misses++);
430
return (firstRemoved && op == JS_DHASH_ADD) ? firstRemoved : entry;
433
if (MATCH_ENTRY_KEYHASH(entry, keyHash) &&
434
matchEntry(table, entry, key)) {
435
METER(table->stats.hits++);
445
* This is a copy of SearchTable, used by ChangeTable, hardcoded to
446
* 1. assume |op == PL_DHASH_ADD|,
447
* 2. assume that |key| will never match an existing entry, and
448
* 3. assume that no entries have been removed from the current table
450
* Avoiding the need for |key| means we can avoid needing a way to map
451
* entries to keys, which means callers can use complex key types more
454
static JSDHashEntryHdr * JS_DHASH_FASTCALL
455
FindFreeEntry(JSDHashTable *table, JSDHashNumber keyHash)
457
JSDHashNumber hash1, hash2;
458
int hashShift, sizeLog2;
459
JSDHashEntryHdr *entry;
462
METER(table->stats.searches++);
463
JS_ASSERT(!(keyHash & COLLISION_FLAG));
465
/* Compute the primary hash address. */
466
hashShift = table->hashShift;
467
hash1 = HASH1(keyHash, hashShift);
468
entry = ADDRESS_ENTRY(table, hash1);
470
/* Miss: return space for a new entry. */
471
if (JS_DHASH_ENTRY_IS_FREE(entry)) {
472
METER(table->stats.misses++);
476
/* Collision: double hash. */
477
sizeLog2 = JS_DHASH_BITS - table->hashShift;
478
hash2 = HASH2(keyHash, sizeLog2, hashShift);
479
sizeMask = JS_BITMASK(sizeLog2);
482
JS_ASSERT(!ENTRY_IS_REMOVED(entry));
483
entry->keyHash |= COLLISION_FLAG;
485
METER(table->stats.steps++);
489
entry = ADDRESS_ENTRY(table, hash1);
490
if (JS_DHASH_ENTRY_IS_FREE(entry)) {
491
METER(table->stats.misses++);
501
ChangeTable(JSDHashTable *table, int deltaLog2)
503
int oldLog2, newLog2;
504
uint32_t oldCapacity, newCapacity;
505
char *newEntryStore, *oldEntryStore, *oldEntryAddr;
506
uint32_t entrySize, i, nbytes;
507
JSDHashEntryHdr *oldEntry, *newEntry;
508
JSDHashMoveEntry moveEntry;
510
uint32_t recursionLevel;
513
/* Look, but don't touch, until we succeed in getting new entry store. */
514
oldLog2 = JS_DHASH_BITS - table->hashShift;
515
newLog2 = oldLog2 + deltaLog2;
516
oldCapacity = JS_BIT(oldLog2);
517
newCapacity = JS_BIT(newLog2);
518
if (newCapacity >= JS_DHASH_SIZE_LIMIT)
520
entrySize = table->entrySize;
521
nbytes = newCapacity * entrySize;
523
newEntryStore = (char *) table->ops->allocTable(table,
524
nbytes + ENTRY_STORE_EXTRA);
528
/* We can't fail from here on, so update table parameters. */
530
recursionLevel = RECURSION_LEVEL(table);
532
table->hashShift = JS_DHASH_BITS - newLog2;
533
table->removedCount = 0;
536
/* Assign the new entry store to table. */
537
memset(newEntryStore, 0, nbytes);
538
oldEntryAddr = oldEntryStore = table->entryStore;
539
table->entryStore = newEntryStore;
540
moveEntry = table->ops->moveEntry;
542
RECURSION_LEVEL(table) = recursionLevel;
545
/* Copy only live entries, leaving removed ones behind. */
546
for (i = 0; i < oldCapacity; i++) {
547
oldEntry = (JSDHashEntryHdr *)oldEntryAddr;
548
if (ENTRY_IS_LIVE(oldEntry)) {
549
oldEntry->keyHash &= ~COLLISION_FLAG;
550
newEntry = FindFreeEntry(table, oldEntry->keyHash);
551
JS_ASSERT(JS_DHASH_ENTRY_IS_FREE(newEntry));
552
moveEntry(table, oldEntry, newEntry);
553
newEntry->keyHash = oldEntry->keyHash;
555
oldEntryAddr += entrySize;
558
table->ops->freeTable(table, oldEntryStore);
562
JS_PUBLIC_API(JSDHashEntryHdr *) JS_DHASH_FASTCALL
563
JS_DHashTableOperate(JSDHashTable *table, const void *key, JSDHashOperator op)
565
JSDHashNumber keyHash;
566
JSDHashEntryHdr *entry;
570
JS_ASSERT(op == JS_DHASH_LOOKUP || RECURSION_LEVEL(table) == 0);
571
INCREMENT_RECURSION_LEVEL(table);
573
keyHash = table->ops->hashKey(table, key);
574
keyHash *= JS_DHASH_GOLDEN_RATIO;
576
/* Avoid 0 and 1 hash codes, they indicate free and removed entries. */
577
ENSURE_LIVE_KEYHASH(keyHash);
578
keyHash &= ~COLLISION_FLAG;
581
case JS_DHASH_LOOKUP:
582
METER(table->stats.lookups++);
583
entry = SearchTable(table, key, keyHash, op);
588
* If alpha is >= .75, grow or compress the table. If key is already
589
* in the table, we may grow once more than necessary, but only if we
590
* are on the edge of being overloaded.
592
size = JS_DHASH_TABLE_SIZE(table);
593
if (table->entryCount + table->removedCount >= MAX_LOAD(table, size)) {
594
/* Compress if a quarter or more of all entries are removed. */
595
if (table->removedCount >= size >> 2) {
596
METER(table->stats.compresses++);
599
METER(table->stats.grows++);
604
* Grow or compress table, returning null if ChangeTable fails and
605
* falling through might claim the last free entry.
607
if (!ChangeTable(table, deltaLog2) &&
608
table->entryCount + table->removedCount == size - 1) {
609
METER(table->stats.addFailures++);
616
* Look for entry after possibly growing, so we don't have to add it,
617
* then skip it while growing the table and re-add it after.
619
entry = SearchTable(table, key, keyHash, op);
620
if (!ENTRY_IS_LIVE(entry)) {
621
/* Initialize the entry, indicating that it's no longer free. */
622
METER(table->stats.addMisses++);
623
if (ENTRY_IS_REMOVED(entry)) {
624
METER(table->stats.addOverRemoved++);
625
table->removedCount--;
626
keyHash |= COLLISION_FLAG;
628
if (table->ops->initEntry &&
629
!table->ops->initEntry(table, entry, key)) {
630
/* We haven't claimed entry yet; fail with null return. */
631
memset(entry + 1, 0, table->entrySize - sizeof *entry);
635
entry->keyHash = keyHash;
638
METER(else table->stats.addHits++);
641
case JS_DHASH_REMOVE:
642
entry = SearchTable(table, key, keyHash, op);
643
if (ENTRY_IS_LIVE(entry)) {
644
/* Clear this entry and mark it as "removed". */
645
METER(table->stats.removeHits++);
646
JS_DHashTableRawRemove(table, entry);
648
/* Shrink if alpha is <= .25 and table isn't too small already. */
649
size = JS_DHASH_TABLE_SIZE(table);
650
if (size > JS_DHASH_MIN_SIZE &&
651
table->entryCount <= MIN_LOAD(table, size)) {
652
METER(table->stats.shrinks++);
653
(void) ChangeTable(table, -1);
656
METER(else table->stats.removeMisses++);
665
DECREMENT_RECURSION_LEVEL(table);
671
JS_DHashTableRawRemove(JSDHashTable *table, JSDHashEntryHdr *entry)
673
JSDHashNumber keyHash; /* load first in case clearEntry goofs it */
675
JS_ASSERT(RECURSION_LEVEL(table) != IMMUTABLE_RECURSION_LEVEL);
677
JS_ASSERT(JS_DHASH_ENTRY_IS_LIVE(entry));
678
keyHash = entry->keyHash;
679
table->ops->clearEntry(table, entry);
680
if (keyHash & COLLISION_FLAG) {
681
MARK_ENTRY_REMOVED(entry);
682
table->removedCount++;
684
METER(table->stats.removeFrees++);
685
MARK_ENTRY_FREE(entry);
690
JS_PUBLIC_API(uint32_t)
691
JS_DHashTableEnumerate(JSDHashTable *table, JSDHashEnumerator etor, void *arg)
693
char *entryAddr, *entryLimit;
694
uint32_t i, capacity, entrySize, ceiling;
696
JSDHashEntryHdr *entry;
699
INCREMENT_RECURSION_LEVEL(table);
701
entryAddr = table->entryStore;
702
entrySize = table->entrySize;
703
capacity = JS_DHASH_TABLE_SIZE(table);
704
entryLimit = entryAddr + capacity * entrySize;
706
didRemove = JS_FALSE;
707
while (entryAddr < entryLimit) {
708
entry = (JSDHashEntryHdr *)entryAddr;
709
if (ENTRY_IS_LIVE(entry)) {
710
op = etor(table, entry, i++, arg);
711
if (op & JS_DHASH_REMOVE) {
712
METER(table->stats.removeEnums++);
713
JS_DHashTableRawRemove(table, entry);
716
if (op & JS_DHASH_STOP)
719
entryAddr += entrySize;
722
JS_ASSERT(!didRemove || RECURSION_LEVEL(table) == 1);
725
* Shrink or compress if a quarter or more of all entries are removed, or
726
* if the table is underloaded according to the configured minimum alpha,
727
* and is not minimal-size already. Do this only if we removed above, so
728
* non-removing enumerations can count on stable table->entryStore until
729
* the next non-lookup-Operate or removing-Enumerate.
732
(table->removedCount >= capacity >> 2 ||
733
(capacity > JS_DHASH_MIN_SIZE &&
734
table->entryCount <= MIN_LOAD(table, capacity)))) {
735
METER(table->stats.enumShrinks++);
736
capacity = table->entryCount;
737
capacity += capacity >> 1;
738
if (capacity < JS_DHASH_MIN_SIZE)
739
capacity = JS_DHASH_MIN_SIZE;
741
JS_CEILING_LOG2(ceiling, capacity);
742
ceiling -= JS_DHASH_BITS - table->hashShift;
744
(void) ChangeTable(table, ceiling);
747
DECREMENT_RECURSION_LEVEL(table);
752
struct SizeOfEntryExcludingThisArg
755
JSDHashSizeOfEntryExcludingThisFun sizeOfEntryExcludingThis;
756
JSMallocSizeOfFun mallocSizeOf;
757
void *arg; // the arg passed by the user
760
static JSDHashOperator
761
SizeOfEntryExcludingThisEnumerator(JSDHashTable *table, JSDHashEntryHdr *hdr,
762
uint32_t number, void *arg)
764
SizeOfEntryExcludingThisArg *e = (SizeOfEntryExcludingThisArg *)arg;
765
e->total += e->sizeOfEntryExcludingThis(hdr, e->mallocSizeOf, e->arg);
766
return JS_DHASH_NEXT;
769
extern JS_PUBLIC_API(size_t)
770
JS_DHashTableSizeOfExcludingThis(const JSDHashTable *table,
771
JSDHashSizeOfEntryExcludingThisFun sizeOfEntryExcludingThis,
772
JSMallocSizeOfFun mallocSizeOf,
773
void *arg /* = NULL */)
776
n += mallocSizeOf(table->entryStore);
777
if (sizeOfEntryExcludingThis) {
778
SizeOfEntryExcludingThisArg arg2 = { 0, sizeOfEntryExcludingThis, mallocSizeOf, arg };
779
JS_DHashTableEnumerate(const_cast<JSDHashTable *>(table),
780
SizeOfEntryExcludingThisEnumerator, &arg2);
786
extern JS_PUBLIC_API(size_t)
787
JS_DHashTableSizeOfIncludingThis(const JSDHashTable *table,
788
JSDHashSizeOfEntryExcludingThisFun sizeOfEntryExcludingThis,
789
JSMallocSizeOfFun mallocSizeOf,
790
void *arg /* = NULL */)
792
return mallocSizeOf(table) +
793
JS_DHashTableSizeOfExcludingThis(table, sizeOfEntryExcludingThis,
799
JS_DHashMarkTableImmutable(JSDHashTable *table)
801
RECURSION_LEVEL(table) = IMMUTABLE_RECURSION_LEVEL;
809
JS_DHashTableDumpMeter(JSDHashTable *table, JSDHashEnumerator dump, FILE *fp)
812
uint32_t entrySize, entryCount;
813
int hashShift, sizeLog2;
814
uint32_t i, tableSize, sizeMask, chainLen, maxChainLen, chainCount;
815
JSDHashNumber hash1, hash2, saveHash1, maxChainHash1, maxChainHash2;
816
double sqsum, mean, variance, sigma;
817
JSDHashEntryHdr *entry, *probe;
819
entryAddr = table->entryStore;
820
entrySize = table->entrySize;
821
hashShift = table->hashShift;
822
sizeLog2 = JS_DHASH_BITS - hashShift;
823
tableSize = JS_DHASH_TABLE_SIZE(table);
824
sizeMask = JS_BITMASK(sizeLog2);
825
chainCount = maxChainLen = 0;
829
for (i = 0; i < tableSize; i++) {
830
entry = (JSDHashEntryHdr *)entryAddr;
831
entryAddr += entrySize;
832
if (!ENTRY_IS_LIVE(entry))
834
hash1 = HASH1(entry->keyHash & ~COLLISION_FLAG, hashShift);
836
probe = ADDRESS_ENTRY(table, hash1);
838
if (probe == entry) {
839
/* Start of a (possibly unit-length) chain. */
842
hash2 = HASH2(entry->keyHash & ~COLLISION_FLAG, sizeLog2,
848
probe = ADDRESS_ENTRY(table, hash1);
849
} while (probe != entry);
851
sqsum += chainLen * chainLen;
852
if (chainLen > maxChainLen) {
853
maxChainLen = chainLen;
854
maxChainHash1 = saveHash1;
855
maxChainHash2 = hash2;
859
entryCount = table->entryCount;
860
if (entryCount && chainCount) {
861
mean = (double)entryCount / chainCount;
862
variance = chainCount * sqsum - entryCount * entryCount;
863
if (variance < 0 || chainCount == 1)
866
variance /= chainCount * (chainCount - 1);
867
sigma = sqrt(variance);
872
fprintf(fp, "Double hashing statistics:\n");
873
fprintf(fp, " table size (in entries): %u\n", tableSize);
874
fprintf(fp, " number of entries: %u\n", table->entryCount);
875
fprintf(fp, " number of removed entries: %u\n", table->removedCount);
876
fprintf(fp, " number of searches: %u\n", table->stats.searches);
877
fprintf(fp, " number of hits: %u\n", table->stats.hits);
878
fprintf(fp, " number of misses: %u\n", table->stats.misses);
879
fprintf(fp, " mean steps per search: %g\n", table->stats.searches ?
880
(double)table->stats.steps
881
/ table->stats.searches :
883
fprintf(fp, " mean hash chain length: %g\n", mean);
884
fprintf(fp, " standard deviation: %g\n", sigma);
885
fprintf(fp, " maximum hash chain length: %u\n", maxChainLen);
886
fprintf(fp, " number of lookups: %u\n", table->stats.lookups);
887
fprintf(fp, " adds that made a new entry: %u\n", table->stats.addMisses);
888
fprintf(fp, "adds that recycled removeds: %u\n", table->stats.addOverRemoved);
889
fprintf(fp, " adds that found an entry: %u\n", table->stats.addHits);
890
fprintf(fp, " add failures: %u\n", table->stats.addFailures);
891
fprintf(fp, " useful removes: %u\n", table->stats.removeHits);
892
fprintf(fp, " useless removes: %u\n", table->stats.removeMisses);
893
fprintf(fp, "removes that freed an entry: %u\n", table->stats.removeFrees);
894
fprintf(fp, " removes while enumerating: %u\n", table->stats.removeEnums);
895
fprintf(fp, " number of grows: %u\n", table->stats.grows);
896
fprintf(fp, " number of shrinks: %u\n", table->stats.shrinks);
897
fprintf(fp, " number of compresses: %u\n", table->stats.compresses);
898
fprintf(fp, "number of enumerate shrinks: %u\n", table->stats.enumShrinks);
900
if (dump && maxChainLen && hash2) {
901
fputs("Maximum hash chain:\n", fp);
902
hash1 = maxChainHash1;
903
hash2 = maxChainHash2;
904
entry = ADDRESS_ENTRY(table, hash1);
907
if (dump(table, entry, i++, fp) != JS_DHASH_NEXT)
911
entry = ADDRESS_ENTRY(table, hash1);
912
} while (JS_DHASH_ENTRY_IS_BUSY(entry));
915
#endif /* JS_DHASHMETER */