Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
michael@0 | 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | /* |
michael@0 | 7 | * Double hashing implementation. |
michael@0 | 8 | */ |
michael@0 | 9 | #include <stdio.h> |
michael@0 | 10 | #include <stdlib.h> |
michael@0 | 11 | #include <string.h> |
michael@0 | 12 | #include "pldhash.h" |
michael@0 | 13 | #include "mozilla/HashFunctions.h" |
michael@0 | 14 | #include "mozilla/MathAlgorithms.h" |
michael@0 | 15 | #include "nsDebug.h" /* for PR_ASSERT */ |
michael@0 | 16 | #include "nsAlgorithm.h" |
michael@0 | 17 | #include "mozilla/Likely.h" |
michael@0 | 18 | #include "mozilla/MemoryReporting.h" |
michael@0 | 19 | #include "mozilla/ChaosMode.h" |
michael@0 | 20 | |
michael@0 | 21 | #ifdef PL_DHASHMETER |
michael@0 | 22 | # define METER(x) x |
michael@0 | 23 | #else |
michael@0 | 24 | # define METER(x) /* nothing */ |
michael@0 | 25 | #endif |
michael@0 | 26 | |
michael@0 | 27 | /* |
michael@0 | 28 | * The following DEBUG-only code is used to assert that calls to one of |
michael@0 | 29 | * table->ops or to an enumerator do not cause re-entry into a call that |
michael@0 | 30 | * can mutate the table. |
michael@0 | 31 | */ |
michael@0 | 32 | #ifdef DEBUG |
michael@0 | 33 | |
michael@0 | 34 | /* |
michael@0 | 35 | * Most callers that assert about the recursion level don't care about |
michael@0 | 36 | * this magical value because they are asserting that mutation is |
michael@0 | 37 | * allowed (and therefore the level is 0 or 1, depending on whether they |
michael@0 | 38 | * incremented it). |
michael@0 | 39 | * |
michael@0 | 40 | * Only PL_DHashTableFinish needs to allow this special value. |
michael@0 | 41 | */ |
michael@0 | 42 | #define IMMUTABLE_RECURSION_LEVEL ((uint16_t)-1) |
michael@0 | 43 | |
michael@0 | 44 | #define RECURSION_LEVEL_SAFE_TO_FINISH(table_) \ |
michael@0 | 45 | (table_->recursionLevel == 0 || \ |
michael@0 | 46 | table_->recursionLevel == IMMUTABLE_RECURSION_LEVEL) |
michael@0 | 47 | |
michael@0 | 48 | #define INCREMENT_RECURSION_LEVEL(table_) \ |
michael@0 | 49 | do { \ |
michael@0 | 50 | if (table_->recursionLevel != IMMUTABLE_RECURSION_LEVEL) \ |
michael@0 | 51 | ++table_->recursionLevel; \ |
michael@0 | 52 | } while(0) |
michael@0 | 53 | #define DECREMENT_RECURSION_LEVEL(table_) \ |
michael@0 | 54 | do { \ |
michael@0 | 55 | if (table->recursionLevel != IMMUTABLE_RECURSION_LEVEL) { \ |
michael@0 | 56 | MOZ_ASSERT(table->recursionLevel > 0); \ |
michael@0 | 57 | --table->recursionLevel; \ |
michael@0 | 58 | } \ |
michael@0 | 59 | } while(0) |
michael@0 | 60 | |
michael@0 | 61 | #else |
michael@0 | 62 | |
michael@0 | 63 | #define INCREMENT_RECURSION_LEVEL(table_) do { } while(0) |
michael@0 | 64 | #define DECREMENT_RECURSION_LEVEL(table_) do { } while(0) |
michael@0 | 65 | |
michael@0 | 66 | #endif /* defined(DEBUG) */ |
michael@0 | 67 | |
michael@0 | 68 | using namespace mozilla; |
michael@0 | 69 | |
michael@0 | 70 | void * |
michael@0 | 71 | PL_DHashAllocTable(PLDHashTable *table, uint32_t nbytes) |
michael@0 | 72 | { |
michael@0 | 73 | return malloc(nbytes); |
michael@0 | 74 | } |
michael@0 | 75 | |
michael@0 | 76 | void |
michael@0 | 77 | PL_DHashFreeTable(PLDHashTable *table, void *ptr) |
michael@0 | 78 | { |
michael@0 | 79 | free(ptr); |
michael@0 | 80 | } |
michael@0 | 81 | |
michael@0 | 82 | PLDHashNumber |
michael@0 | 83 | PL_DHashStringKey(PLDHashTable *table, const void *key) |
michael@0 | 84 | { |
michael@0 | 85 | return HashString(static_cast<const char*>(key)); |
michael@0 | 86 | } |
michael@0 | 87 | |
michael@0 | 88 | PLDHashNumber |
michael@0 | 89 | PL_DHashVoidPtrKeyStub(PLDHashTable *table, const void *key) |
michael@0 | 90 | { |
michael@0 | 91 | return (PLDHashNumber)(ptrdiff_t)key >> 2; |
michael@0 | 92 | } |
michael@0 | 93 | |
michael@0 | 94 | bool |
michael@0 | 95 | PL_DHashMatchEntryStub(PLDHashTable *table, |
michael@0 | 96 | const PLDHashEntryHdr *entry, |
michael@0 | 97 | const void *key) |
michael@0 | 98 | { |
michael@0 | 99 | const PLDHashEntryStub *stub = (const PLDHashEntryStub *)entry; |
michael@0 | 100 | |
michael@0 | 101 | return stub->key == key; |
michael@0 | 102 | } |
michael@0 | 103 | |
michael@0 | 104 | bool |
michael@0 | 105 | PL_DHashMatchStringKey(PLDHashTable *table, |
michael@0 | 106 | const PLDHashEntryHdr *entry, |
michael@0 | 107 | const void *key) |
michael@0 | 108 | { |
michael@0 | 109 | const PLDHashEntryStub *stub = (const PLDHashEntryStub *)entry; |
michael@0 | 110 | |
michael@0 | 111 | /* XXX tolerate null keys on account of sloppy Mozilla callers. */ |
michael@0 | 112 | return stub->key == key || |
michael@0 | 113 | (stub->key && key && |
michael@0 | 114 | strcmp((const char *) stub->key, (const char *) key) == 0); |
michael@0 | 115 | } |
michael@0 | 116 | |
michael@0 | 117 | void |
michael@0 | 118 | PL_DHashMoveEntryStub(PLDHashTable *table, |
michael@0 | 119 | const PLDHashEntryHdr *from, |
michael@0 | 120 | PLDHashEntryHdr *to) |
michael@0 | 121 | { |
michael@0 | 122 | memcpy(to, from, table->entrySize); |
michael@0 | 123 | } |
michael@0 | 124 | |
michael@0 | 125 | void |
michael@0 | 126 | PL_DHashClearEntryStub(PLDHashTable *table, PLDHashEntryHdr *entry) |
michael@0 | 127 | { |
michael@0 | 128 | memset(entry, 0, table->entrySize); |
michael@0 | 129 | } |
michael@0 | 130 | |
michael@0 | 131 | void |
michael@0 | 132 | PL_DHashFreeStringKey(PLDHashTable *table, PLDHashEntryHdr *entry) |
michael@0 | 133 | { |
michael@0 | 134 | const PLDHashEntryStub *stub = (const PLDHashEntryStub *)entry; |
michael@0 | 135 | |
michael@0 | 136 | free((void *) stub->key); |
michael@0 | 137 | memset(entry, 0, table->entrySize); |
michael@0 | 138 | } |
michael@0 | 139 | |
michael@0 | 140 | void |
michael@0 | 141 | PL_DHashFinalizeStub(PLDHashTable *table) |
michael@0 | 142 | { |
michael@0 | 143 | } |
michael@0 | 144 | |
michael@0 | 145 | static const PLDHashTableOps stub_ops = { |
michael@0 | 146 | PL_DHashAllocTable, |
michael@0 | 147 | PL_DHashFreeTable, |
michael@0 | 148 | PL_DHashVoidPtrKeyStub, |
michael@0 | 149 | PL_DHashMatchEntryStub, |
michael@0 | 150 | PL_DHashMoveEntryStub, |
michael@0 | 151 | PL_DHashClearEntryStub, |
michael@0 | 152 | PL_DHashFinalizeStub, |
michael@0 | 153 | nullptr |
michael@0 | 154 | }; |
michael@0 | 155 | |
michael@0 | 156 | const PLDHashTableOps * |
michael@0 | 157 | PL_DHashGetStubOps(void) |
michael@0 | 158 | { |
michael@0 | 159 | return &stub_ops; |
michael@0 | 160 | } |
michael@0 | 161 | |
michael@0 | 162 | static bool |
michael@0 | 163 | SizeOfEntryStore(uint32_t capacity, uint32_t entrySize, uint32_t *nbytes) |
michael@0 | 164 | { |
michael@0 | 165 | uint64_t nbytes64 = uint64_t(capacity) * uint64_t(entrySize); |
michael@0 | 166 | *nbytes = capacity * entrySize; |
michael@0 | 167 | return uint64_t(*nbytes) == nbytes64; // returns false on overflow |
michael@0 | 168 | } |
michael@0 | 169 | |
michael@0 | 170 | PLDHashTable * |
michael@0 | 171 | PL_NewDHashTable(const PLDHashTableOps *ops, void *data, uint32_t entrySize, |
michael@0 | 172 | uint32_t capacity) |
michael@0 | 173 | { |
michael@0 | 174 | PLDHashTable *table = (PLDHashTable *) malloc(sizeof *table); |
michael@0 | 175 | if (!table) |
michael@0 | 176 | return nullptr; |
michael@0 | 177 | if (!PL_DHashTableInit(table, ops, data, entrySize, capacity, fallible_t())) { |
michael@0 | 178 | free(table); |
michael@0 | 179 | return nullptr; |
michael@0 | 180 | } |
michael@0 | 181 | return table; |
michael@0 | 182 | } |
michael@0 | 183 | |
michael@0 | 184 | void |
michael@0 | 185 | PL_DHashTableDestroy(PLDHashTable *table) |
michael@0 | 186 | { |
michael@0 | 187 | PL_DHashTableFinish(table); |
michael@0 | 188 | free(table); |
michael@0 | 189 | } |
michael@0 | 190 | |
michael@0 | 191 | bool |
michael@0 | 192 | PL_DHashTableInit(PLDHashTable *table, const PLDHashTableOps *ops, |
michael@0 | 193 | void *data, uint32_t entrySize, uint32_t capacity, |
michael@0 | 194 | const fallible_t& ) |
michael@0 | 195 | { |
michael@0 | 196 | #ifdef DEBUG |
michael@0 | 197 | if (entrySize > 16 * sizeof(void *)) { |
michael@0 | 198 | printf_stderr( |
michael@0 | 199 | "pldhash: for the table at address %p, the given entrySize" |
michael@0 | 200 | " of %lu definitely favors chaining over double hashing.\n", |
michael@0 | 201 | (void *) table, |
michael@0 | 202 | (unsigned long) entrySize); |
michael@0 | 203 | } |
michael@0 | 204 | #endif |
michael@0 | 205 | |
michael@0 | 206 | table->ops = ops; |
michael@0 | 207 | table->data = data; |
michael@0 | 208 | if (capacity < PL_DHASH_MIN_SIZE) |
michael@0 | 209 | capacity = PL_DHASH_MIN_SIZE; |
michael@0 | 210 | |
michael@0 | 211 | int log2 = CeilingLog2(capacity); |
michael@0 | 212 | |
michael@0 | 213 | capacity = 1u << log2; |
michael@0 | 214 | if (capacity > PL_DHASH_MAX_SIZE) |
michael@0 | 215 | return false; |
michael@0 | 216 | table->hashShift = PL_DHASH_BITS - log2; |
michael@0 | 217 | table->entrySize = entrySize; |
michael@0 | 218 | table->entryCount = table->removedCount = 0; |
michael@0 | 219 | table->generation = 0; |
michael@0 | 220 | uint32_t nbytes; |
michael@0 | 221 | if (!SizeOfEntryStore(capacity, entrySize, &nbytes)) |
michael@0 | 222 | return false; // overflowed |
michael@0 | 223 | |
michael@0 | 224 | table->entryStore = (char *) ops->allocTable(table, nbytes); |
michael@0 | 225 | if (!table->entryStore) |
michael@0 | 226 | return false; |
michael@0 | 227 | memset(table->entryStore, 0, nbytes); |
michael@0 | 228 | METER(memset(&table->stats, 0, sizeof table->stats)); |
michael@0 | 229 | |
michael@0 | 230 | #ifdef DEBUG |
michael@0 | 231 | table->recursionLevel = 0; |
michael@0 | 232 | #endif |
michael@0 | 233 | |
michael@0 | 234 | return true; |
michael@0 | 235 | } |
michael@0 | 236 | |
michael@0 | 237 | void |
michael@0 | 238 | PL_DHashTableInit(PLDHashTable *table, const PLDHashTableOps *ops, void *data, |
michael@0 | 239 | uint32_t entrySize, uint32_t capacity) |
michael@0 | 240 | { |
michael@0 | 241 | if (!PL_DHashTableInit(table, ops, data, entrySize, capacity, fallible_t())) { |
michael@0 | 242 | if (capacity > PL_DHASH_MAX_SIZE) { |
michael@0 | 243 | MOZ_CRASH(); |
michael@0 | 244 | } |
michael@0 | 245 | uint32_t nbytes; |
michael@0 | 246 | if (!SizeOfEntryStore(capacity, entrySize, &nbytes)) { |
michael@0 | 247 | MOZ_CRASH(); |
michael@0 | 248 | } |
michael@0 | 249 | NS_ABORT_OOM(nbytes); |
michael@0 | 250 | } |
michael@0 | 251 | } |
michael@0 | 252 | |
michael@0 | 253 | /* |
michael@0 | 254 | * Compute max and min load numbers (entry counts). We have a secondary max |
michael@0 | 255 | * that allows us to overload a table reasonably if it cannot be grown further |
michael@0 | 256 | * (i.e. if ChangeTable() fails). The table slows down drastically if the |
michael@0 | 257 | * secondary max is too close to 1, but 0.96875 gives only a slight slowdown |
michael@0 | 258 | * while allowing 1.3x more elements. |
michael@0 | 259 | */ |
michael@0 | 260 | static inline uint32_t MaxLoad(uint32_t size) { |
michael@0 | 261 | return size - (size >> 2); // == size * 0.75 |
michael@0 | 262 | } |
michael@0 | 263 | static inline uint32_t MaxLoadOnGrowthFailure(uint32_t size) { |
michael@0 | 264 | return size - (size >> 5); // == size * 0.96875 |
michael@0 | 265 | } |
michael@0 | 266 | static inline uint32_t MinLoad(uint32_t size) { |
michael@0 | 267 | return size >> 2; // == size * 0.25 |
michael@0 | 268 | } |
michael@0 | 269 | |
michael@0 | 270 | /* |
michael@0 | 271 | * Double hashing needs the second hash code to be relatively prime to table |
michael@0 | 272 | * size, so we simply make hash2 odd. |
michael@0 | 273 | */ |
michael@0 | 274 | #define HASH1(hash0, shift) ((hash0) >> (shift)) |
michael@0 | 275 | #define HASH2(hash0,log2,shift) ((((hash0) << (log2)) >> (shift)) | 1) |
michael@0 | 276 | |
michael@0 | 277 | /* |
michael@0 | 278 | * Reserve keyHash 0 for free entries and 1 for removed-entry sentinels. Note |
michael@0 | 279 | * that a removed-entry sentinel need be stored only if the removed entry had |
michael@0 | 280 | * a colliding entry added after it. Therefore we can use 1 as the collision |
michael@0 | 281 | * flag in addition to the removed-entry sentinel value. Multiplicative hash |
michael@0 | 282 | * uses the high order bits of keyHash, so this least-significant reservation |
michael@0 | 283 | * should not hurt the hash function's effectiveness much. |
michael@0 | 284 | * |
michael@0 | 285 | * If you change any of these magic numbers, also update PL_DHASH_ENTRY_IS_LIVE |
michael@0 | 286 | * in pldhash.h. It used to be private to pldhash.c, but then became public to |
michael@0 | 287 | * assist iterator writers who inspect table->entryStore directly. |
michael@0 | 288 | */ |
michael@0 | 289 | #define COLLISION_FLAG ((PLDHashNumber) 1) |
michael@0 | 290 | #define MARK_ENTRY_FREE(entry) ((entry)->keyHash = 0) |
michael@0 | 291 | #define MARK_ENTRY_REMOVED(entry) ((entry)->keyHash = 1) |
michael@0 | 292 | #define ENTRY_IS_REMOVED(entry) ((entry)->keyHash == 1) |
michael@0 | 293 | #define ENTRY_IS_LIVE(entry) PL_DHASH_ENTRY_IS_LIVE(entry) |
michael@0 | 294 | #define ENSURE_LIVE_KEYHASH(hash0) if (hash0 < 2) hash0 -= 2; else (void)0 |
michael@0 | 295 | |
michael@0 | 296 | /* Match an entry's keyHash against an unstored one computed from a key. */ |
michael@0 | 297 | #define MATCH_ENTRY_KEYHASH(entry,hash0) \ |
michael@0 | 298 | (((entry)->keyHash & ~COLLISION_FLAG) == (hash0)) |
michael@0 | 299 | |
michael@0 | 300 | /* Compute the address of the indexed entry in table. */ |
michael@0 | 301 | #define ADDRESS_ENTRY(table, index) \ |
michael@0 | 302 | ((PLDHashEntryHdr *)((table)->entryStore + (index) * (table)->entrySize)) |
michael@0 | 303 | |
michael@0 | 304 | void |
michael@0 | 305 | PL_DHashTableFinish(PLDHashTable *table) |
michael@0 | 306 | { |
michael@0 | 307 | INCREMENT_RECURSION_LEVEL(table); |
michael@0 | 308 | |
michael@0 | 309 | /* Call finalize before clearing entries, so it can enumerate them. */ |
michael@0 | 310 | table->ops->finalize(table); |
michael@0 | 311 | |
michael@0 | 312 | /* Clear any remaining live entries. */ |
michael@0 | 313 | char *entryAddr = table->entryStore; |
michael@0 | 314 | uint32_t entrySize = table->entrySize; |
michael@0 | 315 | char *entryLimit = entryAddr + PL_DHASH_TABLE_SIZE(table) * entrySize; |
michael@0 | 316 | while (entryAddr < entryLimit) { |
michael@0 | 317 | PLDHashEntryHdr *entry = (PLDHashEntryHdr *)entryAddr; |
michael@0 | 318 | if (ENTRY_IS_LIVE(entry)) { |
michael@0 | 319 | METER(table->stats.removeEnums++); |
michael@0 | 320 | table->ops->clearEntry(table, entry); |
michael@0 | 321 | } |
michael@0 | 322 | entryAddr += entrySize; |
michael@0 | 323 | } |
michael@0 | 324 | |
michael@0 | 325 | DECREMENT_RECURSION_LEVEL(table); |
michael@0 | 326 | MOZ_ASSERT(RECURSION_LEVEL_SAFE_TO_FINISH(table)); |
michael@0 | 327 | |
michael@0 | 328 | /* Free entry storage last. */ |
michael@0 | 329 | table->ops->freeTable(table, table->entryStore); |
michael@0 | 330 | } |
michael@0 | 331 | |
michael@0 | 332 | static PLDHashEntryHdr * PL_DHASH_FASTCALL |
michael@0 | 333 | SearchTable(PLDHashTable *table, const void *key, PLDHashNumber keyHash, |
michael@0 | 334 | PLDHashOperator op) |
michael@0 | 335 | { |
michael@0 | 336 | METER(table->stats.searches++); |
michael@0 | 337 | NS_ASSERTION(!(keyHash & COLLISION_FLAG), |
michael@0 | 338 | "!(keyHash & COLLISION_FLAG)"); |
michael@0 | 339 | |
michael@0 | 340 | /* Compute the primary hash address. */ |
michael@0 | 341 | int hashShift = table->hashShift; |
michael@0 | 342 | PLDHashNumber hash1 = HASH1(keyHash, hashShift); |
michael@0 | 343 | PLDHashEntryHdr *entry = ADDRESS_ENTRY(table, hash1); |
michael@0 | 344 | |
michael@0 | 345 | /* Miss: return space for a new entry. */ |
michael@0 | 346 | if (PL_DHASH_ENTRY_IS_FREE(entry)) { |
michael@0 | 347 | METER(table->stats.misses++); |
michael@0 | 348 | return entry; |
michael@0 | 349 | } |
michael@0 | 350 | |
michael@0 | 351 | /* Hit: return entry. */ |
michael@0 | 352 | PLDHashMatchEntry matchEntry = table->ops->matchEntry; |
michael@0 | 353 | if (MATCH_ENTRY_KEYHASH(entry, keyHash) && matchEntry(table, entry, key)) { |
michael@0 | 354 | METER(table->stats.hits++); |
michael@0 | 355 | return entry; |
michael@0 | 356 | } |
michael@0 | 357 | |
michael@0 | 358 | /* Collision: double hash. */ |
michael@0 | 359 | int sizeLog2 = PL_DHASH_BITS - table->hashShift; |
michael@0 | 360 | PLDHashNumber hash2 = HASH2(keyHash, sizeLog2, hashShift); |
michael@0 | 361 | uint32_t sizeMask = (1u << sizeLog2) - 1; |
michael@0 | 362 | |
michael@0 | 363 | /* Save the first removed entry pointer so PL_DHASH_ADD can recycle it. */ |
michael@0 | 364 | PLDHashEntryHdr *firstRemoved = nullptr; |
michael@0 | 365 | |
michael@0 | 366 | for (;;) { |
michael@0 | 367 | if (MOZ_UNLIKELY(ENTRY_IS_REMOVED(entry))) { |
michael@0 | 368 | if (!firstRemoved) |
michael@0 | 369 | firstRemoved = entry; |
michael@0 | 370 | } else { |
michael@0 | 371 | if (op == PL_DHASH_ADD) |
michael@0 | 372 | entry->keyHash |= COLLISION_FLAG; |
michael@0 | 373 | } |
michael@0 | 374 | |
michael@0 | 375 | METER(table->stats.steps++); |
michael@0 | 376 | hash1 -= hash2; |
michael@0 | 377 | hash1 &= sizeMask; |
michael@0 | 378 | |
michael@0 | 379 | entry = ADDRESS_ENTRY(table, hash1); |
michael@0 | 380 | if (PL_DHASH_ENTRY_IS_FREE(entry)) { |
michael@0 | 381 | METER(table->stats.misses++); |
michael@0 | 382 | return (firstRemoved && op == PL_DHASH_ADD) ? firstRemoved : entry; |
michael@0 | 383 | } |
michael@0 | 384 | |
michael@0 | 385 | if (MATCH_ENTRY_KEYHASH(entry, keyHash) && |
michael@0 | 386 | matchEntry(table, entry, key)) { |
michael@0 | 387 | METER(table->stats.hits++); |
michael@0 | 388 | return entry; |
michael@0 | 389 | } |
michael@0 | 390 | } |
michael@0 | 391 | |
michael@0 | 392 | /* NOTREACHED */ |
michael@0 | 393 | return nullptr; |
michael@0 | 394 | } |
michael@0 | 395 | |
michael@0 | 396 | /* |
michael@0 | 397 | * This is a copy of SearchTable, used by ChangeTable, hardcoded to |
michael@0 | 398 | * 1. assume |op == PL_DHASH_ADD|, |
michael@0 | 399 | * 2. assume that |key| will never match an existing entry, and |
michael@0 | 400 | * 3. assume that no entries have been removed from the current table |
michael@0 | 401 | * structure. |
michael@0 | 402 | * Avoiding the need for |key| means we can avoid needing a way to map |
michael@0 | 403 | * entries to keys, which means callers can use complex key types more |
michael@0 | 404 | * easily. |
michael@0 | 405 | */ |
michael@0 | 406 | static PLDHashEntryHdr * PL_DHASH_FASTCALL |
michael@0 | 407 | FindFreeEntry(PLDHashTable *table, PLDHashNumber keyHash) |
michael@0 | 408 | { |
michael@0 | 409 | METER(table->stats.searches++); |
michael@0 | 410 | NS_ASSERTION(!(keyHash & COLLISION_FLAG), |
michael@0 | 411 | "!(keyHash & COLLISION_FLAG)"); |
michael@0 | 412 | |
michael@0 | 413 | /* Compute the primary hash address. */ |
michael@0 | 414 | int hashShift = table->hashShift; |
michael@0 | 415 | PLDHashNumber hash1 = HASH1(keyHash, hashShift); |
michael@0 | 416 | PLDHashEntryHdr *entry = ADDRESS_ENTRY(table, hash1); |
michael@0 | 417 | |
michael@0 | 418 | /* Miss: return space for a new entry. */ |
michael@0 | 419 | if (PL_DHASH_ENTRY_IS_FREE(entry)) { |
michael@0 | 420 | METER(table->stats.misses++); |
michael@0 | 421 | return entry; |
michael@0 | 422 | } |
michael@0 | 423 | |
michael@0 | 424 | /* Collision: double hash. */ |
michael@0 | 425 | int sizeLog2 = PL_DHASH_BITS - table->hashShift; |
michael@0 | 426 | PLDHashNumber hash2 = HASH2(keyHash, sizeLog2, hashShift); |
michael@0 | 427 | uint32_t sizeMask = (1u << sizeLog2) - 1; |
michael@0 | 428 | |
michael@0 | 429 | for (;;) { |
michael@0 | 430 | NS_ASSERTION(!ENTRY_IS_REMOVED(entry), |
michael@0 | 431 | "!ENTRY_IS_REMOVED(entry)"); |
michael@0 | 432 | entry->keyHash |= COLLISION_FLAG; |
michael@0 | 433 | |
michael@0 | 434 | METER(table->stats.steps++); |
michael@0 | 435 | hash1 -= hash2; |
michael@0 | 436 | hash1 &= sizeMask; |
michael@0 | 437 | |
michael@0 | 438 | entry = ADDRESS_ENTRY(table, hash1); |
michael@0 | 439 | if (PL_DHASH_ENTRY_IS_FREE(entry)) { |
michael@0 | 440 | METER(table->stats.misses++); |
michael@0 | 441 | return entry; |
michael@0 | 442 | } |
michael@0 | 443 | } |
michael@0 | 444 | |
michael@0 | 445 | /* NOTREACHED */ |
michael@0 | 446 | return nullptr; |
michael@0 | 447 | } |
michael@0 | 448 | |
michael@0 | 449 | static bool |
michael@0 | 450 | ChangeTable(PLDHashTable *table, int deltaLog2) |
michael@0 | 451 | { |
michael@0 | 452 | /* Look, but don't touch, until we succeed in getting new entry store. */ |
michael@0 | 453 | int oldLog2 = PL_DHASH_BITS - table->hashShift; |
michael@0 | 454 | int newLog2 = oldLog2 + deltaLog2; |
michael@0 | 455 | uint32_t newCapacity = 1u << newLog2; |
michael@0 | 456 | if (newCapacity > PL_DHASH_MAX_SIZE) |
michael@0 | 457 | return false; |
michael@0 | 458 | |
michael@0 | 459 | uint32_t entrySize = table->entrySize; |
michael@0 | 460 | uint32_t nbytes; |
michael@0 | 461 | if (!SizeOfEntryStore(newCapacity, entrySize, &nbytes)) |
michael@0 | 462 | return false; // overflowed |
michael@0 | 463 | |
michael@0 | 464 | char *newEntryStore = (char *) table->ops->allocTable(table, nbytes); |
michael@0 | 465 | if (!newEntryStore) |
michael@0 | 466 | return false; |
michael@0 | 467 | |
michael@0 | 468 | /* We can't fail from here on, so update table parameters. */ |
michael@0 | 469 | #ifdef DEBUG |
michael@0 | 470 | uint32_t recursionLevel = table->recursionLevel; |
michael@0 | 471 | #endif |
michael@0 | 472 | table->hashShift = PL_DHASH_BITS - newLog2; |
michael@0 | 473 | table->removedCount = 0; |
michael@0 | 474 | table->generation++; |
michael@0 | 475 | |
michael@0 | 476 | /* Assign the new entry store to table. */ |
michael@0 | 477 | memset(newEntryStore, 0, nbytes); |
michael@0 | 478 | char *oldEntryStore, *oldEntryAddr; |
michael@0 | 479 | oldEntryAddr = oldEntryStore = table->entryStore; |
michael@0 | 480 | table->entryStore = newEntryStore; |
michael@0 | 481 | PLDHashMoveEntry moveEntry = table->ops->moveEntry; |
michael@0 | 482 | #ifdef DEBUG |
michael@0 | 483 | table->recursionLevel = recursionLevel; |
michael@0 | 484 | #endif |
michael@0 | 485 | |
michael@0 | 486 | /* Copy only live entries, leaving removed ones behind. */ |
michael@0 | 487 | uint32_t oldCapacity = 1u << oldLog2; |
michael@0 | 488 | for (uint32_t i = 0; i < oldCapacity; i++) { |
michael@0 | 489 | PLDHashEntryHdr *oldEntry = (PLDHashEntryHdr *)oldEntryAddr; |
michael@0 | 490 | if (ENTRY_IS_LIVE(oldEntry)) { |
michael@0 | 491 | oldEntry->keyHash &= ~COLLISION_FLAG; |
michael@0 | 492 | PLDHashEntryHdr *newEntry = FindFreeEntry(table, oldEntry->keyHash); |
michael@0 | 493 | NS_ASSERTION(PL_DHASH_ENTRY_IS_FREE(newEntry), |
michael@0 | 494 | "PL_DHASH_ENTRY_IS_FREE(newEntry)"); |
michael@0 | 495 | moveEntry(table, oldEntry, newEntry); |
michael@0 | 496 | newEntry->keyHash = oldEntry->keyHash; |
michael@0 | 497 | } |
michael@0 | 498 | oldEntryAddr += entrySize; |
michael@0 | 499 | } |
michael@0 | 500 | |
michael@0 | 501 | table->ops->freeTable(table, oldEntryStore); |
michael@0 | 502 | return true; |
michael@0 | 503 | } |
michael@0 | 504 | |
michael@0 | 505 | PLDHashEntryHdr * PL_DHASH_FASTCALL |
michael@0 | 506 | PL_DHashTableOperate(PLDHashTable *table, const void *key, PLDHashOperator op) |
michael@0 | 507 | { |
michael@0 | 508 | PLDHashEntryHdr *entry; |
michael@0 | 509 | |
michael@0 | 510 | MOZ_ASSERT(op == PL_DHASH_LOOKUP || table->recursionLevel == 0); |
michael@0 | 511 | INCREMENT_RECURSION_LEVEL(table); |
michael@0 | 512 | |
michael@0 | 513 | PLDHashNumber keyHash = table->ops->hashKey(table, key); |
michael@0 | 514 | keyHash *= PL_DHASH_GOLDEN_RATIO; |
michael@0 | 515 | |
michael@0 | 516 | /* Avoid 0 and 1 hash codes, they indicate free and removed entries. */ |
michael@0 | 517 | ENSURE_LIVE_KEYHASH(keyHash); |
michael@0 | 518 | keyHash &= ~COLLISION_FLAG; |
michael@0 | 519 | |
michael@0 | 520 | switch (op) { |
michael@0 | 521 | case PL_DHASH_LOOKUP: |
michael@0 | 522 | METER(table->stats.lookups++); |
michael@0 | 523 | entry = SearchTable(table, key, keyHash, op); |
michael@0 | 524 | break; |
michael@0 | 525 | |
michael@0 | 526 | case PL_DHASH_ADD: { |
michael@0 | 527 | /* |
michael@0 | 528 | * If alpha is >= .75, grow or compress the table. If key is already |
michael@0 | 529 | * in the table, we may grow once more than necessary, but only if we |
michael@0 | 530 | * are on the edge of being overloaded. |
michael@0 | 531 | */ |
michael@0 | 532 | uint32_t size = PL_DHASH_TABLE_SIZE(table); |
michael@0 | 533 | if (table->entryCount + table->removedCount >= MaxLoad(size)) { |
michael@0 | 534 | /* Compress if a quarter or more of all entries are removed. */ |
michael@0 | 535 | int deltaLog2; |
michael@0 | 536 | if (table->removedCount >= size >> 2) { |
michael@0 | 537 | METER(table->stats.compresses++); |
michael@0 | 538 | deltaLog2 = 0; |
michael@0 | 539 | } else { |
michael@0 | 540 | METER(table->stats.grows++); |
michael@0 | 541 | deltaLog2 = 1; |
michael@0 | 542 | } |
michael@0 | 543 | |
michael@0 | 544 | /* |
michael@0 | 545 | * Grow or compress table. If ChangeTable() fails, allow |
michael@0 | 546 | * overloading up to the secondary max. Once we hit the secondary |
michael@0 | 547 | * max, return null. |
michael@0 | 548 | */ |
michael@0 | 549 | if (!ChangeTable(table, deltaLog2) && |
michael@0 | 550 | table->entryCount + table->removedCount >= |
michael@0 | 551 | MaxLoadOnGrowthFailure(size)) |
michael@0 | 552 | { |
michael@0 | 553 | METER(table->stats.addFailures++); |
michael@0 | 554 | entry = nullptr; |
michael@0 | 555 | break; |
michael@0 | 556 | } |
michael@0 | 557 | } |
michael@0 | 558 | |
michael@0 | 559 | /* |
michael@0 | 560 | * Look for entry after possibly growing, so we don't have to add it, |
michael@0 | 561 | * then skip it while growing the table and re-add it after. |
michael@0 | 562 | */ |
michael@0 | 563 | entry = SearchTable(table, key, keyHash, op); |
michael@0 | 564 | if (!ENTRY_IS_LIVE(entry)) { |
michael@0 | 565 | /* Initialize the entry, indicating that it's no longer free. */ |
michael@0 | 566 | METER(table->stats.addMisses++); |
michael@0 | 567 | if (ENTRY_IS_REMOVED(entry)) { |
michael@0 | 568 | METER(table->stats.addOverRemoved++); |
michael@0 | 569 | table->removedCount--; |
michael@0 | 570 | keyHash |= COLLISION_FLAG; |
michael@0 | 571 | } |
michael@0 | 572 | if (table->ops->initEntry && |
michael@0 | 573 | !table->ops->initEntry(table, entry, key)) { |
michael@0 | 574 | /* We haven't claimed entry yet; fail with null return. */ |
michael@0 | 575 | memset(entry + 1, 0, table->entrySize - sizeof *entry); |
michael@0 | 576 | entry = nullptr; |
michael@0 | 577 | break; |
michael@0 | 578 | } |
michael@0 | 579 | entry->keyHash = keyHash; |
michael@0 | 580 | table->entryCount++; |
michael@0 | 581 | } |
michael@0 | 582 | METER(else table->stats.addHits++); |
michael@0 | 583 | break; |
michael@0 | 584 | } |
michael@0 | 585 | |
michael@0 | 586 | case PL_DHASH_REMOVE: |
michael@0 | 587 | entry = SearchTable(table, key, keyHash, op); |
michael@0 | 588 | if (ENTRY_IS_LIVE(entry)) { |
michael@0 | 589 | /* Clear this entry and mark it as "removed". */ |
michael@0 | 590 | METER(table->stats.removeHits++); |
michael@0 | 591 | PL_DHashTableRawRemove(table, entry); |
michael@0 | 592 | |
michael@0 | 593 | /* Shrink if alpha is <= .25 and table isn't too small already. */ |
michael@0 | 594 | uint32_t size = PL_DHASH_TABLE_SIZE(table); |
michael@0 | 595 | if (size > PL_DHASH_MIN_SIZE && |
michael@0 | 596 | table->entryCount <= MinLoad(size)) { |
michael@0 | 597 | METER(table->stats.shrinks++); |
michael@0 | 598 | (void) ChangeTable(table, -1); |
michael@0 | 599 | } |
michael@0 | 600 | } |
michael@0 | 601 | METER(else table->stats.removeMisses++); |
michael@0 | 602 | entry = nullptr; |
michael@0 | 603 | break; |
michael@0 | 604 | |
michael@0 | 605 | default: |
michael@0 | 606 | NS_NOTREACHED("0"); |
michael@0 | 607 | entry = nullptr; |
michael@0 | 608 | } |
michael@0 | 609 | |
michael@0 | 610 | DECREMENT_RECURSION_LEVEL(table); |
michael@0 | 611 | |
michael@0 | 612 | return entry; |
michael@0 | 613 | } |
michael@0 | 614 | |
michael@0 | 615 | void |
michael@0 | 616 | PL_DHashTableRawRemove(PLDHashTable *table, PLDHashEntryHdr *entry) |
michael@0 | 617 | { |
michael@0 | 618 | MOZ_ASSERT(table->recursionLevel != IMMUTABLE_RECURSION_LEVEL); |
michael@0 | 619 | |
michael@0 | 620 | NS_ASSERTION(PL_DHASH_ENTRY_IS_LIVE(entry), |
michael@0 | 621 | "PL_DHASH_ENTRY_IS_LIVE(entry)"); |
michael@0 | 622 | |
michael@0 | 623 | /* Load keyHash first in case clearEntry() goofs it. */ |
michael@0 | 624 | PLDHashNumber keyHash = entry->keyHash; |
michael@0 | 625 | table->ops->clearEntry(table, entry); |
michael@0 | 626 | if (keyHash & COLLISION_FLAG) { |
michael@0 | 627 | MARK_ENTRY_REMOVED(entry); |
michael@0 | 628 | table->removedCount++; |
michael@0 | 629 | } else { |
michael@0 | 630 | METER(table->stats.removeFrees++); |
michael@0 | 631 | MARK_ENTRY_FREE(entry); |
michael@0 | 632 | } |
michael@0 | 633 | table->entryCount--; |
michael@0 | 634 | } |
michael@0 | 635 | |
michael@0 | 636 | uint32_t |
michael@0 | 637 | PL_DHashTableEnumerate(PLDHashTable *table, PLDHashEnumerator etor, void *arg) |
michael@0 | 638 | { |
michael@0 | 639 | INCREMENT_RECURSION_LEVEL(table); |
michael@0 | 640 | |
michael@0 | 641 | char *entryAddr = table->entryStore; |
michael@0 | 642 | uint32_t entrySize = table->entrySize; |
michael@0 | 643 | uint32_t capacity = PL_DHASH_TABLE_SIZE(table); |
michael@0 | 644 | uint32_t tableSize = capacity * entrySize; |
michael@0 | 645 | char *entryLimit = entryAddr + tableSize; |
michael@0 | 646 | uint32_t i = 0; |
michael@0 | 647 | bool didRemove = false; |
michael@0 | 648 | |
michael@0 | 649 | if (ChaosMode::isActive()) { |
michael@0 | 650 | // Start iterating at a random point in the hashtable. It would be |
michael@0 | 651 | // even more chaotic to iterate in fully random order, but that's a lot |
michael@0 | 652 | // more work. |
michael@0 | 653 | entryAddr += ChaosMode::randomUint32LessThan(capacity) * entrySize; |
michael@0 | 654 | if (entryAddr >= entryLimit) { |
michael@0 | 655 | entryAddr -= tableSize; |
michael@0 | 656 | } |
michael@0 | 657 | } |
michael@0 | 658 | |
michael@0 | 659 | for (uint32_t e = 0; e < capacity; ++e) { |
michael@0 | 660 | PLDHashEntryHdr *entry = (PLDHashEntryHdr *)entryAddr; |
michael@0 | 661 | if (ENTRY_IS_LIVE(entry)) { |
michael@0 | 662 | PLDHashOperator op = etor(table, entry, i++, arg); |
michael@0 | 663 | if (op & PL_DHASH_REMOVE) { |
michael@0 | 664 | METER(table->stats.removeEnums++); |
michael@0 | 665 | PL_DHashTableRawRemove(table, entry); |
michael@0 | 666 | didRemove = true; |
michael@0 | 667 | } |
michael@0 | 668 | if (op & PL_DHASH_STOP) |
michael@0 | 669 | break; |
michael@0 | 670 | } |
michael@0 | 671 | entryAddr += entrySize; |
michael@0 | 672 | if (entryAddr >= entryLimit) { |
michael@0 | 673 | entryAddr -= tableSize; |
michael@0 | 674 | } |
michael@0 | 675 | } |
michael@0 | 676 | |
michael@0 | 677 | MOZ_ASSERT(!didRemove || table->recursionLevel == 1); |
michael@0 | 678 | |
michael@0 | 679 | /* |
michael@0 | 680 | * Shrink or compress if a quarter or more of all entries are removed, or |
michael@0 | 681 | * if the table is underloaded according to the minimum alpha, and is not |
michael@0 | 682 | * minimal-size already. Do this only if we removed above, so non-removing |
michael@0 | 683 | * enumerations can count on stable table->entryStore until the next |
michael@0 | 684 | * non-lookup-Operate or removing-Enumerate. |
michael@0 | 685 | */ |
michael@0 | 686 | if (didRemove && |
michael@0 | 687 | (table->removedCount >= capacity >> 2 || |
michael@0 | 688 | (capacity > PL_DHASH_MIN_SIZE && |
michael@0 | 689 | table->entryCount <= MinLoad(capacity)))) { |
michael@0 | 690 | METER(table->stats.enumShrinks++); |
michael@0 | 691 | capacity = table->entryCount; |
michael@0 | 692 | capacity += capacity >> 1; |
michael@0 | 693 | if (capacity < PL_DHASH_MIN_SIZE) |
michael@0 | 694 | capacity = PL_DHASH_MIN_SIZE; |
michael@0 | 695 | |
michael@0 | 696 | uint32_t ceiling = CeilingLog2(capacity); |
michael@0 | 697 | ceiling -= PL_DHASH_BITS - table->hashShift; |
michael@0 | 698 | |
michael@0 | 699 | (void) ChangeTable(table, ceiling); |
michael@0 | 700 | } |
michael@0 | 701 | |
michael@0 | 702 | DECREMENT_RECURSION_LEVEL(table); |
michael@0 | 703 | |
michael@0 | 704 | return i; |
michael@0 | 705 | } |
michael@0 | 706 | |
michael@0 | 707 | struct SizeOfEntryExcludingThisArg |
michael@0 | 708 | { |
michael@0 | 709 | size_t total; |
michael@0 | 710 | PLDHashSizeOfEntryExcludingThisFun sizeOfEntryExcludingThis; |
michael@0 | 711 | MallocSizeOf mallocSizeOf; |
michael@0 | 712 | void *arg; // the arg passed by the user |
michael@0 | 713 | }; |
michael@0 | 714 | |
michael@0 | 715 | static PLDHashOperator |
michael@0 | 716 | SizeOfEntryExcludingThisEnumerator(PLDHashTable *table, PLDHashEntryHdr *hdr, |
michael@0 | 717 | uint32_t number, void *arg) |
michael@0 | 718 | { |
michael@0 | 719 | SizeOfEntryExcludingThisArg *e = (SizeOfEntryExcludingThisArg *)arg; |
michael@0 | 720 | e->total += e->sizeOfEntryExcludingThis(hdr, e->mallocSizeOf, e->arg); |
michael@0 | 721 | return PL_DHASH_NEXT; |
michael@0 | 722 | } |
michael@0 | 723 | |
michael@0 | 724 | size_t |
michael@0 | 725 | PL_DHashTableSizeOfExcludingThis(const PLDHashTable *table, |
michael@0 | 726 | PLDHashSizeOfEntryExcludingThisFun sizeOfEntryExcludingThis, |
michael@0 | 727 | MallocSizeOf mallocSizeOf, |
michael@0 | 728 | void *arg /* = nullptr */) |
michael@0 | 729 | { |
michael@0 | 730 | size_t n = 0; |
michael@0 | 731 | n += mallocSizeOf(table->entryStore); |
michael@0 | 732 | if (sizeOfEntryExcludingThis) { |
michael@0 | 733 | SizeOfEntryExcludingThisArg arg2 = { 0, sizeOfEntryExcludingThis, mallocSizeOf, arg }; |
michael@0 | 734 | PL_DHashTableEnumerate(const_cast<PLDHashTable *>(table), |
michael@0 | 735 | SizeOfEntryExcludingThisEnumerator, &arg2); |
michael@0 | 736 | n += arg2.total; |
michael@0 | 737 | } |
michael@0 | 738 | return n; |
michael@0 | 739 | } |
michael@0 | 740 | |
michael@0 | 741 | size_t |
michael@0 | 742 | PL_DHashTableSizeOfIncludingThis(const PLDHashTable *table, |
michael@0 | 743 | PLDHashSizeOfEntryExcludingThisFun sizeOfEntryExcludingThis, |
michael@0 | 744 | MallocSizeOf mallocSizeOf, |
michael@0 | 745 | void *arg /* = nullptr */) |
michael@0 | 746 | { |
michael@0 | 747 | return mallocSizeOf(table) + |
michael@0 | 748 | PL_DHashTableSizeOfExcludingThis(table, sizeOfEntryExcludingThis, |
michael@0 | 749 | mallocSizeOf, arg); |
michael@0 | 750 | } |
michael@0 | 751 | |
michael@0 | 752 | #ifdef DEBUG |
michael@0 | 753 | void |
michael@0 | 754 | PL_DHashMarkTableImmutable(PLDHashTable *table) |
michael@0 | 755 | { |
michael@0 | 756 | table->recursionLevel = IMMUTABLE_RECURSION_LEVEL; |
michael@0 | 757 | } |
michael@0 | 758 | #endif |
michael@0 | 759 | |
michael@0 | 760 | #ifdef PL_DHASHMETER |
michael@0 | 761 | #include <math.h> |
michael@0 | 762 | |
michael@0 | 763 | void |
michael@0 | 764 | PL_DHashTableDumpMeter(PLDHashTable *table, PLDHashEnumerator dump, FILE *fp) |
michael@0 | 765 | { |
michael@0 | 766 | PLDHashNumber hash1, hash2, maxChainHash1, maxChainHash2; |
michael@0 | 767 | double sqsum, mean, variance, sigma; |
michael@0 | 768 | PLDHashEntryHdr *entry; |
michael@0 | 769 | |
michael@0 | 770 | char *entryAddr = table->entryStore; |
michael@0 | 771 | uint32_t entrySize = table->entrySize; |
michael@0 | 772 | int hashShift = table->hashShift; |
michael@0 | 773 | int sizeLog2 = PL_DHASH_BITS - hashShift; |
michael@0 | 774 | uint32_t tableSize = PL_DHASH_TABLE_SIZE(table); |
michael@0 | 775 | uint32_t sizeMask = (1u << sizeLog2) - 1; |
michael@0 | 776 | uint32_t chainCount = 0, maxChainLen = 0; |
michael@0 | 777 | hash2 = 0; |
michael@0 | 778 | sqsum = 0; |
michael@0 | 779 | |
michael@0 | 780 | for (uint32_t i = 0; i < tableSize; i++) { |
michael@0 | 781 | entry = (PLDHashEntryHdr *)entryAddr; |
michael@0 | 782 | entryAddr += entrySize; |
michael@0 | 783 | if (!ENTRY_IS_LIVE(entry)) |
michael@0 | 784 | continue; |
michael@0 | 785 | hash1 = HASH1(entry->keyHash & ~COLLISION_FLAG, hashShift); |
michael@0 | 786 | PLDHashNumber saveHash1 = hash1; |
michael@0 | 787 | PLDHashEntryHdr *probe = ADDRESS_ENTRY(table, hash1); |
michael@0 | 788 | uint32_t chainLen = 1; |
michael@0 | 789 | if (probe == entry) { |
michael@0 | 790 | /* Start of a (possibly unit-length) chain. */ |
michael@0 | 791 | chainCount++; |
michael@0 | 792 | } else { |
michael@0 | 793 | hash2 = HASH2(entry->keyHash & ~COLLISION_FLAG, sizeLog2, |
michael@0 | 794 | hashShift); |
michael@0 | 795 | do { |
michael@0 | 796 | chainLen++; |
michael@0 | 797 | hash1 -= hash2; |
michael@0 | 798 | hash1 &= sizeMask; |
michael@0 | 799 | probe = ADDRESS_ENTRY(table, hash1); |
michael@0 | 800 | } while (probe != entry); |
michael@0 | 801 | } |
michael@0 | 802 | sqsum += chainLen * chainLen; |
michael@0 | 803 | if (chainLen > maxChainLen) { |
michael@0 | 804 | maxChainLen = chainLen; |
michael@0 | 805 | maxChainHash1 = saveHash1; |
michael@0 | 806 | maxChainHash2 = hash2; |
michael@0 | 807 | } |
michael@0 | 808 | } |
michael@0 | 809 | |
michael@0 | 810 | uint32_t entryCount = table->entryCount; |
michael@0 | 811 | if (entryCount && chainCount) { |
michael@0 | 812 | mean = (double)entryCount / chainCount; |
michael@0 | 813 | variance = chainCount * sqsum - entryCount * entryCount; |
michael@0 | 814 | if (variance < 0 || chainCount == 1) |
michael@0 | 815 | variance = 0; |
michael@0 | 816 | else |
michael@0 | 817 | variance /= chainCount * (chainCount - 1); |
michael@0 | 818 | sigma = sqrt(variance); |
michael@0 | 819 | } else { |
michael@0 | 820 | mean = sigma = 0; |
michael@0 | 821 | } |
michael@0 | 822 | |
michael@0 | 823 | fprintf(fp, "Double hashing statistics:\n"); |
michael@0 | 824 | fprintf(fp, " table size (in entries): %u\n", tableSize); |
michael@0 | 825 | fprintf(fp, " number of entries: %u\n", table->entryCount); |
michael@0 | 826 | fprintf(fp, " number of removed entries: %u\n", table->removedCount); |
michael@0 | 827 | fprintf(fp, " number of searches: %u\n", table->stats.searches); |
michael@0 | 828 | fprintf(fp, " number of hits: %u\n", table->stats.hits); |
michael@0 | 829 | fprintf(fp, " number of misses: %u\n", table->stats.misses); |
michael@0 | 830 | fprintf(fp, " mean steps per search: %g\n", table->stats.searches ? |
michael@0 | 831 | (double)table->stats.steps |
michael@0 | 832 | / table->stats.searches : |
michael@0 | 833 | 0.); |
michael@0 | 834 | fprintf(fp, " mean hash chain length: %g\n", mean); |
michael@0 | 835 | fprintf(fp, " standard deviation: %g\n", sigma); |
michael@0 | 836 | fprintf(fp, " maximum hash chain length: %u\n", maxChainLen); |
michael@0 | 837 | fprintf(fp, " number of lookups: %u\n", table->stats.lookups); |
michael@0 | 838 | fprintf(fp, " adds that made a new entry: %u\n", table->stats.addMisses); |
michael@0 | 839 | fprintf(fp, "adds that recycled removeds: %u\n", table->stats.addOverRemoved); |
michael@0 | 840 | fprintf(fp, " adds that found an entry: %u\n", table->stats.addHits); |
michael@0 | 841 | fprintf(fp, " add failures: %u\n", table->stats.addFailures); |
michael@0 | 842 | fprintf(fp, " useful removes: %u\n", table->stats.removeHits); |
michael@0 | 843 | fprintf(fp, " useless removes: %u\n", table->stats.removeMisses); |
michael@0 | 844 | fprintf(fp, "removes that freed an entry: %u\n", table->stats.removeFrees); |
michael@0 | 845 | fprintf(fp, " removes while enumerating: %u\n", table->stats.removeEnums); |
michael@0 | 846 | fprintf(fp, " number of grows: %u\n", table->stats.grows); |
michael@0 | 847 | fprintf(fp, " number of shrinks: %u\n", table->stats.shrinks); |
michael@0 | 848 | fprintf(fp, " number of compresses: %u\n", table->stats.compresses); |
michael@0 | 849 | fprintf(fp, "number of enumerate shrinks: %u\n", table->stats.enumShrinks); |
michael@0 | 850 | |
michael@0 | 851 | if (dump && maxChainLen && hash2) { |
michael@0 | 852 | fputs("Maximum hash chain:\n", fp); |
michael@0 | 853 | hash1 = maxChainHash1; |
michael@0 | 854 | hash2 = maxChainHash2; |
michael@0 | 855 | entry = ADDRESS_ENTRY(table, hash1); |
michael@0 | 856 | uint32_t i = 0; |
michael@0 | 857 | do { |
michael@0 | 858 | if (dump(table, entry, i++, fp) != PL_DHASH_NEXT) |
michael@0 | 859 | break; |
michael@0 | 860 | hash1 -= hash2; |
michael@0 | 861 | hash1 &= sizeMask; |
michael@0 | 862 | entry = ADDRESS_ENTRY(table, hash1); |
michael@0 | 863 | } while (PL_DHASH_ENTRY_IS_BUSY(entry)); |
michael@0 | 864 | } |
michael@0 | 865 | } |
michael@0 | 866 | #endif /* PL_DHASHMETER */ |