/src/openvswitch/lib/hmap.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2008, 2009, 2010, 2012, 2013, 2015, 2019 Nicira, Inc. |
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 | | #include <config.h> |
18 | | #include "openvswitch/hmap.h" |
19 | | #include <stdint.h> |
20 | | #include <string.h> |
21 | | #include "coverage.h" |
22 | | #include "random.h" |
23 | | #include "util.h" |
24 | | #include "openvswitch/vlog.h" |
25 | | |
26 | | VLOG_DEFINE_THIS_MODULE(hmap); |
27 | | |
28 | | COVERAGE_DEFINE(hmap_pathological); |
29 | | COVERAGE_DEFINE(hmap_expand); |
30 | | COVERAGE_DEFINE(hmap_shrink); |
31 | | COVERAGE_DEFINE(hmap_reserve); |
32 | | |
33 | | /* Initializes 'hmap' as an empty hash table. */ |
34 | | void |
35 | | hmap_init(struct hmap *hmap) |
36 | 0 | { |
37 | 0 | hmap->buckets = &hmap->one; |
38 | 0 | hmap->one = NULL; |
39 | 0 | hmap->mask = 0; |
40 | 0 | hmap->n = 0; |
41 | 0 | } |
42 | | |
43 | | /* Frees memory reserved by 'hmap'. It is the client's responsibility to free |
44 | | * the nodes themselves, if necessary. */ |
45 | | void |
46 | | hmap_destroy(struct hmap *hmap) |
47 | 0 | { |
48 | 0 | if (hmap && hmap->buckets != &hmap->one) { |
49 | 0 | free(hmap->buckets); |
50 | 0 | } |
51 | 0 | } |
52 | | |
53 | | /* Removes all node from 'hmap', leaving it ready to accept more nodes. Does |
54 | | * not free memory allocated for 'hmap'. |
55 | | * |
56 | | * This function is appropriate when 'hmap' will soon have about as many |
57 | | * elements as it did before. If 'hmap' will likely have fewer elements than |
58 | | * before, use hmap_destroy() followed by hmap_init() to save memory and |
59 | | * iteration time. */ |
60 | | void |
61 | | hmap_clear(struct hmap *hmap) |
62 | 0 | { |
63 | 0 | if (hmap->n > 0) { |
64 | 0 | hmap->n = 0; |
65 | 0 | memset(hmap->buckets, 0, (hmap->mask + 1) * sizeof *hmap->buckets); |
66 | 0 | } |
67 | 0 | } |
68 | | |
69 | | /* Exchanges hash maps 'a' and 'b'. */ |
70 | | void |
71 | | hmap_swap(struct hmap *a, struct hmap *b) |
72 | 0 | { |
73 | 0 | struct hmap tmp = *a; |
74 | 0 | *a = *b; |
75 | 0 | *b = tmp; |
76 | 0 | hmap_moved(a); |
77 | 0 | hmap_moved(b); |
78 | 0 | } |
79 | | |
80 | | /* Adjusts 'hmap' to compensate for having moved position in memory (e.g. due |
81 | | * to realloc()). */ |
82 | | void |
83 | | hmap_moved(struct hmap *hmap) |
84 | 0 | { |
85 | 0 | if (!hmap->mask) { |
86 | 0 | hmap->buckets = &hmap->one; |
87 | 0 | } |
88 | 0 | } |
89 | | |
90 | | static void |
91 | | resize(struct hmap *hmap, size_t new_mask, const char *where) |
92 | 0 | { |
93 | 0 | struct hmap tmp; |
94 | 0 | size_t i; |
95 | |
|
96 | 0 | ovs_assert(is_pow2(new_mask + 1)); |
97 | |
|
98 | 0 | hmap_init(&tmp); |
99 | 0 | if (new_mask) { |
100 | 0 | tmp.buckets = xmalloc(sizeof *tmp.buckets * (new_mask + 1)); |
101 | 0 | tmp.mask = new_mask; |
102 | 0 | for (i = 0; i <= tmp.mask; i++) { |
103 | 0 | tmp.buckets[i] = NULL; |
104 | 0 | } |
105 | 0 | } |
106 | 0 | int n_big_buckets = 0; |
107 | 0 | int biggest_count = 0; |
108 | 0 | int n_biggest_buckets = 0; |
109 | 0 | for (i = 0; i <= hmap->mask; i++) { |
110 | 0 | struct hmap_node *node, *next; |
111 | 0 | int count = 0; |
112 | 0 | for (node = hmap->buckets[i]; node; node = next) { |
113 | 0 | next = node->next; |
114 | 0 | hmap_insert_fast(&tmp, node, node->hash); |
115 | 0 | count++; |
116 | 0 | } |
117 | 0 | if (count > 5) { |
118 | 0 | n_big_buckets++; |
119 | 0 | if (count > biggest_count) { |
120 | 0 | biggest_count = count; |
121 | 0 | n_biggest_buckets = 1; |
122 | 0 | } else if (count == biggest_count) { |
123 | 0 | n_biggest_buckets++; |
124 | 0 | } |
125 | 0 | } |
126 | 0 | } |
127 | 0 | hmap_swap(hmap, &tmp); |
128 | 0 | hmap_destroy(&tmp); |
129 | |
|
130 | 0 | if (n_big_buckets) { |
131 | 0 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 10); |
132 | 0 | COVERAGE_INC(hmap_pathological); |
133 | 0 | VLOG_DBG_RL(&rl, "%s: %d bucket%s with 6+ nodes, " |
134 | 0 | "including %d bucket%s with %d nodes " |
135 | 0 | "(%"PRIuSIZE" nodes total across %"PRIuSIZE" buckets)", |
136 | 0 | where, |
137 | 0 | n_big_buckets, n_big_buckets > 1 ? "s" : "", |
138 | 0 | n_biggest_buckets, n_biggest_buckets > 1 ? "s" : "", |
139 | 0 | biggest_count, |
140 | 0 | hmap->n, hmap->mask + 1); |
141 | 0 | } |
142 | 0 | } |
143 | | |
144 | | static size_t |
145 | | calc_mask(size_t capacity) |
146 | 0 | { |
147 | 0 | size_t mask = capacity / 2; |
148 | 0 | mask |= mask >> 1; |
149 | 0 | mask |= mask >> 2; |
150 | 0 | mask |= mask >> 4; |
151 | 0 | mask |= mask >> 8; |
152 | 0 | mask |= mask >> 16; |
153 | 0 | #if SIZE_MAX > UINT32_MAX |
154 | 0 | mask |= mask >> 32; |
155 | 0 | #endif |
156 | | |
157 | | /* If we need to dynamically allocate buckets we might as well allocate at |
158 | | * least 4 of them. */ |
159 | 0 | mask |= (mask & 1) << 1; |
160 | |
|
161 | 0 | return mask; |
162 | 0 | } |
163 | | |
164 | | /* Expands 'hmap', if necessary, to optimize the performance of searches. |
165 | | * |
166 | | * ('where' is used in debug logging. Commonly one would use hmap_expand() to |
167 | | * automatically provide the caller's source file and line number for |
168 | | * 'where'.) */ |
169 | | void |
170 | | hmap_expand_at(struct hmap *hmap, const char *where) |
171 | 0 | { |
172 | 0 | size_t new_mask = calc_mask(hmap->n); |
173 | 0 | if (new_mask > hmap->mask) { |
174 | 0 | COVERAGE_INC(hmap_expand); |
175 | 0 | resize(hmap, new_mask, where); |
176 | 0 | } |
177 | 0 | } |
178 | | |
179 | | /* Shrinks 'hmap', if necessary, to optimize the performance of iteration. |
180 | | * |
181 | | * ('where' is used in debug logging. Commonly one would use hmap_shrink() to |
182 | | * automatically provide the caller's source file and line number for |
183 | | * 'where'.) */ |
184 | | void |
185 | | hmap_shrink_at(struct hmap *hmap, const char *where) |
186 | 0 | { |
187 | 0 | size_t new_mask = calc_mask(hmap->n); |
188 | 0 | if (new_mask < hmap->mask) { |
189 | 0 | COVERAGE_INC(hmap_shrink); |
190 | 0 | resize(hmap, new_mask, where); |
191 | 0 | } |
192 | 0 | } |
193 | | |
194 | | /* Expands 'hmap', if necessary, to optimize the performance of searches when |
195 | | * it has up to 'n' elements. (But iteration will be slow in a hash map whose |
196 | | * allocated capacity is much higher than its current number of nodes.) |
197 | | * |
198 | | * ('where' is used in debug logging. Commonly one would use hmap_reserve() to |
199 | | * automatically provide the caller's source file and line number for |
200 | | * 'where'.) */ |
201 | | void |
202 | | hmap_reserve_at(struct hmap *hmap, size_t n, const char *where) |
203 | 0 | { |
204 | 0 | size_t new_mask = calc_mask(n); |
205 | 0 | if (new_mask > hmap->mask) { |
206 | 0 | COVERAGE_INC(hmap_reserve); |
207 | 0 | resize(hmap, new_mask, where); |
208 | 0 | } |
209 | 0 | } |
210 | | |
211 | | /* Adjusts 'hmap' to compensate for 'old_node' having moved position in memory |
212 | | * to 'node' (e.g. due to realloc()). */ |
213 | | void |
214 | | hmap_node_moved(struct hmap *hmap, |
215 | | struct hmap_node *old_node, struct hmap_node *node) |
216 | 0 | { |
217 | 0 | struct hmap_node **bucket = &hmap->buckets[node->hash & hmap->mask]; |
218 | 0 | while (*bucket != old_node) { |
219 | 0 | bucket = &(*bucket)->next; |
220 | 0 | } |
221 | 0 | *bucket = node; |
222 | 0 | } |
223 | | |
224 | | /* Chooses and returns a randomly selected node from 'hmap', which must not be |
225 | | * empty. |
226 | | * |
227 | | * I wouldn't depend on this algorithm to be fair, since I haven't analyzed it. |
228 | | * But it does at least ensure that any node in 'hmap' can be chosen. */ |
229 | | struct hmap_node * |
230 | | hmap_random_node(const struct hmap *hmap) |
231 | 0 | { |
232 | 0 | struct hmap_node *bucket, *node; |
233 | 0 | size_t n, i; |
234 | | |
235 | | /* Choose a random non-empty bucket. */ |
236 | 0 | for (;;) { |
237 | 0 | bucket = hmap->buckets[random_uint32() & hmap->mask]; |
238 | 0 | if (bucket) { |
239 | 0 | break; |
240 | 0 | } |
241 | 0 | } |
242 | | |
243 | | /* Count nodes in bucket. */ |
244 | 0 | n = 0; |
245 | 0 | for (node = bucket; node; node = node->next) { |
246 | 0 | n++; |
247 | 0 | } |
248 | | |
249 | | /* Choose random node from bucket. */ |
250 | 0 | i = random_range(n); |
251 | 0 | for (node = bucket; i-- > 0; node = node->next) { |
252 | 0 | continue; |
253 | 0 | } |
254 | 0 | return node; |
255 | 0 | } |
256 | | |
257 | | /* Returns the next node in 'hmap' in hash order, or NULL if no nodes remain in |
258 | | * 'hmap'. Uses '*pos' to determine where to begin iteration, and updates |
259 | | * '*pos' to pass on the next iteration into them before returning. |
260 | | * |
261 | | * It's better to use plain HMAP_FOR_EACH and related functions, since they are |
262 | | * faster and better at dealing with hmaps that change during iteration. |
263 | | * |
264 | | * Before beginning iteration, set '*pos' to all zeros. */ |
265 | | struct hmap_node * |
266 | | hmap_at_position(const struct hmap *hmap, |
267 | | struct hmap_position *pos) |
268 | 0 | { |
269 | 0 | size_t offset; |
270 | 0 | size_t b_idx; |
271 | |
|
272 | 0 | offset = pos->offset; |
273 | 0 | for (b_idx = pos->bucket; b_idx <= hmap->mask; b_idx++) { |
274 | 0 | struct hmap_node *node; |
275 | 0 | size_t n_idx; |
276 | |
|
277 | 0 | for (n_idx = 0, node = hmap->buckets[b_idx]; node != NULL; |
278 | 0 | n_idx++, node = node->next) { |
279 | 0 | if (n_idx == offset) { |
280 | 0 | if (node->next) { |
281 | 0 | pos->bucket = node->hash & hmap->mask; |
282 | 0 | pos->offset = offset + 1; |
283 | 0 | } else { |
284 | 0 | pos->bucket = (node->hash & hmap->mask) + 1; |
285 | 0 | pos->offset = 0; |
286 | 0 | } |
287 | 0 | return node; |
288 | 0 | } |
289 | 0 | } |
290 | 0 | offset = 0; |
291 | 0 | } |
292 | | |
293 | 0 | pos->bucket = 0; |
294 | 0 | pos->offset = 0; |
295 | 0 | return NULL; |
296 | 0 | } |
297 | | |
298 | | /* Returns true if 'node' is in 'hmap', false otherwise. */ |
299 | | bool |
300 | | hmap_contains(const struct hmap *hmap, const struct hmap_node *node) |
301 | 0 | { |
302 | 0 | struct hmap_node *p; |
303 | |
|
304 | 0 | for (p = hmap_first_in_bucket(hmap, node->hash); p; p = p->next) { |
305 | 0 | if (p == node) { |
306 | 0 | return true; |
307 | 0 | } |
308 | 0 | } |
309 | | |
310 | 0 | return false; |
311 | 0 | } |