1 /*
2 * Copyright 2016 gRPC authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #ifndef GRPC_CORE_LIB_SLICE_SLICE_HASH_TABLE_H
18 #define GRPC_CORE_LIB_SLICE_SLICE_HASH_TABLE_H
19
20 #include <grpc/support/port_platform.h>
21
22 #include <string.h>
23
24 #include <grpc/support/alloc.h>
25 #include <grpc/support/log.h>
26
27 #include "src/core/lib/gpr/useful.h"
28 #include "src/core/lib/gprpp/ref_counted.h"
29 #include "src/core/lib/gprpp/ref_counted_ptr.h"
30 #include "src/core/lib/slice/slice_internal.h"
31
32 /// Hash table implementation.
33 ///
34 /// This implementation uses open addressing
35 /// (https://en.wikipedia.org/wiki/Open_addressing) with linear
36 /// probing (https://en.wikipedia.org/wiki/Linear_probing).
37 ///
38 /// The keys are \a grpc_slice objects. The values can be any type.
39 ///
40 /// Hash tables are intentionally immutable, to avoid the need for locking.
41
42 namespace grpc_core {
43
44 template <typename T>
45 class SliceHashTable : public RefCounted<SliceHashTable<T>> {
46 public:
47 struct Entry {
48 grpc_slice key;
49 T value;
50 bool is_set;
51 };
52
53 // Function for comparing values.
54 // TODO(roth): Eliminate this and the Cmp() method from this API once
55 // grpc_channel_args is redesigned to require that keys are unique.
56 typedef int (*ValueCmp)(const T&, const T&);
57
58 /// Creates a new hash table containing \a entries, which is an array
59 /// of length \a num_entries. Takes ownership of all keys and values in \a
60 /// entries. If not null, \a value_cmp will be used to compare values in
61 /// the context of \a Cmp(). If null, raw pointer (\a GPR_ICMP) comparison
62 /// will be used.
63 static RefCountedPtr<SliceHashTable> Create(size_t num_entries,
64 Entry* entries,
65 ValueCmp value_cmp);
66
67 /// Returns the value from the table associated with \a key.
68 /// Returns null if \a key is not found.
69 const T* Get(const grpc_slice& key) const;
70
71 /// Compares \a a vs. \a b.
72 /// A table is considered "smaller" (resp. "greater") if:
73 /// - GPR_ICMP(a->value_cmp, b->value_cmp) < 1 (resp. > 1),
74 /// - else, it contains fewer (resp. more) entries,
75 /// - else, if strcmp(a_key, b_key) < 1 (resp. > 1),
76 /// - else, if value_cmp(a_value, b_value) < 1 (resp. > 1).
77 static int Cmp(const SliceHashTable& a, const SliceHashTable& b);
78
79 private:
80 // So New() can call our private ctor.
81 template <typename T2, typename... Args>
82 friend T2* New(Args&&... args);
83
84 // So Delete() can call our private dtor.
85 template <typename T2>
86 friend void Delete(T2*);
87
88 SliceHashTable(size_t num_entries, Entry* entries, ValueCmp value_cmp);
89 virtual ~SliceHashTable();
90
91 void Add(grpc_slice key, T& value);
92
93 // Default value comparison function, if none specified by caller.
DefaultValueCmp(const T & a,const T & b)94 static int DefaultValueCmp(const T& a, const T& b) { return GPR_ICMP(a, b); }
95
96 const ValueCmp value_cmp_;
97 const size_t size_;
98 size_t max_num_probes_;
99 Entry* entries_;
100 };
101
102 //
103 // implementation -- no user-serviceable parts below
104 //
105
106 template <typename T>
Create(size_t num_entries,Entry * entries,ValueCmp value_cmp)107 RefCountedPtr<SliceHashTable<T>> SliceHashTable<T>::Create(size_t num_entries,
108 Entry* entries,
109 ValueCmp value_cmp) {
110 return MakeRefCounted<SliceHashTable<T>>(num_entries, entries, value_cmp);
111 }
112
113 template <typename T>
SliceHashTable(size_t num_entries,Entry * entries,ValueCmp value_cmp)114 SliceHashTable<T>::SliceHashTable(size_t num_entries, Entry* entries,
115 ValueCmp value_cmp)
116 : value_cmp_(value_cmp),
117 // Keep load factor low to improve performance of lookups.
118 size_(num_entries * 2),
119 max_num_probes_(0) {
120 entries_ = static_cast<Entry*>(gpr_zalloc(sizeof(Entry) * size_));
121 for (size_t i = 0; i < num_entries; ++i) {
122 Entry* entry = &entries[i];
123 Add(entry->key, entry->value);
124 }
125 }
126
127 template <typename T>
~SliceHashTable()128 SliceHashTable<T>::~SliceHashTable() {
129 for (size_t i = 0; i < size_; ++i) {
130 Entry& entry = entries_[i];
131 if (entry.is_set) {
132 grpc_slice_unref_internal(entry.key);
133 entry.value.~T();
134 }
135 }
136 gpr_free(entries_);
137 }
138
139 template <typename T>
Add(grpc_slice key,T & value)140 void SliceHashTable<T>::Add(grpc_slice key, T& value) {
141 const size_t hash = grpc_slice_hash(key);
142 for (size_t offset = 0; offset < size_; ++offset) {
143 const size_t idx = (hash + offset) % size_;
144 if (!entries_[idx].is_set) {
145 entries_[idx].is_set = true;
146 entries_[idx].key = key;
147 entries_[idx].value = std::move(value);
148 // Keep track of the maximum number of probes needed, since this
149 // provides an upper bound for lookups.
150 if (offset > max_num_probes_) max_num_probes_ = offset;
151 return;
152 }
153 }
154 GPR_ASSERT(false); // Table should never be full.
155 }
156
157 template <typename T>
Get(const grpc_slice & key)158 const T* SliceHashTable<T>::Get(const grpc_slice& key) const {
159 const size_t hash = grpc_slice_hash(key);
160 // We cap the number of probes at the max number recorded when
161 // populating the table.
162 for (size_t offset = 0; offset <= max_num_probes_; ++offset) {
163 const size_t idx = (hash + offset) % size_;
164 if (!entries_[idx].is_set) break;
165 if (grpc_slice_eq(entries_[idx].key, key)) {
166 return &entries_[idx].value;
167 }
168 }
169 return nullptr; // Not found.
170 }
171
172 template <typename T>
Cmp(const SliceHashTable & a,const SliceHashTable & b)173 int SliceHashTable<T>::Cmp(const SliceHashTable& a, const SliceHashTable& b) {
174 ValueCmp value_cmp_a =
175 a.value_cmp_ != nullptr ? a.value_cmp_ : DefaultValueCmp;
176 ValueCmp value_cmp_b =
177 b.value_cmp_ != nullptr ? b.value_cmp_ : DefaultValueCmp;
178 // Compare value_fns
179 const int value_fns_cmp = GPR_ICMP((void*)value_cmp_a, (void*)value_cmp_b);
180 if (value_fns_cmp != 0) return value_fns_cmp;
181 // Compare sizes
182 if (a.size_ < b.size_) return -1;
183 if (a.size_ > b.size_) return 1;
184 // Compare rows.
185 for (size_t i = 0; i < a.size_; ++i) {
186 if (!a.entries_[i].is_set) {
187 if (b.entries_[i].is_set) {
188 return -1; // a empty but b non-empty
189 }
190 continue; // both empty, no need to check key or value
191 } else if (!b.entries_[i].is_set) {
192 return 1; // a non-empty but b empty
193 }
194 // neither entry is empty
195 const int key_cmp = grpc_slice_cmp(a.entries_[i].key, b.entries_[i].key);
196 if (key_cmp != 0) return key_cmp;
197 const int value_cmp = value_cmp_a(a.entries_[i].value, b.entries_[i].value);
198 if (value_cmp != 0) return value_cmp;
199 }
200 return 0;
201 }
202
203 } // namespace grpc_core
204
205 #endif /* GRPC_CORE_LIB_SLICE_SLICE_HASH_TABLE_H */
206