| Index: src/core/SkTDynamicHash.h
|
| diff --git a/src/core/SkTDynamicHash.h b/src/core/SkTDynamicHash.h
|
| index 21aca07b907174d3d7e3071e64b0b7857be60aa5..a40870193eef2e74727f94457a44fa80bb432932 100644
|
| --- a/src/core/SkTDynamicHash.h
|
| +++ b/src/core/SkTDynamicHash.h
|
| @@ -15,13 +15,16 @@ template <typename T,
|
| typename Key,
|
| const Key& (GetKey)(const T&),
|
| uint32_t (Hash)(const Key&),
|
| - bool (Equal)(const T&, const Key&)>
|
| + bool (Equal)(const T&, const Key&),
|
| + int kGrowPercent = 75, // Larger -> more memory efficient, but slower.
|
| + int kShrinkPercent = 25>
|
| class SkTDynamicHash {
|
| + static const int kMinCapacity = 4; // Smallest capacity we allow.
|
| public:
|
| - SkTDynamicHash(int initialCapacity=64/sizeof(T*))
|
| - : fCount(0)
|
| - , fCapacity(SkNextPow2(initialCapacity > 0 ? initialCapacity : 1))
|
| - , fArray(AllocArray(fCapacity)) {}
|
| + SkTDynamicHash(int initialCapacity=64/sizeof(T*)) {
|
| + this->reset(SkNextPow2(initialCapacity > kMinCapacity ? initialCapacity : kMinCapacity));
|
| + SkASSERT(this->validate());
|
| + }
|
|
|
| ~SkTDynamicHash() {
|
| sk_free(fArray);
|
| @@ -34,10 +37,10 @@ public:
|
| int index = this->firstIndex(key);
|
| for (int round = 0; round < fCapacity; round++) {
|
| T* candidate = fArray[index];
|
| - if (candidate == Empty()) {
|
| + if (Empty() == candidate) {
|
| return NULL;
|
| }
|
| - if (candidate != Deleted() && Equal(*candidate, key)) {
|
| + if (Deleted() != candidate && Equal(*candidate, key)) {
|
| return candidate;
|
| }
|
| index = this->nextIndex(index, round);
|
| @@ -46,32 +49,22 @@ public:
|
| return NULL;
|
| }
|
|
|
| - // Add an entry with this key.
|
| + // Add an entry with this key. We require that no entry with newEntry's key is already present.
|
| void add(T* newEntry) {
|
| + SkASSERT(NULL == this->find(GetKey(*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(!"add: should be unreachable");
|
| + SkASSERT(this->validate());
|
| + this->innerAdd(newEntry);
|
| + SkASSERT(this->validate());
|
| }
|
|
|
| - // Remove entry with this key, if we have it.
|
| + // Remove the entry with this key. We reqire that an entry with this key is present.
|
| void remove(const Key& key) {
|
| + SkASSERT(NULL != this->find(key));
|
| this->innerRemove(key);
|
| + SkASSERT(this->validate());
|
| this->maybeShrink();
|
| + SkASSERT(this->validate());
|
| }
|
|
|
| protected:
|
| @@ -84,7 +77,7 @@ protected:
|
| int index = this->firstIndex(key);
|
| for (int round = 0; round < fCapacity; round++) {
|
| const T* candidate = fArray[index];
|
| - if (candidate == Empty() || candidate == Deleted() || Equal(*candidate, key)) {
|
| + if (Empty() == candidate || Deleted() == candidate || Equal(*candidate, key)) {
|
| return round;
|
| }
|
| index = this->nextIndex(index, round);
|
| @@ -95,8 +88,8 @@ protected:
|
|
|
| private:
|
| // 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* Empty() { return reinterpret_cast<T*>(0); } // i.e. NULL
|
| + static T* Deleted() { return reinterpret_cast<T*>(1); } // Also an invalid pointer.
|
|
|
| static T** AllocArray(int capacity) {
|
| T** array = (T**)sk_malloc_throw(sizeof(T*) * capacity);
|
| @@ -104,16 +97,91 @@ private:
|
| return array;
|
| }
|
|
|
| - void innerRemove(const Key& key) {
|
| + void reset(int capacity) {
|
| + fCount = 0;
|
| + fDeleted = 0;
|
| + fCapacity = capacity;
|
| + fArray = AllocArray(fCapacity);
|
| + }
|
| +
|
| + bool validate() const {
|
| + #define CHECK(x) SkASSERT((x)); if (!(x)) return false
|
| +
|
| + // Is capacity sane?
|
| + CHECK(SkIsPow2(fCapacity));
|
| + CHECK(fCapacity >= kMinCapacity);
|
| +
|
| + // Is fCount correct?
|
| + int count = 0;
|
| + for (int i = 0; i < fCapacity; i++) {
|
| + if (Empty() != fArray[i] && Deleted() != fArray[i]) {
|
| + count++;
|
| + }
|
| + }
|
| + CHECK(count == fCount);
|
| +
|
| + // Is fDeleted correct?
|
| + int deleted = 0;
|
| + for (int i = 0; i < fCapacity; i++) {
|
| + if (Deleted() == fArray[i]) {
|
| + deleted++;
|
| + }
|
| + }
|
| + CHECK(deleted == fDeleted);
|
| +
|
| + // Are all entries findable?
|
| + for (int i = 0; i < fCapacity; i++) {
|
| + if (Empty() == fArray[i] || Deleted() == fArray[i]) {
|
| + continue;
|
| + }
|
| + CHECK(NULL != this->find(GetKey(*fArray[i])));
|
| + }
|
| +
|
| + // Are all entries unique?
|
| + for (int i = 0; i < fCapacity; i++) {
|
| + if (Empty() == fArray[i] || Deleted() == fArray[i]) {
|
| + continue;
|
| + }
|
| + for (int j = i+1; j < fCapacity; j++) {
|
| + if (Empty() == fArray[j] || Deleted() == fArray[j]) {
|
| + continue;
|
| + }
|
| + CHECK(fArray[i] != fArray[j]);
|
| + CHECK(!Equal(*fArray[i], GetKey(*fArray[j])));
|
| + CHECK(!Equal(*fArray[j], GetKey(*fArray[i])));
|
| + }
|
| + }
|
| + #undef CHECK
|
| + return true;
|
| + }
|
| +
|
| + void innerAdd(T* newEntry) {
|
| + const Key& key = GetKey(*newEntry);
|
| int index = this->firstIndex(key);
|
| for (int round = 0; round < fCapacity; round++) {
|
| const T* candidate = fArray[index];
|
| - if (candidate == Empty()) {
|
| + if (Empty() == candidate || Deleted() == candidate) {
|
| + if (Deleted() == candidate) {
|
| + fDeleted--;
|
| + }
|
| + fCount++;
|
| + fArray[index] = newEntry;
|
| return;
|
| }
|
| - if (candidate != Deleted() && Equal(*candidate, key)) {
|
| - fArray[index] = const_cast<T*>(Deleted());
|
| + index = this->nextIndex(index, round);
|
| + }
|
| + SkASSERT(!"add: should be unreachable");
|
| + }
|
| +
|
| + void innerRemove(const Key& key) {
|
| + const int firstIndex = this->firstIndex(key);
|
| + int index = firstIndex;
|
| + for (int round = 0; round < fCapacity; round++) {
|
| + const T* candidate = fArray[index];
|
| + if (Deleted() != candidate && Equal(*candidate, key)) {
|
| + fDeleted++;
|
| fCount--;
|
| + fArray[index] = Deleted();
|
| return;
|
| }
|
| index = this->nextIndex(index, round);
|
| @@ -122,21 +190,27 @@ private:
|
| }
|
|
|
| void maybeGrow() {
|
| - if (fCount < fCapacity / 2) {
|
| - return;
|
| + if (fCount + fDeleted + 1 > (fCapacity * kGrowPercent) / 100) {
|
| + resize(fCapacity * 2);
|
| + }
|
| + }
|
| +
|
| + void maybeShrink() {
|
| + if (fCount < (fCapacity * kShrinkPercent) / 100 && fCapacity / 2 > kMinCapacity) {
|
| + resize(fCapacity / 2);
|
| }
|
| + }
|
|
|
| + void resize(int newCapacity) {
|
| SkDEBUGCODE(int oldCount = fCount;)
|
| int oldCapacity = fCapacity;
|
| T** oldArray = fArray;
|
|
|
| - fCount = 0;
|
| - fCapacity *= 2;
|
| - fArray = AllocArray(fCapacity);
|
| + reset(newCapacity);
|
|
|
| for (int i = 0; i < oldCapacity; i++) {
|
| T* entry = oldArray[i];
|
| - if (entry != Empty() && entry != Deleted()) {
|
| + if (Empty() != entry && Deleted() != entry) {
|
| this->add(entry);
|
| }
|
| }
|
| @@ -145,10 +219,6 @@ private:
|
| sk_free(oldArray);
|
| }
|
|
|
| - 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; }
|
|
|
| @@ -162,7 +232,8 @@ private:
|
| return (index + round + 1) & this->hashMask();
|
| }
|
|
|
| - int fCount; // Number of non-empty, non-deleted entries in fArray.
|
| + int fCount; // Number of non Empty(), non Deleted() entries in fArray.
|
| + int fDeleted; // Number of Deleted() entries in fArray.
|
| int fCapacity; // Number of entries in fArray. Always a power of 2.
|
| T** fArray;
|
| };
|
|
|