• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/memory.h"
29 #include "src/core/lib/gprpp/ref_counted.h"
30 #include "src/core/lib/gprpp/ref_counted_ptr.h"
31 #include "src/core/lib/slice/slice_internal.h"
32 
33 /// Hash table implementation.
34 ///
35 /// This implementation uses open addressing
36 /// (https://en.wikipedia.org/wiki/Open_addressing) with linear
37 /// probing (https://en.wikipedia.org/wiki/Linear_probing).
38 ///
39 /// The keys are \a grpc_slice objects.  The values can be any type.
40 ///
41 /// Hash tables are intentionally immutable, to avoid the need for locking.
42 
43 namespace grpc_core {
44 
45 template <typename T>
46 class SliceHashTable : public RefCounted<SliceHashTable<T>> {
47  public:
48   struct Entry {
49     grpc_slice key;
50     T value;
51     bool is_set;
52   };
53 
54   // Function for comparing values.
55   // TODO(roth): Eliminate this and the Cmp() method from this API once
56   // grpc_channel_args is redesigned to require that keys are unique.
57   typedef int (*ValueCmp)(const T&, const T&);
58 
59   /// Creates a new hash table containing \a entries, which is an array
60   /// of length \a num_entries.  Takes ownership of all keys and values in \a
61   /// entries.  If not null, \a value_cmp will be used to compare values in
62   /// the context of \a Cmp(). If null, raw pointer (\a GPR_ICMP) comparison
63   /// will be used.
64   static RefCountedPtr<SliceHashTable> Create(size_t num_entries,
65                                               Entry* entries,
66                                               ValueCmp value_cmp);
67 
68   // Use Create function instead of using this directly.
69   SliceHashTable(size_t num_entries, Entry* entries, ValueCmp value_cmp);
70   virtual ~SliceHashTable();
71 
72   /// Returns the value from the table associated with \a key.
73   /// Returns null if \a key is not found.
74   const T* Get(const grpc_slice& key) const;
75 
76   /// Compares \a a vs. \a b.
77   /// A table is considered "smaller" (resp. "greater") if:
78   ///  - GPR_ICMP(a->value_cmp, b->value_cmp) < 1 (resp. > 1),
79   ///  - else, it contains fewer (resp. more) entries,
80   ///  - else, if strcmp(a_key, b_key) < 1 (resp. > 1),
81   ///  - else, if value_cmp(a_value, b_value) < 1 (resp. > 1).
82   static int Cmp(const SliceHashTable& a, const SliceHashTable& b);
83 
84  private:
85   void Add(const grpc_slice& key, T& value);
86 
87   // Default value comparison function, if none specified by caller.
DefaultValueCmp(const T & a,const T & b)88   static int DefaultValueCmp(const T& a, const T& b) { return GPR_ICMP(a, b); }
89 
90   const ValueCmp value_cmp_;
91   const size_t size_;
92   size_t max_num_probes_;
93   Entry* entries_;
94 };
95 
96 //
97 // implementation -- no user-serviceable parts below
98 //
99 
100 template <typename T>
Create(size_t num_entries,Entry * entries,ValueCmp value_cmp)101 RefCountedPtr<SliceHashTable<T>> SliceHashTable<T>::Create(size_t num_entries,
102                                                            Entry* entries,
103                                                            ValueCmp value_cmp) {
104   return MakeRefCounted<SliceHashTable<T>>(num_entries, entries, value_cmp);
105 }
106 
107 template <typename T>
SliceHashTable(size_t num_entries,Entry * entries,ValueCmp value_cmp)108 SliceHashTable<T>::SliceHashTable(size_t num_entries, Entry* entries,
109                                   ValueCmp value_cmp)
110     : value_cmp_(value_cmp),
111       // Keep load factor low to improve performance of lookups.
112       size_(num_entries * 2),
113       max_num_probes_(0) {
114   entries_ = static_cast<Entry*>(gpr_zalloc(sizeof(Entry) * size_));
115   for (size_t i = 0; i < num_entries; ++i) {
116     Entry* entry = &entries[i];
117     Add(entry->key, entry->value);
118   }
119 }
120 
121 template <typename T>
~SliceHashTable()122 SliceHashTable<T>::~SliceHashTable() {
123   for (size_t i = 0; i < size_; ++i) {
124     Entry& entry = entries_[i];
125     if (entry.is_set) {
126       grpc_slice_unref_internal(entry.key);
127       entry.value.~T();
128     }
129   }
130   gpr_free(entries_);
131 }
132 
133 template <typename T>
Add(const grpc_slice & key,T & value)134 void SliceHashTable<T>::Add(const grpc_slice& key, T& value) {
135   const size_t hash = grpc_slice_hash_internal(key);
136   for (size_t offset = 0; offset < size_; ++offset) {
137     const size_t idx = (hash + offset) % size_;
138     if (!entries_[idx].is_set) {
139       entries_[idx].is_set = true;
140       entries_[idx].key = key;
141       entries_[idx].value = std::move(value);
142       // Keep track of the maximum number of probes needed, since this
143       // provides an upper bound for lookups.
144       if (offset > max_num_probes_) max_num_probes_ = offset;
145       return;
146     }
147   }
148   GPR_ASSERT(false);  // Table should never be full.
149 }
150 
151 template <typename T>
Get(const grpc_slice & key)152 const T* SliceHashTable<T>::Get(const grpc_slice& key) const {
153   const size_t hash = grpc_slice_hash_internal(key);
154   // We cap the number of probes at the max number recorded when
155   // populating the table.
156   for (size_t offset = 0; offset <= max_num_probes_; ++offset) {
157     const size_t idx = (hash + offset) % size_;
158     if (!entries_[idx].is_set) break;
159     if (grpc_slice_eq(entries_[idx].key, key)) {
160       return &entries_[idx].value;
161     }
162   }
163   return nullptr;  // Not found.
164 }
165 
166 template <typename T>
Cmp(const SliceHashTable & a,const SliceHashTable & b)167 int SliceHashTable<T>::Cmp(const SliceHashTable& a, const SliceHashTable& b) {
168   ValueCmp value_cmp_a =
169       a.value_cmp_ != nullptr ? a.value_cmp_ : DefaultValueCmp;
170   ValueCmp value_cmp_b =
171       b.value_cmp_ != nullptr ? b.value_cmp_ : DefaultValueCmp;
172   // Compare value_fns
173   const int value_fns_cmp = GPR_ICMP((void*)value_cmp_a, (void*)value_cmp_b);
174   if (value_fns_cmp != 0) return value_fns_cmp;
175   // Compare sizes
176   if (a.size_ < b.size_) return -1;
177   if (a.size_ > b.size_) return 1;
178   // Compare rows.
179   for (size_t i = 0; i < a.size_; ++i) {
180     if (!a.entries_[i].is_set) {
181       if (b.entries_[i].is_set) {
182         return -1;  // a empty but b non-empty
183       }
184       continue;  // both empty, no need to check key or value
185     } else if (!b.entries_[i].is_set) {
186       return 1;  // a non-empty but b empty
187     }
188     // neither entry is empty
189     const int key_cmp = grpc_slice_cmp(a.entries_[i].key, b.entries_[i].key);
190     if (key_cmp != 0) return key_cmp;
191     const int value_cmp = value_cmp_a(a.entries_[i].value, b.entries_[i].value);
192     if (value_cmp != 0) return value_cmp;
193   }
194   return 0;
195 }
196 
197 }  // namespace grpc_core
198 
199 #endif /* GRPC_CORE_LIB_SLICE_SLICE_HASH_TABLE_H */
200