/* * Copyright 2016 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef GRPC_CORE_LIB_SLICE_SLICE_HASH_TABLE_H #define GRPC_CORE_LIB_SLICE_SLICE_HASH_TABLE_H #include #include #include #include #include "src/core/lib/gpr/useful.h" #include "src/core/lib/gprpp/ref_counted.h" #include "src/core/lib/gprpp/ref_counted_ptr.h" #include "src/core/lib/slice/slice_internal.h" /// Hash table implementation. /// /// This implementation uses open addressing /// (https://en.wikipedia.org/wiki/Open_addressing) with linear /// probing (https://en.wikipedia.org/wiki/Linear_probing). /// /// The keys are \a grpc_slice objects. The values can be any type. /// /// Hash tables are intentionally immutable, to avoid the need for locking. namespace grpc_core { template class SliceHashTable : public RefCounted> { public: struct Entry { grpc_slice key; T value; bool is_set; }; // Function for comparing values. // TODO(roth): Eliminate this and the Cmp() method from this API once // grpc_channel_args is redesigned to require that keys are unique. typedef int (*ValueCmp)(const T&, const T&); /// Creates a new hash table containing \a entries, which is an array /// of length \a num_entries. Takes ownership of all keys and values in \a /// entries. If not null, \a value_cmp will be used to compare values in /// the context of \a Cmp(). If null, raw pointer (\a GPR_ICMP) comparison /// will be used. static RefCountedPtr Create(size_t num_entries, Entry* entries, ValueCmp value_cmp); /// Returns the value from the table associated with \a key. /// Returns null if \a key is not found. const T* Get(const grpc_slice& key) const; /// Compares \a a vs. \a b. /// A table is considered "smaller" (resp. "greater") if: /// - GPR_ICMP(a->value_cmp, b->value_cmp) < 1 (resp. > 1), /// - else, it contains fewer (resp. more) entries, /// - else, if strcmp(a_key, b_key) < 1 (resp. > 1), /// - else, if value_cmp(a_value, b_value) < 1 (resp. > 1). static int Cmp(const SliceHashTable& a, const SliceHashTable& b); private: // So New() can call our private ctor. template friend T2* New(Args&&... args); // So Delete() can call our private dtor. template friend void Delete(T2*); SliceHashTable(size_t num_entries, Entry* entries, ValueCmp value_cmp); virtual ~SliceHashTable(); void Add(grpc_slice key, T& value); // Default value comparison function, if none specified by caller. static int DefaultValueCmp(const T& a, const T& b) { return GPR_ICMP(a, b); } const ValueCmp value_cmp_; const size_t size_; size_t max_num_probes_; Entry* entries_; }; // // implementation -- no user-serviceable parts below // template RefCountedPtr> SliceHashTable::Create(size_t num_entries, Entry* entries, ValueCmp value_cmp) { return MakeRefCounted>(num_entries, entries, value_cmp); } template SliceHashTable::SliceHashTable(size_t num_entries, Entry* entries, ValueCmp value_cmp) : value_cmp_(value_cmp), // Keep load factor low to improve performance of lookups. size_(num_entries * 2), max_num_probes_(0) { entries_ = static_cast(gpr_zalloc(sizeof(Entry) * size_)); for (size_t i = 0; i < num_entries; ++i) { Entry* entry = &entries[i]; Add(entry->key, entry->value); } } template SliceHashTable::~SliceHashTable() { for (size_t i = 0; i < size_; ++i) { Entry& entry = entries_[i]; if (entry.is_set) { grpc_slice_unref_internal(entry.key); entry.value.~T(); } } gpr_free(entries_); } template void SliceHashTable::Add(grpc_slice key, T& value) { const size_t hash = grpc_slice_hash(key); for (size_t offset = 0; offset < size_; ++offset) { const size_t idx = (hash + offset) % size_; if (!entries_[idx].is_set) { entries_[idx].is_set = true; entries_[idx].key = key; entries_[idx].value = std::move(value); // Keep track of the maximum number of probes needed, since this // provides an upper bound for lookups. if (offset > max_num_probes_) max_num_probes_ = offset; return; } } GPR_ASSERT(false); // Table should never be full. } template const T* SliceHashTable::Get(const grpc_slice& key) const { const size_t hash = grpc_slice_hash(key); // We cap the number of probes at the max number recorded when // populating the table. for (size_t offset = 0; offset <= max_num_probes_; ++offset) { const size_t idx = (hash + offset) % size_; if (!entries_[idx].is_set) break; if (grpc_slice_eq(entries_[idx].key, key)) { return &entries_[idx].value; } } return nullptr; // Not found. } template int SliceHashTable::Cmp(const SliceHashTable& a, const SliceHashTable& b) { ValueCmp value_cmp_a = a.value_cmp_ != nullptr ? a.value_cmp_ : DefaultValueCmp; ValueCmp value_cmp_b = b.value_cmp_ != nullptr ? b.value_cmp_ : DefaultValueCmp; // Compare value_fns const int value_fns_cmp = GPR_ICMP((void*)value_cmp_a, (void*)value_cmp_b); if (value_fns_cmp != 0) return value_fns_cmp; // Compare sizes if (a.size_ < b.size_) return -1; if (a.size_ > b.size_) return 1; // Compare rows. for (size_t i = 0; i < a.size_; ++i) { if (!a.entries_[i].is_set) { if (b.entries_[i].is_set) { return -1; // a empty but b non-empty } continue; // both empty, no need to check key or value } else if (!b.entries_[i].is_set) { return 1; // a non-empty but b empty } // neither entry is empty const int key_cmp = grpc_slice_cmp(a.entries_[i].key, b.entries_[i].key); if (key_cmp != 0) return key_cmp; const int value_cmp = value_cmp_a(a.entries_[i].value, b.entries_[i].value); if (value_cmp != 0) return value_cmp; } return 0; } } // namespace grpc_core #endif /* GRPC_CORE_LIB_SLICE_SLICE_HASH_TABLE_H */