Coverage Report

Created: 2025-07-01 06:50

/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
}