| Index: src/core/SkTDynamicHash.h
|
| diff --git a/src/core/SkTDynamicHash.h b/src/core/SkTDynamicHash.h
|
| index b3d4b26476b34ede5c39d9b6d2699da1cd2c4911..21aca07b907174d3d7e3071e64b0b7857be60aa5 100644
|
| --- a/src/core/SkTDynamicHash.h
|
| +++ b/src/core/SkTDynamicHash.h
|
| @@ -9,185 +9,162 @@
|
| #define SkTDynamicHash_DEFINED
|
|
|
| #include "SkTypes.h"
|
| +#include "SkMath.h"
|
|
|
| template <typename T,
|
| - typename KEY,
|
| - const KEY& (KEY_FROM_T)(const T&),
|
| - uint32_t (HASH_FROM_KEY)(const KEY&),
|
| - bool (EQ_T_KEY)(const T&, const KEY&)>
|
| + typename Key,
|
| + const Key& (GetKey)(const T&),
|
| + uint32_t (Hash)(const Key&),
|
| + bool (Equal)(const T&, const Key&)>
|
| class SkTDynamicHash {
|
| -private:
|
| - T* fStorage[4]; // cheap storage for small arrays
|
| - T** fArray;
|
| - int fCapacity; // number of slots in fArray. Must be pow2
|
| - int fCountUsed; // number of valid entries in fArray
|
| - int fCountDeleted; // number of deletedValue() entries in fArray
|
| -
|
| - // Need an illegal ptr value different from NULL (which we use to
|
| - // signal empty/unused.
|
| - const T* deletedValue() const { return reinterpret_cast<const T*>(-1); }
|
| -
|
| - // fCapacity is a pow2, so that minus one is a clean mask to grab
|
| - // the low bits of hash to use as an index.
|
| - uint32_t hashMask() const { return fCapacity - 1; }
|
| -
|
| - int hashToIndex(uint32_t hash) const {
|
| - // this 16bit fold may be overkill, if we trust that hash is good
|
| - return ((hash >> 16) ^ hash) & this->hashMask();
|
| - }
|
| -
|
| public:
|
| - SkTDynamicHash() {
|
| - sk_bzero(fStorage, sizeof(fStorage));
|
| - fArray = fStorage;
|
| - fCapacity = SK_ARRAY_COUNT(fStorage);
|
| - fCountUsed = fCountDeleted = 0;
|
| - }
|
| + SkTDynamicHash(int initialCapacity=64/sizeof(T*))
|
| + : fCount(0)
|
| + , fCapacity(SkNextPow2(initialCapacity > 0 ? initialCapacity : 1))
|
| + , fArray(AllocArray(fCapacity)) {}
|
| +
|
| ~SkTDynamicHash() {
|
| - if (fArray != fStorage) {
|
| - sk_free(fArray);
|
| - }
|
| + sk_free(fArray);
|
| }
|
|
|
| - T* find(const KEY& key) {
|
| - const T* const deleted = this->deletedValue();
|
| - const unsigned mask = this->hashMask();
|
| - int index = this->hashToIndex(HASH_FROM_KEY(key));
|
| - int delta = 1;
|
| + int count() const { return fCount; }
|
|
|
| - do {
|
| + // Return the entry with this key if we have it, otherwise NULL.
|
| + T* find(const Key& key) const {
|
| + int index = this->firstIndex(key);
|
| + for (int round = 0; round < fCapacity; round++) {
|
| T* candidate = fArray[index];
|
| - if (NULL == candidate) {
|
| + if (candidate == Empty()) {
|
| return NULL;
|
| }
|
| - if (deleted != candidate && EQ_T_KEY(*candidate, key)) {
|
| + if (candidate != Deleted() && Equal(*candidate, key)) {
|
| return candidate;
|
| }
|
| - index = (index + delta) & mask;
|
| - delta <<= 1;
|
| - } while (delta <= fCapacity);
|
| + index = this->nextIndex(index, round);
|
| + }
|
| + SkASSERT(!"find: should be unreachable");
|
| return NULL;
|
| }
|
|
|
| - bool add(const KEY& key, T* newElement, bool autoGrow = true) {
|
| - const T* const deleted = this->deletedValue();
|
| - for (;;) {
|
| - const unsigned mask = this->hashMask();
|
| - int index = this->hashToIndex(HASH_FROM_KEY(key));
|
| - int delta = 1;
|
| -
|
| - do {
|
| - const T* candidate = fArray[index];
|
| - if (NULL == candidate || deleted == candidate) {
|
| - fArray[index] = newElement;
|
| - fCountUsed += 1;
|
| - if (deleted == candidate) {
|
| - SkASSERT(fCountDeleted > 0);
|
| - fCountDeleted -= 1;
|
| - }
|
| - return true;
|
| - }
|
| - index = (index + delta) & mask;
|
| - delta <<= 1;
|
| - } while (delta <= fCapacity);
|
| - if (autoGrow) {
|
| - this->grow();
|
| - } else {
|
| - return false;
|
| + // Add an entry with this key.
|
| + void add(T* newEntry) {
|
| + this->maybeGrow();
|
| +
|
| + const Key& key = GetKey(*newEntry);
|
| + int index = this->firstIndex(key);
|
| + for (int round = 0; round < fCapacity; round++) {
|
| + T* candidate = fArray[index];
|
| + if (candidate == Empty() || candidate == Deleted()) {
|
| + fArray[index] = newEntry;
|
| + fCount++;
|
| + return;
|
| + }
|
| + if (Equal(*candidate, key)) {
|
| + fArray[index] = newEntry;
|
| + return;
|
| }
|
| + index = this->nextIndex(index, round);
|
| }
|
| - SkASSERT(!"never get here");
|
| - return false;
|
| + SkASSERT(!"add: should be unreachable");
|
| + }
|
| +
|
| + // Remove entry with this key, if we have it.
|
| + void remove(const Key& key) {
|
| + this->innerRemove(key);
|
| + this->maybeShrink();
|
| }
|
|
|
| - void remove(const KEY& key) {
|
| - const T* const deleted = this->deletedValue();
|
| - const unsigned mask = this->hashMask();
|
| - int index = this->hashToIndex(HASH_FROM_KEY(key));
|
| - int delta = 1;
|
| +protected:
|
| + // These methods are used by tests only.
|
|
|
| - for (;;) {
|
| + int capacity() const { return fCapacity; }
|
| +
|
| + // How many collisions do we go through before finding where this entry should be inserted?
|
| + int countCollisions(const Key& key) const {
|
| + int index = this->firstIndex(key);
|
| + for (int round = 0; round < fCapacity; round++) {
|
| const T* candidate = fArray[index];
|
| - SkASSERT(candidate);
|
| - if (deleted != candidate && EQ_T_KEY(*candidate, key)) {
|
| - fArray[index] = const_cast<T*>(deleted);
|
| - fCountUsed -= 1;
|
| - fCountDeleted += 1;
|
| - break;
|
| + if (candidate == Empty() || candidate == Deleted() || Equal(*candidate, key)) {
|
| + return round;
|
| }
|
| - index = (index + delta) & mask;
|
| - delta <<= 1;
|
| - SkASSERT(delta <= fCapacity);
|
| + index = this->nextIndex(index, round);
|
| }
|
| - this->checkStrink();
|
| + SkASSERT(!"countCollisions: should be unreachable");
|
| + return -1;
|
| }
|
|
|
| private:
|
| - int countCollisions(const KEY& key) const {
|
| - const T* const deleted = this->deletedValue();
|
| - const unsigned mask = this->hashMask();
|
| - int index = this->hashToIndex(HASH_FROM_KEY(key));
|
| - int delta = 1;
|
| - int collisionCount = 0;
|
| -
|
| - for (;;) {
|
| + // We have two special values to indicate an empty or deleted entry.
|
| + static const T* Empty() { return reinterpret_cast<const T*>(0); } // i.e. NULL
|
| + static const T* Deleted() { return reinterpret_cast<const T*>(1); } // Also an invalid pointer.
|
| +
|
| + static T** AllocArray(int capacity) {
|
| + T** array = (T**)sk_malloc_throw(sizeof(T*) * capacity);
|
| + sk_bzero(array, sizeof(T*) * capacity); // All cells == Empty().
|
| + return array;
|
| + }
|
| +
|
| + void innerRemove(const Key& key) {
|
| + int index = this->firstIndex(key);
|
| + for (int round = 0; round < fCapacity; round++) {
|
| const T* candidate = fArray[index];
|
| - SkASSERT(candidate);
|
| - if (deleted != candidate && EQ_T_KEY(*candidate, key)) {
|
| - break;
|
| + if (candidate == Empty()) {
|
| + return;
|
| }
|
| - index = (index + delta) & mask;
|
| - delta <<= 1;
|
| - collisionCount += 1;
|
| - SkASSERT(delta <= fCapacity);
|
| + if (candidate != Deleted() && Equal(*candidate, key)) {
|
| + fArray[index] = const_cast<T*>(Deleted());
|
| + fCount--;
|
| + return;
|
| + }
|
| + index = this->nextIndex(index, round);
|
| }
|
| - return collisionCount;
|
| + SkASSERT(!"innerRemove: should be unreachable");
|
| }
|
|
|
| - void grow() {
|
| - const T* const deleted = this->deletedValue();
|
| -#if 0
|
| - SkDebugf("growing from %d: used=%d\n", fCapacity, fCountUsed);
|
| - for (int i = 0; i < fCapacity; ++i) {
|
| - T* elem = fArray[i];
|
| - if (NULL == elem || deleted == elem) {
|
| - continue;
|
| - }
|
| - SkDebugf(" entry[%d] had %d collisions\n", i, countCollisions(KEY_FROM_T(*elem)));
|
| + void maybeGrow() {
|
| + if (fCount < fCapacity / 2) {
|
| + return;
|
| }
|
| -#endif
|
| +
|
| + SkDEBUGCODE(int oldCount = fCount;)
|
| int oldCapacity = fCapacity;
|
| T** oldArray = fArray;
|
|
|
| - int newCapacity = oldCapacity << 1;
|
| - T** newArray = (T**)sk_malloc_throw(sizeof(T*) * newCapacity);
|
| - sk_bzero(newArray, sizeof(T*) * newCapacity);
|
| + fCount = 0;
|
| + fCapacity *= 2;
|
| + fArray = AllocArray(fCapacity);
|
|
|
| - SkDEBUGCODE(int oldCountUsed = fCountUsed;)
|
| - fArray = newArray;
|
| - fCapacity = newCapacity;
|
| - fCountUsed = 0;
|
| - fCountDeleted = 0;
|
| -
|
| - for (int i = 0; i < oldCapacity; ++i) {
|
| - T* elem = oldArray[i];
|
| - if (NULL == elem || deleted == elem) {
|
| - continue;
|
| + for (int i = 0; i < oldCapacity; i++) {
|
| + T* entry = oldArray[i];
|
| + if (entry != Empty() && entry != Deleted()) {
|
| + this->add(entry);
|
| }
|
| - SkDEBUGCODE(bool success =) this->add(KEY_FROM_T(*elem), elem, false);
|
| - SkASSERT(success);
|
| }
|
| - SkASSERT(oldCountUsed == fCountUsed);
|
| + SkASSERT(oldCount == fCount);
|
|
|
| - if (oldArray != fStorage) {
|
| - sk_free(oldArray);
|
| - }
|
| + sk_free(oldArray);
|
| }
|
|
|
| - void checkStrink() {
|
| - // todo: based on density and deadspace (fCountDeleted), consider
|
| - // shrinking fArray and repopulating it.
|
| + void maybeShrink() {
|
| + // TODO
|
| }
|
| +
|
| + // fCapacity is always a power of 2, so this masks the correct low bits to index into our hash.
|
| + uint32_t hashMask() const { return fCapacity - 1; }
|
| +
|
| + int firstIndex(const Key& key) const {
|
| + return Hash(key) & this->hashMask();
|
| + }
|
| +
|
| + // Given index at round N, what is the index to check at N+1? round should start at 0.
|
| + int nextIndex(int index, int round) const {
|
| + // This will search a power-of-two array fully without repeating an index.
|
| + return (index + round + 1) & this->hashMask();
|
| + }
|
| +
|
| + int fCount; // Number of non-empty, non-deleted entries in fArray.
|
| + int fCapacity; // Number of entries in fArray. Always a power of 2.
|
| + T** fArray;
|
| };
|
|
|
| #endif
|
|
|