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