blob: 4bbcf88e895533cc773ae1f7a560052b5bc5db43 [file] [log] [blame]
/*
* 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 <grpc/support/port_platform.h>
#include <string.h>
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#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 <typename T>
class SliceHashTable : public RefCounted<SliceHashTable<T>> {
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<SliceHashTable> 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 <typename T2, typename... Args>
friend T2* New(Args&&... args);
// So Delete() can call our private dtor.
template <typename T2>
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 <typename T>
RefCountedPtr<SliceHashTable<T>> SliceHashTable<T>::Create(size_t num_entries,
Entry* entries,
ValueCmp value_cmp) {
return MakeRefCounted<SliceHashTable<T>>(num_entries, entries, value_cmp);
}
template <typename T>
SliceHashTable<T>::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<Entry*>(gpr_zalloc(sizeof(Entry) * size_));
for (size_t i = 0; i < num_entries; ++i) {
Entry* entry = &entries[i];
Add(entry->key, entry->value);
}
}
template <typename T>
SliceHashTable<T>::~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 <typename T>
void SliceHashTable<T>::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 <typename T>
const T* SliceHashTable<T>::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 <typename T>
int SliceHashTable<T>::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 */