| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright 2013 Google Inc. | 2 * Copyright 2013 Google Inc. |
| 3 * | 3 * |
| 4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
| 5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
| 6 */ | 6 */ |
| 7 | 7 |
| 8 #ifndef SkTDynamicHash_DEFINED | 8 #ifndef SkTDynamicHash_DEFINED |
| 9 #define SkTDynamicHash_DEFINED | 9 #define SkTDynamicHash_DEFINED |
| 10 | 10 |
| 11 #include "SkTypes.h" | 11 #include "SkTypes.h" |
| 12 #include "SkMath.h" |
| 12 | 13 |
| 13 template <typename T, | 14 template <typename T, |
| 14 typename KEY, | 15 typename Key, |
| 15 const KEY& (KEY_FROM_T)(const T&), | 16 const Key& (GetKey)(const T&), |
| 16 uint32_t (HASH_FROM_KEY)(const KEY&), | 17 uint32_t (Hash)(const Key&), |
| 17 bool (EQ_T_KEY)(const T&, const KEY&)> | 18 bool (Equal)(const T&, const Key&)> |
| 18 class SkTDynamicHash { | 19 class SkTDynamicHash { |
| 19 private: | 20 public: |
| 20 T* fStorage[4]; // cheap storage for small arrays | 21 SkTDynamicHash(int initialCapacity=64/sizeof(T*)) |
| 21 T** fArray; | 22 : fCount(0) |
| 22 int fCapacity; // number of slots in fArray. Must be pow2 | 23 , fCapacity(SkNextPow2(initialCapacity > 0 ? initialCapacity : 1)) |
| 23 int fCountUsed; // number of valid entries in fArray | 24 , fArray(AllocArray(fCapacity)) {} |
| 24 int fCountDeleted; // number of deletedValue() entries in fArray | |
| 25 | 25 |
| 26 // Need an illegal ptr value different from NULL (which we use to | 26 ~SkTDynamicHash() { |
| 27 // signal empty/unused. | 27 sk_free(fArray); |
| 28 const T* deletedValue() const { return reinterpret_cast<const T*>(-1); } | |
| 29 | |
| 30 // fCapacity is a pow2, so that minus one is a clean mask to grab | |
| 31 // the low bits of hash to use as an index. | |
| 32 uint32_t hashMask() const { return fCapacity - 1; } | |
| 33 | |
| 34 int hashToIndex(uint32_t hash) const { | |
| 35 // this 16bit fold may be overkill, if we trust that hash is good | |
| 36 return ((hash >> 16) ^ hash) & this->hashMask(); | |
| 37 } | 28 } |
| 38 | 29 |
| 39 public: | 30 int count() const { return fCount; } |
| 40 SkTDynamicHash() { | |
| 41 sk_bzero(fStorage, sizeof(fStorage)); | |
| 42 fArray = fStorage; | |
| 43 fCapacity = SK_ARRAY_COUNT(fStorage); | |
| 44 fCountUsed = fCountDeleted = 0; | |
| 45 } | |
| 46 ~SkTDynamicHash() { | |
| 47 if (fArray != fStorage) { | |
| 48 sk_free(fArray); | |
| 49 } | |
| 50 } | |
| 51 | 31 |
| 52 T* find(const KEY& key) { | 32 // Return the entry with this key if we have it, otherwise NULL. |
| 53 const T* const deleted = this->deletedValue(); | 33 T* find(const Key& key) const { |
| 54 const unsigned mask = this->hashMask(); | 34 int index = this->firstIndex(key); |
| 55 int index = this->hashToIndex(HASH_FROM_KEY(key)); | 35 for (int round = 0; round < fCapacity; round++) { |
| 56 int delta = 1; | |
| 57 | |
| 58 do { | |
| 59 T* candidate = fArray[index]; | 36 T* candidate = fArray[index]; |
| 60 if (NULL == candidate) { | 37 if (candidate == Empty()) { |
| 61 return NULL; | 38 return NULL; |
| 62 } | 39 } |
| 63 if (deleted != candidate && EQ_T_KEY(*candidate, key)) { | 40 if (candidate != Deleted() && Equal(*candidate, key)) { |
| 64 return candidate; | 41 return candidate; |
| 65 } | 42 } |
| 66 index = (index + delta) & mask; | 43 index = this->nextIndex(index, round); |
| 67 delta <<= 1; | 44 } |
| 68 } while (delta <= fCapacity); | 45 SkASSERT(!"find: should be unreachable"); |
| 69 return NULL; | 46 return NULL; |
| 70 } | 47 } |
| 71 | 48 |
| 72 bool add(const KEY& key, T* newElement, bool autoGrow = true) { | 49 // Add an entry with this key. |
| 73 const T* const deleted = this->deletedValue(); | 50 void add(T* newEntry) { |
| 74 for (;;) { | 51 this->maybeGrow(); |
| 75 const unsigned mask = this->hashMask(); | |
| 76 int index = this->hashToIndex(HASH_FROM_KEY(key)); | |
| 77 int delta = 1; | |
| 78 | 52 |
| 79 do { | 53 const Key& key = GetKey(*newEntry); |
| 80 const T* candidate = fArray[index]; | 54 int index = this->firstIndex(key); |
| 81 if (NULL == candidate || deleted == candidate) { | 55 for (int round = 0; round < fCapacity; round++) { |
| 82 fArray[index] = newElement; | 56 T* candidate = fArray[index]; |
| 83 fCountUsed += 1; | 57 if (candidate == Empty() || candidate == Deleted()) { |
| 84 if (deleted == candidate) { | 58 fArray[index] = newEntry; |
| 85 SkASSERT(fCountDeleted > 0); | 59 fCount++; |
| 86 fCountDeleted -= 1; | 60 return; |
| 87 } | |
| 88 return true; | |
| 89 } | |
| 90 index = (index + delta) & mask; | |
| 91 delta <<= 1; | |
| 92 } while (delta <= fCapacity); | |
| 93 if (autoGrow) { | |
| 94 this->grow(); | |
| 95 } else { | |
| 96 return false; | |
| 97 } | 61 } |
| 62 if (Equal(*candidate, key)) { |
| 63 fArray[index] = newEntry; |
| 64 return; |
| 65 } |
| 66 index = this->nextIndex(index, round); |
| 98 } | 67 } |
| 99 SkASSERT(!"never get here"); | 68 SkASSERT(!"add: should be unreachable"); |
| 100 return false; | |
| 101 } | 69 } |
| 102 | 70 |
| 103 void remove(const KEY& key) { | 71 // Remove entry with this key, if we have it. |
| 104 const T* const deleted = this->deletedValue(); | 72 void remove(const Key& key) { |
| 105 const unsigned mask = this->hashMask(); | 73 this->innerRemove(key); |
| 106 int index = this->hashToIndex(HASH_FROM_KEY(key)); | 74 this->maybeShrink(); |
| 107 int delta = 1; | 75 } |
| 108 | 76 |
| 109 for (;;) { | 77 protected: |
| 78 // These methods are used by tests only. |
| 79 |
| 80 int capacity() const { return fCapacity; } |
| 81 |
| 82 // How many collisions do we go through before finding where this entry shou
ld be inserted? |
| 83 int countCollisions(const Key& key) const { |
| 84 int index = this->firstIndex(key); |
| 85 for (int round = 0; round < fCapacity; round++) { |
| 110 const T* candidate = fArray[index]; | 86 const T* candidate = fArray[index]; |
| 111 SkASSERT(candidate); | 87 if (candidate == Empty() || candidate == Deleted() || Equal(*candida
te, key)) { |
| 112 if (deleted != candidate && EQ_T_KEY(*candidate, key)) { | 88 return round; |
| 113 fArray[index] = const_cast<T*>(deleted); | |
| 114 fCountUsed -= 1; | |
| 115 fCountDeleted += 1; | |
| 116 break; | |
| 117 } | 89 } |
| 118 index = (index + delta) & mask; | 90 index = this->nextIndex(index, round); |
| 119 delta <<= 1; | |
| 120 SkASSERT(delta <= fCapacity); | |
| 121 } | 91 } |
| 122 this->checkStrink(); | 92 SkASSERT(!"countCollisions: should be unreachable"); |
| 93 return -1; |
| 123 } | 94 } |
| 124 | 95 |
| 125 private: | 96 private: |
| 126 int countCollisions(const KEY& key) const { | 97 // We have two special values to indicate an empty or deleted entry. |
| 127 const T* const deleted = this->deletedValue(); | 98 static const T* Empty() { return reinterpret_cast<const T*>(0); } // i.e.
NULL |
| 128 const unsigned mask = this->hashMask(); | 99 static const T* Deleted() { return reinterpret_cast<const T*>(1); } // Also
an invalid pointer. |
| 129 int index = this->hashToIndex(HASH_FROM_KEY(key)); | |
| 130 int delta = 1; | |
| 131 int collisionCount = 0; | |
| 132 | 100 |
| 133 for (;;) { | 101 static T** AllocArray(int capacity) { |
| 134 const T* candidate = fArray[index]; | 102 T** array = (T**)sk_malloc_throw(sizeof(T*) * capacity); |
| 135 SkASSERT(candidate); | 103 sk_bzero(array, sizeof(T*) * capacity); // All cells == Empty(). |
| 136 if (deleted != candidate && EQ_T_KEY(*candidate, key)) { | 104 return array; |
| 137 break; | |
| 138 } | |
| 139 index = (index + delta) & mask; | |
| 140 delta <<= 1; | |
| 141 collisionCount += 1; | |
| 142 SkASSERT(delta <= fCapacity); | |
| 143 } | |
| 144 return collisionCount; | |
| 145 } | 105 } |
| 146 | 106 |
| 147 void grow() { | 107 void innerRemove(const Key& key) { |
| 148 const T* const deleted = this->deletedValue(); | 108 int index = this->firstIndex(key); |
| 149 #if 0 | 109 for (int round = 0; round < fCapacity; round++) { |
| 150 SkDebugf("growing from %d: used=%d\n", fCapacity, fCountUsed); | 110 const T* candidate = fArray[index]; |
| 151 for (int i = 0; i < fCapacity; ++i) { | 111 if (candidate == Empty()) { |
| 152 T* elem = fArray[i]; | 112 return; |
| 153 if (NULL == elem || deleted == elem) { | |
| 154 continue; | |
| 155 } | 113 } |
| 156 SkDebugf(" entry[%d] had %d collisions\n", i, countCollisions(KEY
_FROM_T(*elem))); | 114 if (candidate != Deleted() && Equal(*candidate, key)) { |
| 115 fArray[index] = const_cast<T*>(Deleted()); |
| 116 fCount--; |
| 117 return; |
| 118 } |
| 119 index = this->nextIndex(index, round); |
| 157 } | 120 } |
| 158 #endif | 121 SkASSERT(!"innerRemove: should be unreachable"); |
| 122 } |
| 123 |
| 124 void maybeGrow() { |
| 125 if (fCount < fCapacity / 2) { |
| 126 return; |
| 127 } |
| 128 |
| 129 SkDEBUGCODE(int oldCount = fCount;) |
| 159 int oldCapacity = fCapacity; | 130 int oldCapacity = fCapacity; |
| 160 T** oldArray = fArray; | 131 T** oldArray = fArray; |
| 161 | 132 |
| 162 int newCapacity = oldCapacity << 1; | 133 fCount = 0; |
| 163 T** newArray = (T**)sk_malloc_throw(sizeof(T*) * newCapacity); | 134 fCapacity *= 2; |
| 164 sk_bzero(newArray, sizeof(T*) * newCapacity); | 135 fArray = AllocArray(fCapacity); |
| 165 | 136 |
| 166 SkDEBUGCODE(int oldCountUsed = fCountUsed;) | 137 for (int i = 0; i < oldCapacity; i++) { |
| 167 fArray = newArray; | 138 T* entry = oldArray[i]; |
| 168 fCapacity = newCapacity; | 139 if (entry != Empty() && entry != Deleted()) { |
| 169 fCountUsed = 0; | 140 this->add(entry); |
| 170 fCountDeleted = 0; | 141 } |
| 142 } |
| 143 SkASSERT(oldCount == fCount); |
| 171 | 144 |
| 172 for (int i = 0; i < oldCapacity; ++i) { | 145 sk_free(oldArray); |
| 173 T* elem = oldArray[i]; | |
| 174 if (NULL == elem || deleted == elem) { | |
| 175 continue; | |
| 176 } | |
| 177 SkDEBUGCODE(bool success =) this->add(KEY_FROM_T(*elem), elem, false
); | |
| 178 SkASSERT(success); | |
| 179 } | |
| 180 SkASSERT(oldCountUsed == fCountUsed); | |
| 181 | |
| 182 if (oldArray != fStorage) { | |
| 183 sk_free(oldArray); | |
| 184 } | |
| 185 } | 146 } |
| 186 | 147 |
| 187 void checkStrink() { | 148 void maybeShrink() { |
| 188 // todo: based on density and deadspace (fCountDeleted), consider | 149 // TODO |
| 189 // shrinking fArray and repopulating it. | |
| 190 } | 150 } |
| 151 |
| 152 // fCapacity is always a power of 2, so this masks the correct low bits to i
ndex into our hash. |
| 153 uint32_t hashMask() const { return fCapacity - 1; } |
| 154 |
| 155 int firstIndex(const Key& key) const { |
| 156 return Hash(key) & this->hashMask(); |
| 157 } |
| 158 |
| 159 // Given index at round N, what is the index to check at N+1? round should
start at 0. |
| 160 int nextIndex(int index, int round) const { |
| 161 // This will search a power-of-two array fully without repeating an inde
x. |
| 162 return (index + round + 1) & this->hashMask(); |
| 163 } |
| 164 |
| 165 int fCount; // Number of non-empty, non-deleted entries in fArray. |
| 166 int fCapacity; // Number of entries in fArray. Always a power of 2. |
| 167 T** fArray; |
| 191 }; | 168 }; |
| 192 | 169 |
| 193 #endif | 170 #endif |
| OLD | NEW |