src/HashTable.h
author David Anderson <danderson@mozilla.com>
Wed Jul 25 00:48:08 2012 -0700 (2012-07-25)
changeset 91 5f4402180204
parent 86 32ad0523b309
child 132 d8379f5d5a1d
permissions -rw-r--r--
Added clang support. A lot of the motion here is introducing a new offsetof because clang's seems broken?
     1 /*
     2  * Copyright (C) 2012 David Anderson
     3  *
     4  * This file is part of JITCraft.
     5  *
     6  * JITCraft is free software: you can redistribute it and/or modify it under
     7  * the terms of the GNU General Public License as published by the Free
     8  * Software Foundation, either version 3 of the License, or (at your option)
     9  * any later version.
    10  * 
    11  * Foobar is distributed in the hope that it will be useful, but WITHOUT ANY
    12  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    13  * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
    14  *
    15  * You should have received a copy of the GNU General Public License along with
    16  * JITCraft. If not, see http://www.gnu.org/licenses/.
    17  */
    18 #ifndef _INCLUDE_KEIMA_HASHTABLE_H_
    19 #define _INCLUDE_KEIMA_HASHTABLE_H_
    20 
    21 #include <new>
    22 #include <limits.h>
    23 #include "Utility.h"
    24 
    25 namespace ke {
    26 
    27 template <typename T>
    28 class HashTableEntry
    29 {
    30     uint32 hash_;
    31     T t_;
    32 
    33   public:
    34     static const uint32 kFreeHash = 0;
    35     static const uint32 kRemovedHash = 1;
    36 
    37   public:
    38     void setHash(uint32 hash) {
    39         hash_ = hash;
    40     }
    41     uint32 hash() const {
    42         return hash_;
    43     }
    44     void setRemoved() {
    45         hash_ = kRemovedHash;
    46         t_ = T();
    47     }
    48     void construct() {
    49         hash_ = kFreeHash;
    50         new (&t_) T();
    51     }
    52     void destruct() {
    53         t_.~T();
    54     }
    55     bool removed() const {
    56         return hash_ == kRemovedHash;
    57     }
    58     bool free() const {
    59         return hash_ == kFreeHash;
    60     }
    61     bool isLive() const {
    62         return hash_ > kRemovedHash;
    63     }
    64     T &payload() {
    65         return t_;
    66     }
    67     bool sameHash(uint32 hash) const {
    68         return hash_ == hash;
    69     }
    70 };
    71 
    72 template <typename HashPolicy, typename AllocPolicy>
    73 class HashTable : public AllocPolicy
    74 {
    75     friend class iterator;
    76 
    77     typedef typename HashPolicy::Payload Payload;
    78     typedef HashTableEntry<Payload> Entry;
    79 
    80     uint32 capacity_;
    81     uint32 nelements_;
    82     uint32 ndeleted_;
    83     Entry *table_;
    84     uint32 minCapacity_;
    85 
    86   private:
    87     static const uint32 kMinCapacity = 16;
    88     static const uint32 kMaxCapacity = INT_MAX / sizeof(Entry);
    89 
    90     template <typename Key>
    91     uint32 computeHash(const Key &key) {
    92         // Multiply by golden ratio.
    93         uint32 hash = HashPolicy::hash(key) * 0x9E3779B9;
    94         if (hash == Entry::kFreeHash || hash == Entry::kRemovedHash)
    95             hash += 2;
    96         return hash;
    97     }
    98 
    99     Entry *createTable(uint32 capacity) {
   100         assert(capacity <= kMaxCapacity);
   101 
   102         Entry *table = (Entry *)this->malloc(capacity * sizeof(Entry));
   103         if (!table)
   104             return NULL;
   105 
   106         for (size_t i = 0; i < capacity; i++)
   107             table[i].construct();
   108 
   109         return table;
   110     }
   111 
   112   public:
   113     class Result
   114     {
   115         friend class HashTable;
   116 
   117         Entry *entry_;
   118 
   119         Entry &entry() {
   120             return *entry_;
   121         }
   122 
   123       public:
   124         Result(Entry *entry)
   125           : entry_(entry)
   126         { }
   127 
   128         Payload * operator ->() {
   129             return entry_->payload();
   130         }
   131         Payload & operator *() {
   132             return entry_->payload();
   133         }
   134 
   135         bool found() const {
   136             return entry_->isLive();
   137         }
   138     };
   139 
   140     class Insert : public Result
   141     {
   142         uint32 hash_;
   143 
   144       public:
   145         Insert(Entry *entry, uint32 hash)
   146           : Result(entry),
   147             hash_(hash)
   148         {
   149         }
   150 
   151         uint32 hash() const {
   152             return hash_;
   153         }
   154     };
   155 
   156   private:
   157     class Probulator {
   158         uint32 hash_;
   159         uint32 capacity_;
   160 
   161       public:
   162         Probulator(uint32 hash, uint32 capacity)
   163           : hash_(hash),
   164             capacity_(capacity)
   165         {
   166             assert(IsPowerOfTwo(capacity_)); 
   167         }
   168 
   169         uint32 entry() const {
   170             return hash_ & (capacity_ - 1);
   171         }
   172         uint32 next() {
   173             hash_++;
   174             return entry();
   175         }
   176     };
   177 
   178     bool underloaded() const {
   179         // Check if the table is underloaded: < 25% entries used.
   180         return (capacity_ > kMinCapacity) && (nelements_ + ndeleted_ < capacity_ / 4);
   181     }
   182     bool overloaded() const {
   183         // Grow if the table is overloaded: > 75% entries used.
   184         return (nelements_ + ndeleted_) > ((capacity_ / 2) + (capacity_ / 4));
   185     }
   186 
   187     bool shrink() {
   188         if ((capacity_ >> 1) < minCapacity_)
   189             return true;
   190         return changeCapacity(capacity_ >> 1);
   191     }
   192     
   193     bool grow() {
   194         assert(IsPowerOfTwo(kMaxCapacity));
   195         if (capacity_ >= kMaxCapacity) {
   196             this->reportAllocationOverflow();
   197             return false;
   198         }
   199         return changeCapacity(capacity_ << 1);
   200     }
   201 
   202     bool changeCapacity(uint32 newCapacity) {
   203         assert(newCapacity <= kMaxCapacity);
   204 
   205         Entry *newTable = createTable(newCapacity);
   206         if (!newTable)
   207             return false;
   208 
   209         Entry *oldTable = table_;
   210         uint32 oldCapacity = capacity_;
   211 
   212         table_ = newTable;
   213         capacity_ = newCapacity;
   214         ndeleted_ = 0;
   215 
   216         for (uint32 i = 0; i < oldCapacity; i++) {
   217             Entry &oldEntry = oldTable[i];
   218             if (oldEntry.isLive()) {
   219                 Insert p = insertUnique(oldEntry.hash());
   220                 p.entry().setHash(p.hash());
   221                 p.entry().payload() = oldEntry.payload();
   222             }
   223             oldEntry.destruct();
   224         }
   225         this->free(oldTable);
   226 
   227         return true;
   228     }
   229 
   230     // For use when the key is known to be unique.
   231     Insert insertUnique(uint32 hash) {
   232         Probulator probulator(hash, capacity_);
   233 
   234         Entry *e = &table_[probulator.entry()];
   235         for (;;) {
   236             if (e->free() || e->removed())
   237                 break;
   238             e = &table_[probulator.next()];
   239         }
   240 
   241         return Insert(e, hash);
   242     }
   243 
   244     template <typename Key>
   245     Result lookup(const Key &key) {
   246         uint32 hash = computeHash(key);
   247         Probulator probulator(hash, capacity_);
   248 
   249         Entry *e = &table_[probulator.entry()];
   250         for (;;) {
   251             if (e->free())
   252                 break;
   253             if (e->isLive() &&
   254                 e->sameHash(hash) &&
   255                 HashPolicy::matches(key, e->payload()))
   256             {
   257                 return Result(e);
   258             }
   259             e = &table_[probulator.next()];
   260         }
   261 
   262         return Result(e);
   263     }
   264 
   265     template <typename Key>
   266     Insert lookupForAdd(const Key &key) {
   267         uint32 hash = computeHash(key);
   268         Probulator probulator(hash, capacity_);
   269 
   270         Entry *e = &table_[probulator.entry()];
   271         for (;;) {
   272             if (!e->isLive())
   273                 break;
   274             if (e->sameHash(hash) && HashPolicy::matches(key, e->payload()))
   275                 break;
   276             e = &table_[probulator.next()];
   277         }
   278 
   279         return Insert(e, hash);
   280     }
   281 
   282     bool add(Insert &i) {
   283         assert(!i.found());
   284 
   285         // If the entry is deleted, just re-use the slot.
   286         if (i.entry().removed()) {
   287             ndeleted_--;
   288             return true;
   289         }
   290 
   291         // Otherwise, see if we're at max capacity.
   292         if (nelements_ == kMaxCapacity) {
   293             this->reportAllocationOverflow();
   294             return false;
   295         }
   296 
   297         // Check if the table is over or underloaded. The table is always at
   298         // least 25% free, so this check is enough to guarantee one free slot.
   299         // (Without one free slot, insertion search could infinite loop.)
   300         uint32 oldCapacity = capacity_;
   301         if (!checkDensity())
   302             return false;
   303 
   304         // If the table changed size, we need to find a new insertion point.
   305         // Note that a removed entry is impossible: either we caught it above,
   306         // or we just resized and no entries are removed.
   307         if (capacity_ != oldCapacity)
   308             i = insertUnique(i.hash());
   309 
   310         i.entry().setHash(i.hash());
   311         nelements_++;
   312 
   313         return true;
   314     }
   315 
   316     void remove(Entry &e) {
   317         assert(e.isLive());
   318         e.setRemoved();
   319         ndeleted_++;
   320         nelements_--;
   321     }
   322 
   323   public:
   324     HashTable(AllocPolicy ap)
   325       : AllocPolicy(ap),
   326         capacity_(0),
   327         nelements_(0),
   328         ndeleted_(0),
   329         table_(NULL),
   330         minCapacity_(kMinCapacity)
   331     {
   332     }
   333 
   334     ~HashTable()
   335     {
   336         for (uint32 i = 0; i < capacity_; i++)
   337             table_[i].destruct();
   338         this->free(table_);
   339     }
   340 
   341     bool init(uint32 capacity = 0) {
   342         if (capacity < kMinCapacity)
   343             capacity = kMinCapacity;
   344         else if (capacity > kMaxCapacity)
   345             capacity = kMaxCapacity;
   346 
   347         minCapacity_ = capacity;
   348 
   349         assert(IsPowerOfTwo(capacity));
   350         capacity_ = capacity;
   351 
   352         table_ = createTable(capacity_);
   353         if (!table_)
   354             return false;
   355 
   356         return true;
   357     }
   358 
   359     template <typename Key>
   360     Result find(const Key &key) {
   361         return lookup(key);
   362     }
   363 
   364     template <typename Key>
   365     Insert findForAdd(const Key &key) {
   366         return lookupForAdd(key);
   367     }
   368 
   369     bool add(Insert &i, const Payload &payload) {
   370         if (!add(i))
   371             return false;
   372         *i = payload;
   373         return true;
   374     }
   375 
   376     bool checkDensity() {
   377         if (underloaded())
   378             return shrink(); 
   379         if (overloaded())
   380             return grow();
   381         return true;
   382     }
   383 
   384   public:
   385     class iterator
   386     {
   387         HashTable *table_;
   388         Entry *i_;
   389         Entry *end_;
   390 
   391       public:
   392         iterator(HashTable *table)
   393           : table_(table),
   394             i_(table->table_),
   395             end_(table->table_ + table->capacity_)
   396         {
   397             while (i_ < end_ && !i_->isLive())
   398                 i_++;
   399         }
   400 
   401         bool empty() const {
   402             return i_ == end_;
   403         }
   404 
   405         void erase() {
   406             assert(!empty());
   407             table_->remove(*i_);
   408         }
   409 
   410         const Payload &operator *() const {
   411             return i_->payload();
   412         }
   413 
   414         void next() {
   415             do {
   416                 i_++;
   417             } while (i_ < end_ && !i_->isLive());
   418         }
   419     };
   420 };
   421 
   422 }
   423 
   424 #endif // _INCLUDE_KEIMA_HASHTABLE_H_