Chromium Code Reviews| Index: src/inline-hash-map.h |
| diff --git a/src/inline-hash-map.h b/src/inline-hash-map.h |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..30256b05626d26f3fcebb4b8cc23ef6a337fe605 |
| --- /dev/null |
| +++ b/src/inline-hash-map.h |
| @@ -0,0 +1,500 @@ |
| +// Copyright 2016 the V8 project authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#ifndef V8_ZONE_TEMPLATE_HASH_MAP_H_ |
| +#define V8_ZONE_TEMPLATE_HASH_MAP_H_ |
|
rmcilroy
2016/09/12 14:17:31
I wonder how much of this should really live in th
|
| + |
| +#include "src/zone.h" |
| + |
| +namespace v8 { |
| +namespace internal { |
|
rmcilroy
2016/09/12 14:17:31
This should really be v8/base (and in the base dir
|
| + |
| +class DefaultAllocationPolicy { |
| + public: |
| + V8_INLINE void* New(size_t size) { return malloc(size); } |
| + V8_INLINE static void Delete(void* p) { free(p); } |
| +}; |
| + |
| +namespace detail { |
|
rmcilroy
2016/09/12 14:17:31
Typically we use annoymous namespaces for private
|
| + |
| +template <typename Key, typename Value> |
| +class InlineHashMapEntry { |
| + public: |
| + InlineHashMapEntry(Key key, Value value, size_t hash) |
| + : keyValue_(std::move(key), std::move(value)), |
| + hash_(hash), |
| + exists_(true) {} |
| + |
| + const std::pair<const Key, Value>& keyValue() const { return keyValue_; } |
| + std::pair<const Key, Value>& keyValue() { return keyValue_; } |
| + |
| + size_t hash() const { return hash_; } |
| + |
| + bool exists() const { return exists_; } |
| + |
| + void clear() { exists_ = false; } |
| + |
| + bool operator==(const InlineHashMapEntry& other) { |
| + if (!exists()) { |
| + return !other.exists(); |
| + } |
| + |
| + if (!other.exists()) { |
| + return false; |
| + } |
| + |
| + return keyValue() == other.keyValue(); |
| + } |
| + |
| + bool operator!=(const InlineHashMapEntry& other) { return !(*this == other); } |
| + |
| + private: |
| + std::pair<const Key, Value> keyValue_; |
| + size_t hash_; // The full hash value for key |
| + bool exists_; // TODO(leszeks): this could be a tagged hash |
| +}; |
| + |
| +// Specialize InlineHashMapEntry for pointer types |
| +template <typename Key, typename Value> |
| +class InlineHashMapEntry<Key*, Value> { |
| + public: |
| + InlineHashMapEntry(Key* key, Value value, size_t hash) |
| + : keyValue_(std::move(key), std::move(value)), hash_(hash) {} |
| + |
| + const std::pair<Key* const, Value>& keyValue() const { return keyValue_; } |
| + std::pair<Key* const, Value>& keyValue() { return keyValue_; } |
| + |
| + size_t hash() const { return hash_; } |
| + |
| + bool exists() const { return keyValue_.first != nullptr; } |
| + |
| + void clear() { |
| + // Nasty const_cast to allow us to set the normally const key of the entry. |
| + // Only valid because we absolutely promise that we're not using this object |
| + // except for existence tests. |
| + const_cast<Key*&>(keyValue_.first) = nullptr; |
| + } |
| + |
| + bool operator==(const InlineHashMapEntry& other) { |
| + if (!exists()) { |
| + return !other.exists(); |
| + } |
| + |
| + if (!other.exists()) { |
| + return false; |
| + } |
| + |
| + return keyValue() == other.keyValue(); |
| + } |
| + |
| + bool operator!=(const InlineHashMapEntry& other) { return !(*this == other); } |
| + |
| + private: |
| + std::pair<Key* const, Value> keyValue_; |
| + size_t hash_; // The full hash value for key |
| +}; |
| +} // namespace detail |
| + |
| +template <typename Key, typename Value, typename Hash, |
| + typename AllocationPolicy = DefaultAllocationPolicy> |
| +class InlineHashMap |
| + : private AllocationPolicy /* for Empty Base Optimization */ { |
| + public: |
| + // The default capacity. This is used by the call sites which want |
| + // to pass in a non-default AllocationPolicy but want to use the |
| + // default value of capacity specified by the implementation. |
| + static const uint32_t kDefaultHashMapCapacity = 8; |
| + |
| + // initial_capacity is the size of the initial hash map; |
| + // it must be a power of 2 (and thus must not be 0). |
| + explicit InlineHashMap(uint32_t capacity = kDefaultHashMapCapacity, |
| + AllocationPolicy allocator = AllocationPolicy()); |
| + |
| + ~InlineHashMap(); |
| + |
| + class Iterator; |
| + typedef Iterator iterator; |
| + typedef const Iterator const_iterator; |
| + |
| + // If an entry with matching key is found, returns an iterator to that entry. |
| + // Otherwise, the end() iterator is returned |
| + iterator Find(const Key& key) const; |
| + |
| + // Inserts the given key and value into the map, returning true if there was |
| + // already another entry under that key |
| + bool Insert(Key key, Value value); |
| + |
| + // If an entry with matching key is found, returns the value of that entry. |
| + // Otherwise, it creates the value using the given function, inserts it, and |
| + // returns it |
| + template <typename Func> |
| + Value& LookupOrInsert(const Key& key, const Func& valueFunc); |
| + |
| + // If an entry with matching key is found, returns the value of that entry. |
| + // Otherwise, it creates the value with its default constructor, inserts it, |
| + // and returns it |
| + Value& LookupOrInsert(const Key& key); |
| + |
| + Value& operator[](const Key& key); |
| + |
| + iterator start(); |
| + |
| + iterator end(); |
| + |
| + // Removes the entry with matching key. |
| + // Returns true if there was an entry. |
| + bool Remove(const Key& key); |
| + |
| + void Clear(); |
| + |
| + // The number of (non-empty) entries in the table. |
| + uint32_t occupancy() const { return occupancy_; } |
| + |
| + // The capacity of the table. The implementation |
| + // makes sure that occupancy is at most 80% of |
| + // the table capacity. |
| + uint32_t capacity() const { return capacity_; } |
| + |
| + private: |
| + typedef detail::InlineHashMapEntry<Key, Value> Entry; |
| + |
| + Entry* map_; |
| + uint32_t capacity_; |
| + uint32_t occupancy_; |
| + |
| + Entry* map_end() const { return map_ + capacity_; } |
| + |
| + void EnsureInitialized() const; |
| + void Initialize(uint32_t capacity); |
| + |
| + Entry* Probe(const Key& key, size_t hash) const; |
| + Entry* FillProbe(Entry* entry, Key key, Value value, size_t hashval); |
| + Entry* ResizeAndProbe(const Key& key, size_t hash); |
| +}; |
| + |
| +template <typename Key, typename Value, typename Hash, |
| + typename AllocationPolicy> |
| +InlineHashMap<Key, Value, Hash, AllocationPolicy>::InlineHashMap( |
| + uint32_t initial_capacity, AllocationPolicy allocator) |
| + : AllocationPolicy(allocator), |
| + map_(nullptr), |
|
rmcilroy
2016/09/12 14:17:31
As discussed offline, let's make this just eagerly
|
| + capacity_(initial_capacity), |
| + occupancy_(0) { |
| + // initialize lazily |
| +} |
| + |
| +template <typename Key, typename Value, typename Hash, |
| + typename AllocationPolicy> |
| +InlineHashMap<Key, Value, Hash, AllocationPolicy>::~InlineHashMap() { |
| + AllocationPolicy::Delete(map_); |
| +} |
| + |
| +template <typename Key, typename Value, typename Hash, |
| + typename AllocationPolicy> |
| +typename InlineHashMap<Key, Value, Hash, AllocationPolicy>::iterator |
| +InlineHashMap<Key, Value, Hash, AllocationPolicy>::Find(const Key& key) const { |
| + Entry* p = Probe(key, Hash()(key)); |
| + return Iterator(p->exists() ? p : map_end(), map_end()); |
| +} |
| + |
| +template <typename Key, typename Value, typename Hash, |
| + typename AllocationPolicy> |
| +bool InlineHashMap<Key, Value, Hash, AllocationPolicy>::Insert(Key key, |
| + Value value) { |
| + size_t hashval = Hash()(key); |
| + Entry* p = Probe(key, hashval); |
| + bool valueReplaced = p->exists(); |
| + p = FillProbe(p, key, Value(), hashval); |
| + return valueReplaced; |
| +} |
| + |
| +template <typename Key, typename Value, typename Hash, |
| + typename AllocationPolicy> |
| +template <typename Func> |
| +Value& InlineHashMap<Key, Value, Hash, AllocationPolicy>::LookupOrInsert( |
| + const Key& key, const Func& valueFunc) { |
| + size_t hashval = Hash()(key); |
| + Entry* p = Probe(key, hashval); |
| + if (!p->exists()) { |
| + p = FillProbe(p, key, valueFunc(), hashval); |
| + } |
| + return p->keyValue().second; |
| +} |
| + |
| +template <typename Key, typename Value, typename Hash, |
| + typename AllocationPolicy> |
| +Value& InlineHashMap<Key, Value, Hash, AllocationPolicy>::LookupOrInsert( |
| + const Key& key) { |
| + return LookupOrInsert(key, []() { return Value(); }); |
| +} |
| + |
| +template <typename Key, typename Value, typename Hash, |
| + typename AllocationPolicy> |
| +Value& InlineHashMap<Key, Value, Hash, AllocationPolicy>::operator[]( |
| + const Key& key) { |
| + return LookupOrInsert(key); |
| +} |
| + |
| +template <typename Key, typename Value, typename Hash, |
| + typename AllocationPolicy> |
| +typename InlineHashMap<Key, Value, Hash, AllocationPolicy>::iterator |
| +InlineHashMap<Key, Value, Hash, AllocationPolicy>::start() { |
| + if (map_ == nullptr) { |
| + return Iterator(nullptr, nullptr); |
| + } |
| + // Increment from map_ - 1 to find the first entry that exists |
| + return ++Iterator(map_ - 1, map_end()); |
| +} |
| + |
| +template <typename Key, typename Value, typename Hash, |
| + typename AllocationPolicy> |
| +typename InlineHashMap<Key, Value, Hash, AllocationPolicy>::iterator |
| +InlineHashMap<Key, Value, Hash, AllocationPolicy>::end() { |
| + if (map_ == nullptr) { |
| + return Iterator(nullptr, nullptr); |
| + } |
| + return Iterator(map_end(), map_end()); |
| +} |
| + |
| +template <typename Key, typename Value, typename Hash, |
| + typename AllocationPolicy> |
| +void InlineHashMap<Key, Value, Hash, AllocationPolicy>::EnsureInitialized() |
| + const { |
| + if (map_ == nullptr) { |
| + // Cast away the constness of "this", because this is a lazy |
| + // initialization and "virtually" happens in the constructor |
| + const_cast<InlineHashMap*>(this)->Initialize(capacity_); |
| + } |
| +} |
| + |
| +template <typename Key, typename Value, typename Hash, |
| + typename AllocationPolicy> |
| +void InlineHashMap<Key, Value, Hash, AllocationPolicy>::Initialize( |
| + uint32_t capacity) { |
| + DCHECK(base::bits::IsPowerOfTwo32(capacity)); |
| + map_ = |
| + reinterpret_cast<Entry*>(AllocationPolicy::New(capacity * sizeof(Entry))); |
| + if (map_ == nullptr) { |
| + FATAL("Out of memory: HashMap::Initialize"); |
| + return; |
| + } |
| + capacity_ = capacity; |
| + Clear(); |
| +} |
| + |
| +template <typename Key, typename Value, typename Hash, |
| + typename AllocationPolicy> |
| +bool InlineHashMap<Key, Value, Hash, AllocationPolicy>::Remove(const Key& key) { |
| + if (map_ == nullptr) { |
| + return false; |
| + } |
| + |
| + size_t hashval = Hash()(key); |
| + // Lookup the entry for the key to remove. |
| + Entry* p = Probe(key, hashval); |
| + if (!p->exists()) { |
| + // Key not found nothing to remove. |
| + return false; |
| + } |
| + |
| + // To remove an entry we need to ensure that it does not create an empty |
| + // entry that will cause the search for another entry to stop too soon. If all |
| + // the entries between the entry to remove and the next empty slot have their |
| + // initial position inside this interval, clearing the entry to remove will |
| + // not break the search. If, while searching for the next empty entry, an |
| + // entry is encountered which does not have its initial position between the |
| + // entry to remove and the position looked at, then this entry can be moved to |
| + // the place of the entry to remove without breaking the search for it. The |
| + // entry made vacant by this move is now the entry to remove and the process |
| + // starts over. |
| + // Algorithm from http://en.wikipedia.org/wiki/Open_addressing. |
| + |
| + // This guarantees loop termination as there is at least one empty entry so |
| + // eventually the removed entry will have an empty entry after it. |
| + DCHECK(occupancy_ < capacity_); |
| + |
| + // p is the candidate entry to clear. q is used to scan forwards. |
| + Entry* q = p; // Start at the entry to remove. |
| + while (true) { |
| + // Move q to the next entry. |
| + q = q + 1; |
| + if (q == map_end()) { |
| + q = map_; |
| + } |
| + |
| + // All entries between p and q have their initial position between p and q |
| + // and the entry p can be cleared without breaking the search for these |
| + // entries. |
| + if (!q->exists()) { |
| + break; |
| + } |
| + |
| + // Find the initial position for the entry at position q. |
| + Entry* r = map_ + (q->hash & (capacity_ - 1)); |
| + |
| + // If the entry at position q has its initial position outside the range |
| + // between p and q it can be moved forward to position p and will still be |
| + // found. There is now a new candidate entry for clearing. |
| + if ((q > p && (r <= p || r > q)) || (q < p && (r <= p && r > q))) { |
| + *p = *q; |
| + p = q; |
| + } |
| + } |
| + |
| + // Clear the entry which is allowed to be emptied. |
| + p->clear(); |
| + occupancy_--; |
| + return true; |
| +} |
| + |
| +template <typename Key, typename Value, typename Hash, |
| + typename AllocationPolicy> |
| +void InlineHashMap<Key, Value, Hash, AllocationPolicy>::Clear() { |
| + for (Entry* e = map_; e < map_end(); ++e) { |
| + e->clear(); |
| + } |
| + occupancy_ = 0; |
| +} |
| + |
| +template <typename Key, typename Value, typename Hash, |
| + typename AllocationPolicy> |
| +typename InlineHashMap<Key, Value, Hash, AllocationPolicy>::Entry* |
| +InlineHashMap<Key, Value, Hash, AllocationPolicy>::Probe(const Key& key, |
| + size_t hash) const { |
| + DCHECK(base::bits::IsPowerOfTwo32(capacity_)); |
| + EnsureInitialized(); |
| + |
| + Entry* p = map_ + (hash & (capacity_ - 1)); |
| + const Entry* end = map_end(); |
| + DCHECK(map_ <= p && p < end); |
| + |
| + DCHECK(occupancy_ < capacity_); // Guarantees loop termination. |
| + while (p->exists() && (hash != p->hash() || key != p->keyValue().first)) { |
| + p++; |
| + if (p >= end) { |
| + p = map_; |
| + } |
| + } |
| + |
| + return p; |
| +} |
| + |
| +template <typename Key, typename Value, typename Hash, |
| + typename AllocationPolicy> |
| +typename InlineHashMap<Key, Value, Hash, AllocationPolicy>::Entry* |
| +InlineHashMap<Key, Value, Hash, AllocationPolicy>::FillProbe(Entry* entry, |
| + Key key, |
| + Value value, |
| + size_t hashval) { |
| + DCHECK(!entry->exists()); |
| + |
| + // No entry found; insert one. |
| + new (entry) Entry(std::move(key), std::move(value), hashval); |
| + occupancy_++; |
| + |
| + // Grow the map if we reached >= 80% occupancy. |
| + if (occupancy_ + occupancy_ / 4 >= capacity_) { |
| + entry = ResizeAndProbe(key, hashval); |
| + } |
| + |
| + return entry; |
| +} |
| + |
| +template <typename Key, typename Value, typename Hash, |
| + typename AllocationPolicy> |
| +typename InlineHashMap<Key, Value, Hash, AllocationPolicy>::Entry* |
| +InlineHashMap<Key, Value, Hash, AllocationPolicy>::ResizeAndProbe( |
| + const Key& recoveredKey, size_t recoveredHash) { |
| + Entry* map = map_; |
| + Entry* recoveredVal = nullptr; |
| + uint32_t n = occupancy_; |
| + |
| + // Allocate larger map. |
| + Initialize(capacity_ * 2); |
| + |
| + // Rehash all current entries. |
| + for (Entry* p = map; n > 0; p++) { |
| + if (p->exists()) { |
| + Entry* new_p = Probe(p->keyValue().first, p->hash()); |
| + // Manually fill the probed entry to skip the occupancy check |
| + DCHECK(!new_p->exists()); |
| + new (new_p) Entry(std::move(p->keyValue().first), |
| + std::move(p->keyValue().second), p->hash()); |
| + occupancy_++; |
| + n--; |
| + |
| + // If this entry is the one that caused the resize, save its new pointer |
| + // This saves us doing another probe after the resize. |
| + if (recoveredVal == nullptr && p->hash() == recoveredHash && |
| + p->keyValue().first == recoveredKey) { |
| + recoveredVal = new_p; |
| + } |
| + } |
| + } |
| + |
| + // Delete previous map |
| + AllocationPolicy::Delete(map); |
| + |
| + DCHECK(recoveredVal != nullptr); |
| + return recoveredVal; |
| +} |
| + |
| +template <typename Key, typename Value, typename Hash, |
| + typename AllocationPolicy> |
| +class InlineHashMap<Key, Value, Hash, AllocationPolicy>::Iterator { |
| + public: |
| + Iterator() : entry_(nullptr), end_(nullptr) {} |
| + Iterator(Entry* start, Entry* end) : entry_(start), end_(end) {} |
| + |
| + std::pair<const Key, Value>& operator*() { return entry_->keyValue(); } |
| + const std::pair<const Key, Value>& operator*() const { |
| + return entry_->keyValue(); |
| + } |
| + |
| + std::pair<const Key, Value>* operator->() { return &entry_->keyValue(); } |
| + const std::pair<const Key, Value>* operator->() const { |
| + return &entry_->keyValue(); |
| + } |
| + |
| + Iterator operator++() { |
| + ++entry_; |
| + while (entry_ < end_ && !entry_->exists()) { |
| + ++entry_; |
| + } |
| + return *this; |
| + } |
| + |
| + Iterator operator++(int) { |
| + Iterator ret(*this); |
| + this->operator++(); |
| + return ret; |
| + } |
| + |
| + bool operator==(const Iterator& other) const { |
| + return entry_ == other.entry_; |
| + } |
| + |
| + bool operator!=(const Iterator& other) const { return entry_ != other.entry; } |
| + |
| + private: |
| + Entry* entry_; |
| + Entry* end_; |
| +}; |
| + |
| +template <typename Key, typename Value, typename Hash> |
| +class ZoneInlineHashMap |
| + : public InlineHashMap<Key, Value, Hash, ZoneAllocationPolicy> { |
| + public: |
| + explicit ZoneInlineHashMap( |
| + Zone* zone, |
| + uint32_t capacity = InlineHashMap< |
| + Key, Value, ZoneAllocationPolicy>::kDefaultHashMapCapacity) |
| + : InlineHashMap<Key, Value, Hash, ZoneAllocationPolicy>( |
| + capacity, ZoneAllocationPolicy(zone)) {} |
| +}; |
| + |
| +} // namespace internal |
| +} // namespace v8 |
| + |
| +#endif // V8_ZONE_TEMPLATE_HASH_MAP_H_ |