• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2013 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #ifndef SkTDynamicHash_DEFINED
9 #define SkTDynamicHash_DEFINED
10 
11 #include "SkTypes.h"
12 #include "SkMath.h"
13 
14 template <typename T,
15           typename Key,
16           const Key& (GetKey)(const T&),
17           uint32_t (Hash)(const Key&),
18           bool (Equal)(const T&, const Key&),
19           int kGrowPercent   = 75,  // Larger -> more memory efficient, but slower.
20           int kShrinkPercent = 25>
21 class SkTDynamicHash {
22     static const int kMinCapacity = 4;  // Smallest capacity we allow.
23 public:
24     SkTDynamicHash(int initialCapacity=64/sizeof(T*)) {
25         this->reset(SkNextPow2(initialCapacity > kMinCapacity ? initialCapacity : kMinCapacity));
26         SkASSERT(this->validate());
27     }
28 
~SkTDynamicHash()29     ~SkTDynamicHash() {
30         sk_free(fArray);
31     }
32 
count()33     int count() const { return fCount; }
34 
35     // Return the entry with this key if we have it, otherwise NULL.
find(const Key & key)36     T* find(const Key& key) const {
37         int index = this->firstIndex(key);
38         for (int round = 0; round < fCapacity; round++) {
39             T* candidate = fArray[index];
40             if (Empty() == candidate) {
41                 return NULL;
42             }
43             if (Deleted() != candidate && Equal(*candidate, key)) {
44                 return candidate;
45             }
46             index = this->nextIndex(index, round);
47         }
48         SkASSERT(0); //  find: should be unreachable
49         return NULL;
50     }
51 
52     // Add an entry with this key.  We require that no entry with newEntry's key is already present.
add(T * newEntry)53     void add(T* newEntry) {
54         SkASSERT(NULL == this->find(GetKey(*newEntry)));
55         this->maybeGrow();
56         SkASSERT(this->validate());
57         this->innerAdd(newEntry);
58         SkASSERT(this->validate());
59     }
60 
61     // Remove the entry with this key.  We reqire that an entry with this key is present.
remove(const Key & key)62     void remove(const Key& key) {
63         SkASSERT(NULL != this->find(key));
64         this->innerRemove(key);
65         SkASSERT(this->validate());
66         this->maybeShrink();
67         SkASSERT(this->validate());
68     }
69 
70 protected:
71     // These methods are used by tests only.
72 
capacity()73     int capacity() const { return fCapacity; }
74 
75     // How many collisions do we go through before finding where this entry should be inserted?
countCollisions(const Key & key)76     int countCollisions(const Key& key) const {
77         int index = this->firstIndex(key);
78         for (int round = 0; round < fCapacity; round++) {
79             const T* candidate = fArray[index];
80             if (Empty() == candidate || Deleted() == candidate || Equal(*candidate, key)) {
81                 return round;
82             }
83             index = this->nextIndex(index, round);
84         }
85         SkASSERT(0); // countCollisions: should be unreachable
86         return -1;
87     }
88 
89 private:
90     // We have two special values to indicate an empty or deleted entry.
Empty()91     static T* Empty()   { return reinterpret_cast<T*>(0); }  // i.e. NULL
Deleted()92     static T* Deleted() { return reinterpret_cast<T*>(1); }  // Also an invalid pointer.
93 
AllocArray(int capacity)94     static T** AllocArray(int capacity) {
95         return (T**)sk_calloc_throw(sizeof(T*) * capacity);  // All cells == Empty().
96     }
97 
reset(int capacity)98     void reset(int capacity) {
99         fCount = 0;
100         fDeleted = 0;
101         fCapacity = capacity;
102         fArray = AllocArray(fCapacity);
103     }
104 
validate()105     bool validate() const {
106         #define SKTDYNAMICHASH_CHECK(x) SkASSERT((x)); if (!(x)) return false
107 
108         // Is capacity sane?
109         SKTDYNAMICHASH_CHECK(SkIsPow2(fCapacity));
110         SKTDYNAMICHASH_CHECK(fCapacity >= kMinCapacity);
111 
112         // Is fCount correct?
113         int count = 0;
114         for (int i = 0; i < fCapacity; i++) {
115             if (Empty() != fArray[i] && Deleted() != fArray[i]) {
116                 count++;
117             }
118         }
119         SKTDYNAMICHASH_CHECK(count == fCount);
120 
121         // Is fDeleted correct?
122         int deleted = 0;
123         for (int i = 0; i < fCapacity; i++) {
124             if (Deleted() == fArray[i]) {
125                 deleted++;
126             }
127         }
128         SKTDYNAMICHASH_CHECK(deleted == fDeleted);
129 
130         // Are all entries findable?
131         for (int i = 0; i < fCapacity; i++) {
132             if (Empty() == fArray[i] || Deleted() == fArray[i]) {
133                 continue;
134             }
135             SKTDYNAMICHASH_CHECK(NULL != this->find(GetKey(*fArray[i])));
136         }
137 
138         // Are all entries unique?
139         for (int i = 0; i < fCapacity; i++) {
140             if (Empty() == fArray[i] || Deleted() == fArray[i]) {
141                 continue;
142             }
143             for (int j = i+1; j < fCapacity; j++) {
144                 if (Empty() == fArray[j] || Deleted() == fArray[j]) {
145                     continue;
146                 }
147                 SKTDYNAMICHASH_CHECK(fArray[i] != fArray[j]);
148                 SKTDYNAMICHASH_CHECK(!Equal(*fArray[i], GetKey(*fArray[j])));
149                 SKTDYNAMICHASH_CHECK(!Equal(*fArray[j], GetKey(*fArray[i])));
150             }
151         }
152         #undef SKTDYNAMICHASH_CHECK
153         return true;
154     }
155 
innerAdd(T * newEntry)156     void innerAdd(T* newEntry) {
157         const Key& key = GetKey(*newEntry);
158         int index = this->firstIndex(key);
159         for (int round = 0; round < fCapacity; round++) {
160             const T* candidate = fArray[index];
161             if (Empty() == candidate || Deleted() == candidate) {
162                 if (Deleted() == candidate) {
163                     fDeleted--;
164                 }
165                 fCount++;
166                 fArray[index] = newEntry;
167                 return;
168             }
169             index = this->nextIndex(index, round);
170         }
171         SkASSERT(0); // add: should be unreachable
172     }
173 
innerRemove(const Key & key)174     void innerRemove(const Key& key) {
175         const int firstIndex = this->firstIndex(key);
176         int index = firstIndex;
177         for (int round = 0; round < fCapacity; round++) {
178             const T* candidate = fArray[index];
179             if (Deleted() != candidate && Equal(*candidate, key)) {
180                 fDeleted++;
181                 fCount--;
182                 fArray[index] = Deleted();
183                 return;
184             }
185             index = this->nextIndex(index, round);
186         }
187         SkASSERT(0); // innerRemove: should be unreachable
188     }
189 
maybeGrow()190     void maybeGrow() {
191         if (fCount + fDeleted + 1 > (fCapacity * kGrowPercent) / 100) {
192             resize(fCapacity * 2);
193         }
194     }
195 
maybeShrink()196     void maybeShrink() {
197         if (fCount < (fCapacity * kShrinkPercent) / 100 && fCapacity / 2 > kMinCapacity) {
198             resize(fCapacity / 2);
199         }
200     }
201 
resize(int newCapacity)202     void resize(int newCapacity) {
203         SkDEBUGCODE(int oldCount = fCount;)
204         int oldCapacity = fCapacity;
205         T** oldArray = fArray;
206 
207         reset(newCapacity);
208 
209         for (int i = 0; i < oldCapacity; i++) {
210             T* entry = oldArray[i];
211             if (Empty() != entry && Deleted() != entry) {
212                 this->add(entry);
213             }
214         }
215         SkASSERT(oldCount == fCount);
216 
217         sk_free(oldArray);
218     }
219 
220     // fCapacity is always a power of 2, so this masks the correct low bits to index into our hash.
hashMask()221     uint32_t hashMask() const { return fCapacity - 1; }
222 
firstIndex(const Key & key)223     int firstIndex(const Key& key) const {
224         return Hash(key) & this->hashMask();
225     }
226 
227     // Given index at round N, what is the index to check at N+1?  round should start at 0.
nextIndex(int index,int round)228     int nextIndex(int index, int round) const {
229         // This will search a power-of-two array fully without repeating an index.
230         return (index + round + 1) & this->hashMask();
231     }
232 
233     int fCount;     // Number of non Empty(), non Deleted() entries in fArray.
234     int fDeleted;   // Number of Deleted() entries in fArray.
235     int fCapacity;  // Number of entries in fArray.  Always a power of 2.
236     T** fArray;
237 };
238 
239 #endif
240