Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(6)

Side by Side Diff: src/core/SkTDynamicHash.h

Issue 22292004: revise SkTDynamicHash and add unit tests (Closed) Base URL: https://skia.googlecode.com/svn/trunk
Patch Set: Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « src/core/SkScaledImageCache.cpp ('k') | tests/DynamicHashTest.cpp » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
OLDNEW
« no previous file with comments | « src/core/SkScaledImageCache.cpp ('k') | tests/DynamicHashTest.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698