2 * Copyright (c) 2017 Cisco and/or its affiliates.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at:
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
17 * cuckoo hash implementation based on paper
18 * 'Algorithmic Improvements for Fast Concurrent Cuckoo Hashing'
19 * by Xiaozhou Li, David G. Andersen, Michael Kaminsky, Michael J. Freedman
20 * and their libcuckoo implementation (https://github.com/efficient/libcuckoo)
23 #include <vppinfra/vec.h>
24 #include <vppinfra/cuckoo_template.h>
26 int CV (clib_cuckoo_search) (CVT (clib_cuckoo) * h,
27 CVT (clib_cuckoo_kv) * search_v,
28 CVT (clib_cuckoo_kv) * return_v)
30 CVT (clib_cuckoo_kv) tmp = *search_v;
31 int rv = CV (clib_cuckoo_search_inline) (h, &tmp);
32 if (CLIB_CUCKOO_ERROR_SUCCESS == rv)
40 CVT (clib_cuckoo_bucket) *
41 CV (clib_cuckoo_bucket_at_index) (CVT (clib_cuckoo) * h, uword bucket)
43 return vec_elt_at_index (h->buckets, bucket);
46 static uword CV (clib_cuckoo_get_nbuckets) (CVT (clib_cuckoo) * h)
48 return vec_len (h->buckets);
52 CV (clib_cuckoo_elt_in_bucket_to_offset) (CVT (clib_cuckoo_bucket) * b,
53 CVT (clib_cuckoo_kv) * e)
55 ASSERT (e >= b->elts);
56 ASSERT (e <= &b->elts[sizeof (b->elts) / sizeof (b->elts[0]) - 1]);
60 u8 *CV (format_cuckoo_elt) (u8 * s, va_list * args)
62 CVT (clib_cuckoo_kv) * elt = va_arg (*args, CVT (clib_cuckoo_kv) *);
63 unsigned reduced_hash = va_arg (*args, unsigned);
64 if (CV (clib_cuckoo_kv_is_free) (elt))
66 s = format (s, "[ -- empty -- ]");
70 s = format (s, "[%U, reduced hash: %u]", CV (format_cuckoo_kvp), elt,
76 u8 *CV (format_cuckoo_bucket) (u8 * s, va_list * args)
78 CVT (clib_cuckoo_bucket) * bucket =
79 va_arg (*args, CVT (clib_cuckoo_bucket) *);
83 clib_cuckoo_bucket_foreach_idx (i)
85 CVT (clib_cuckoo_kv) *elt = bucket->elts + i;
86 s = format (s, "bucket %p, offset %d: %U\n", bucket, i,
87 CV (format_cuckoo_elt), elt, bucket->reduced_hashes[i]);
90 clib_cuckoo_bucket_aux_t aux = bucket->aux;
91 s = format (s, "version: %lld, use count: %d\n",
92 clib_cuckoo_bucket_aux_get_version (aux),
93 clib_cuckoo_bucket_aux_get_use_count (aux));
98 static void CV (clib_cuckoo_deep_self_check) (CVT (clib_cuckoo) * h)
100 CVT (clib_cuckoo_bucket) * bucket;
101 uword bucket_idx = 0;
103 clib_cuckoo_foreach_bucket (bucket, h)
107 clib_cuckoo_bucket_foreach_idx (i)
109 CVT (clib_cuckoo_kv) *elt = bucket->elts + i;
110 if (!CV (clib_cuckoo_kv_is_free) (elt))
112 u64 hash = CV (clib_cuckoo_hash) (elt);
113 clib_cuckoo_lookup_info_t lookup = CV (clib_cuckoo_calc_lookup) (
114 CV (clib_cuckoo_get_snapshot) (h), hash);
115 CVT (clib_cuckoo_kv) kv = *elt;
116 int rv = CV (clib_cuckoo_search) (h, &kv, &kv);
117 if (CLIB_CUCKOO_ERROR_SUCCESS != rv)
119 CLIB_CUCKOO_DBG ("Search for elt `%U' failed!",
120 CV (format_cuckoo_elt), elt,
121 bucket->reduced_hashes[i]);
122 CLIB_CUCKOO_DBG ("%U", CV (format_cuckoo), h, 1);
124 ASSERT (aux.bucket1 == bucket_idx || aux.bucket2 == bucket_idx);
125 ASSERT (CLIB_CUCKOO_ERROR_SUCCESS == rv);
129 clib_cuckoo_bucket_aux_t aux = bucket->aux;
130 ASSERT (used == clib_cuckoo_bucket_aux_get_use_count (aux));
134 // CLIB_CUCKOO_DBG ("Deep self check passed: %U", CV (format_cuckoo), h);
137 #define CLIB_CUCKOO_DEEP_SELF_CHECK(h) CV (clib_cuckoo_deep_self_check) (h)
138 #define CLIB_CUCKOO_ASSERT_BUCKET_SORTED(b) \
142 int min_free = CLIB_CUCKOO_KVP_PER_BUCKET; \
144 clib_cuckoo_bucket_foreach_idx (i) \
146 if (!CV (clib_cuckoo_kv_is_free) (b->elts + i)) \
150 if (CV (clib_cuckoo_kv_is_free) (b->elts + \
151 (CLIB_CUCKOO_KVP_PER_BUCKET - i))) \
156 ASSERT (min_free > max_used); \
161 #define CLIB_CUCKOO_DEEP_SELF_CHECK(h)
162 #define CLIB_CUCKOO_ASSERT_BUCKET_SORTED(b)
165 void CV (clib_cuckoo_init) (CVT (clib_cuckoo) * h, const char *name,
167 void (*garbage_callback) (CVT (clib_cuckoo) *,
171 uword log2_nbuckets = max_log2 (nbuckets);
172 nbuckets = 1ULL << (log2_nbuckets);
173 CLIB_CUCKOO_DBG ("New cuckoo, adjusted nbuckets %wu", nbuckets);
174 CVT (clib_cuckoo_bucket) * buckets = NULL;
175 vec_validate_aligned (buckets, nbuckets - 1, CLIB_CACHE_LINE_BYTES);
176 ASSERT (nbuckets == vec_len (buckets));
177 h->buckets = buckets;
178 clib_spinlock_init (&h->writer_lock);
179 /* mark all elements free ... */
180 CVT (clib_cuckoo_bucket) * bucket;
182 clib_cuckoo_foreach_bucket (
183 bucket, h, { clib_memset (bucket->elts, 0xff, sizeof (bucket->elts)); });
186 h->garbage_callback = garbage_callback;
187 h->garbage_ctx = garbage_ctx;
190 void CV (clib_cuckoo_free) (CVT (clib_cuckoo) * h)
192 clib_memset (h, 0, sizeof (*h));
195 static clib_cuckoo_bucket_aux_t
196 CV (clib_cuckoo_bucket_version_bump_and_lock) (CVT (clib_cuckoo_bucket) * b)
198 clib_cuckoo_bucket_aux_t aux = b->aux;
199 u64 version = clib_cuckoo_bucket_aux_get_version (aux);
200 u8 use_count = clib_cuckoo_bucket_aux_get_use_count (aux);
201 u8 writer_flag = clib_cuckoo_bucket_aux_get_writer_flag (aux);
202 ASSERT (0 == writer_flag);
203 aux = clib_cuckoo_bucket_aux_pack (version + 1, use_count, 1);
208 static void CV (clib_cuckoo_bucket_unlock) (CVT (clib_cuckoo_bucket) * b,
209 clib_cuckoo_bucket_aux_t aux)
211 u64 version = clib_cuckoo_bucket_aux_get_version (aux);
212 u8 use_count = clib_cuckoo_bucket_aux_get_use_count (aux);
213 u8 writer_flag = clib_cuckoo_bucket_aux_get_writer_flag (aux);
214 ASSERT (1 == writer_flag);
215 aux = clib_cuckoo_bucket_aux_pack (version, use_count, 0);
219 #define CLIB_CUCKOO_DEBUG_PATH (1)
220 #define CLIB_CUCKOO_DEBUG_PATH_DETAIL (0)
222 #if CLIB_CUCKOO_DEBUG && CLIB_CUCKOO_DEBUG_PATH
223 static u8 *CV (format_cuckoo_path) (u8 * s, va_list * args);
226 static clib_cuckoo_path_t *CV (clib_cuckoo_path_get) (CVT (clib_cuckoo) * h)
228 clib_cuckoo_path_t *path;
229 pool_get (h->paths, path);
230 clib_memset (path, 0, sizeof (*path));
231 #if CLIB_CUCKOO_DEBUG_PATH_DETAIL
232 CLIB_CUCKOO_DBG ("Get path @%lu", (long unsigned) (path - h->paths));
237 static void CV (clib_cuckoo_path_put) (CVT (clib_cuckoo) * h, uword path_idx)
239 clib_cuckoo_path_t *path = pool_elt_at_index (h->paths, path_idx);
240 #if CLIB_CUCKOO_DEBUG_PATH_DETAIL
241 CLIB_CUCKOO_DBG ("Put path @%lu", (long unsigned) (path - h->paths));
243 pool_put (h->paths, path);
246 static clib_cuckoo_path_t *CV (clib_cuckoo_path_begin) (CVT (clib_cuckoo) * h,
250 ASSERT (next_offset < CLIB_CUCKOO_KVP_PER_BUCKET);
251 clib_cuckoo_path_t *new_path = CV (clib_cuckoo_path_get) (h);
252 new_path->length = 1;
253 new_path->data = next_offset;
254 new_path->start = bucket;
255 new_path->bucket = bucket;
256 #if CLIB_CUCKOO_DEBUG_PATH
257 CLIB_CUCKOO_DBG ("Create new path @%wu, length: %u data: %llu bucket: %wu "
259 new_path - h->paths, new_path->length,
260 (long long unsigned) new_path->data, new_path->bucket,
267 * create a new path based on existing path extended by adding a bucket
270 static uword CV (clib_cuckoo_path_extend) (CVT (clib_cuckoo) * h,
271 uword path_idx, uword bucket,
274 ASSERT (offset < CLIB_CUCKOO_KVP_PER_BUCKET);
275 clib_cuckoo_path_t *new_path = CV (clib_cuckoo_path_get) (h);
276 uword new_path_idx = new_path - h->paths;
277 clib_cuckoo_path_t *path = pool_elt_at_index (h->paths, path_idx);
278 new_path->start = path->start;
279 new_path->length = path->length + 1;
280 new_path->data = (path->data << CLIB_CUCKOO_LOG2_KVP_PER_BUCKET) + offset;
281 new_path->bucket = bucket;
282 #if CLIB_CUCKOO_DEBUG_PATH
283 CLIB_CUCKOO_DBG ("Extend path @%wu, new path @%wu, %U", path_idx,
284 new_path_idx, CV (format_cuckoo_path), h, new_path_idx);
289 /** return the offset of the last element in the path */
290 static uword CV (clib_cuckoo_path_peek_offset) (const clib_cuckoo_path_t *
293 ASSERT (path->length > 0);
294 uword mask = (1 << CLIB_CUCKOO_LOG2_KVP_PER_BUCKET) - 1;
295 uword offset = path->data & mask;
300 CVT (clib_cuckoo_kv) *
301 CV (clib_cuckoo_bucket_find_empty) (CVT (clib_cuckoo_bucket) * bucket)
303 clib_cuckoo_bucket_aux_t aux = bucket->aux;
304 u8 use_count = clib_cuckoo_bucket_aux_get_use_count (aux);
305 if (use_count < CLIB_CUCKOO_KVP_PER_BUCKET)
307 return bucket->elts + use_count;
313 * walk the cuckoo path two ways,
314 * first backwards, extracting offsets,
315 * then forward, extracting buckets
317 * buckets and offsets are arrays filled with elements extracted from path
318 * the arrays must be able to contain CLIB_CUCKOO_BFS_MAX_PATH_LENGTH elements
321 clib_cuckoo_path_walk (CVT (clib_cuckoo) * h, uword path_idx,
322 uword * buckets, uword * offsets)
324 clib_cuckoo_path_t *path = pool_elt_at_index (h->paths, path_idx);
325 ASSERT (path->length > 0);
326 u64 data = path->data;
327 uword mask = (1 << CLIB_CUCKOO_LOG2_KVP_PER_BUCKET) - 1;
329 for (i = path->length; i > 0; --i)
331 uword offset = data & mask;
332 offsets[i - 1] = offset;
333 data >>= CLIB_CUCKOO_LOG2_KVP_PER_BUCKET;
335 buckets[0] = path->start;
336 for (i = 1; i < path->length; ++i)
338 CVT (clib_cuckoo_bucket) * b =
339 CV (clib_cuckoo_bucket_at_index) (h, buckets[i - 1]);
341 clib_cuckoo_get_other_bucket (CV (clib_cuckoo_get_nbuckets) (h),
343 b->reduced_hashes[offsets[i - 1]]);
347 #if CLIB_CUCKOO_DEBUG && CLIB_CUCKOO_DEBUG_PATH
348 static u8 *CV (format_cuckoo_path) (u8 * s, va_list * args)
350 CVT (clib_cuckoo) * h = va_arg (*args, CVT (clib_cuckoo) *);
351 uword path_idx = va_arg (*args, uword);
352 clib_cuckoo_path_t *p = pool_elt_at_index (h->paths, path_idx);
353 uword buckets[CLIB_CUCKOO_BFS_MAX_PATH_LENGTH];
354 uword offsets[CLIB_CUCKOO_BFS_MAX_PATH_LENGTH];
355 clib_cuckoo_path_walk (h, path_idx, buckets, offsets);
356 s = format (s, "length %u: ", p->length);
357 for (uword i = p->length - 1; i > 0; --i)
359 s = format (s, "%wu[%wu]->", buckets[i], offsets[i]);
363 s = format (s, "%wu[%wu]", buckets[0], offsets[0]);
370 * perform breadth-first search in the cuckoo hash, finding the closest
371 * empty slot, i.e. one which requires minimum swaps to move it
372 * to one of the buckets provided
374 static int CV (clib_cuckoo_find_empty_slot_bfs) (CVT (clib_cuckoo) * h,
375 clib_cuckoo_lookup_info_t *
376 lookup, uword * path_idx_out,
377 uword * found_bucket,
378 CVT (clib_cuckoo_kv) *
382 ASSERT (!vec_len (h->bfs_search_queue));
383 clib_cuckoo_path_t *tmp;
384 pool_flush (tmp, h->paths,);
385 int rv = CLIB_CUCKOO_ERROR_AGAIN;
387 /* start by creating paths starting in each of the buckets ... */
388 vec_add2 (h->bfs_search_queue, tail, CLIB_CUCKOO_KVP_PER_BUCKET);
390 for (i = 0; i < CLIB_CUCKOO_KVP_PER_BUCKET; ++i)
392 clib_cuckoo_path_t *path =
393 CV (clib_cuckoo_path_begin) (h, lookup->bucket1, i);
394 tail[i] = path - h->paths;
396 if (lookup->bucket1 != lookup->bucket2)
398 vec_add2 (h->bfs_search_queue, tail, CLIB_CUCKOO_KVP_PER_BUCKET);
399 for (i = 0; i < CLIB_CUCKOO_KVP_PER_BUCKET; ++i)
401 clib_cuckoo_path_t *path =
402 CV (clib_cuckoo_path_begin) (h, lookup->bucket2, i);
403 tail[i] = path - h->paths;
408 if (counter >= CLIB_CUCKOO_BFS_MAX_STEPS)
410 #if CLIB_CUCKOO_DEBUG_COUNTERS
415 if (counter >= vec_len (h->bfs_search_queue))
417 #if CLIB_CUCKOO_DEBUG_COUNTERS
418 ++h->bfs_queue_emptied;
422 const uword path_idx = vec_elt (h->bfs_search_queue, counter);
423 const clib_cuckoo_path_t *path = pool_elt_at_index (h->paths, path_idx);
424 #if CLIB_CUCKOO_DEBUG_PATH
425 CLIB_CUCKOO_DBG ("Examine path @%wu: %U", path_idx,
426 CV (format_cuckoo_path), h, path_idx);
428 /* TODO prefetch ? */
429 /* search the alternative bucket for free space */
430 int offset = CV (clib_cuckoo_path_peek_offset) (path);
431 CVT (clib_cuckoo_bucket) * bucket =
432 CV (clib_cuckoo_bucket_at_index) (h, path->bucket);
434 clib_cuckoo_get_other_bucket (CV (clib_cuckoo_get_nbuckets) (h),
436 bucket->reduced_hashes[offset]);
438 ("Path ends in bucket %wu, offset #%wu, other bucket is %wu",
439 path->bucket, CV (clib_cuckoo_path_peek_offset) (path),
441 if (path->bucket != other_bucket)
444 CV (clib_cuckoo_bucket_find_empty) (CV
445 (clib_cuckoo_bucket_at_index)
448 /* found empty element */
449 *found_bucket = other_bucket;
450 *path_idx_out = path_idx;
451 rv = CLIB_CUCKOO_ERROR_SUCCESS;
452 #if CLIB_CUCKOO_DEBUG_PATH
453 CLIB_CUCKOO_DBG ("Bucket with empty slot:\n%U",
454 CV (format_cuckoo_bucket),
455 CV (clib_cuckoo_bucket_at_index) (h,
460 /* extend the current path with possible next buckets and add to
462 if (path->length < CLIB_CUCKOO_BFS_MAX_PATH_LENGTH &&
463 vec_len (h->bfs_search_queue) < CLIB_CUCKOO_BFS_MAX_STEPS)
466 vec_add2 (h->bfs_search_queue, tail,
467 CLIB_CUCKOO_KVP_PER_BUCKET);
468 for (i = 0; i < CLIB_CUCKOO_KVP_PER_BUCKET; ++i)
471 CV (clib_cuckoo_path_extend) (h, path_idx, other_bucket,
473 tail[i] = new_path_idx;
479 CLIB_CUCKOO_DBG ("Discard path @%wu, loop detected", path_idx);
481 /* done with this path - put back to pool for later reuse */
482 CV (clib_cuckoo_path_put) (h, path_idx);
486 vec_reset_length (h->bfs_search_queue);
490 static void CV (clib_cuckoo_swap_elts_in_bucket) (CVT (clib_cuckoo_bucket) *
491 b, uword e1, uword e2)
493 CVT (clib_cuckoo_kv) kv;
494 clib_memcpy (&kv, b->elts + e1, sizeof (kv));
495 clib_memcpy (b->elts + e1, b->elts + e2, sizeof (kv));
496 clib_memcpy (b->elts + e2, &kv, sizeof (kv));
497 u8 reduced_hash = b->reduced_hashes[e1];
498 b->reduced_hashes[e1] = b->reduced_hashes[e2];
499 b->reduced_hashes[e2] = reduced_hash;
502 static void CV (clib_cuckoo_bucket_tidy) (CVT (clib_cuckoo_bucket) * b)
505 int j = CLIB_CUCKOO_KVP_PER_BUCKET - 1;
510 while (!CV (clib_cuckoo_kv_is_free) (&b->elts[min_free]))
514 while (CV (clib_cuckoo_kv_is_free) (&b->elts[max_used]))
518 if (min_free < max_used)
520 CV (clib_cuckoo_swap_elts_in_bucket) (b, min_free, max_used);
531 static void CV (clib_cuckoo_free_locked_elt) (CVT (clib_cuckoo_kv) * elt)
534 * FIXME - improve performance by getting rid of this clib_memset - make all
535 * functions in this file not rely on clib_cuckoo_kv_is_free but instead
536 * take use_count into account */
537 clib_memset (elt, 0xff, sizeof (*elt));
540 static void CV (clib_cuckoo_free_elt_in_bucket) (CVT (clib_cuckoo_bucket) * b,
541 CVT (clib_cuckoo_kv) * elt)
543 clib_cuckoo_bucket_aux_t aux =
544 CV (clib_cuckoo_bucket_version_bump_and_lock) (b);
545 int use_count = clib_cuckoo_bucket_aux_get_use_count (aux);
546 int offset = elt - b->elts;
547 ASSERT (offset < use_count);
548 CV (clib_cuckoo_free_locked_elt) (elt);
549 if (offset != use_count - 1)
551 CV (clib_cuckoo_bucket_tidy) (b);
553 aux = clib_cuckoo_bucket_aux_set_use_count (aux, use_count - 1);
554 CV (clib_cuckoo_bucket_unlock) (b, aux);
557 static void CV (clib_cuckoo_set_locked_elt) (CVT (clib_cuckoo_bucket) * b,
558 CVT (clib_cuckoo_kv) * elt,
559 CVT (clib_cuckoo_kv) * kvp,
562 int offset = CV (clib_cuckoo_elt_in_bucket_to_offset) (b, elt);
563 clib_memcpy (elt, kvp, sizeof (*elt));
564 b->reduced_hashes[offset] = reduced_hash;
565 CLIB_CUCKOO_DBG ("Set bucket %p, offset %d, %U", b, offset,
566 CV (format_cuckoo_elt), elt, b->reduced_hashes[offset]);
569 static void CV (clib_cuckoo_set_elt) (CVT (clib_cuckoo_bucket) * b,
570 CVT (clib_cuckoo_kv) * elt,
571 CVT (clib_cuckoo_kv) * kvp,
574 clib_cuckoo_bucket_aux_t aux =
575 CV (clib_cuckoo_bucket_version_bump_and_lock) (b);
576 CV (clib_cuckoo_set_locked_elt) (b, elt, kvp, reduced_hash);
577 CV (clib_cuckoo_bucket_unlock) (b, aux);
580 static int CV (clib_cuckoo_add_slow) (CVT (clib_cuckoo) * h,
581 CVT (clib_cuckoo_kv) * kvp,
582 clib_cuckoo_lookup_info_t * lookup,
586 uword empty_bucket_idx;
587 CVT (clib_cuckoo_kv) * empty_elt;
588 int rv = CV (clib_cuckoo_find_empty_slot_bfs) (h, lookup, &path_idx,
591 if (CLIB_CUCKOO_ERROR_SUCCESS == rv)
593 uword buckets[CLIB_CUCKOO_BFS_MAX_PATH_LENGTH];
594 uword offsets[CLIB_CUCKOO_BFS_MAX_PATH_LENGTH];
595 clib_cuckoo_path_walk (h, path_idx, buckets, offsets);
597 * walk back the path, moving the free element forward to one of our
600 clib_cuckoo_path_t *path = pool_elt_at_index (h->paths, path_idx);
601 CVT (clib_cuckoo_bucket) * empty_bucket =
602 CV (clib_cuckoo_bucket_at_index) (h, empty_bucket_idx);
604 for (i = path->length - 1; i >= 0; --i)
606 /* copy the key-value in path to the bucket with empty element */
607 CVT (clib_cuckoo_bucket) * b =
608 CV (clib_cuckoo_bucket_at_index) (h, buckets[i]);
609 CVT (clib_cuckoo_kv) * elt = b->elts + offsets[i];
610 clib_cuckoo_bucket_aux_t empty_aux =
611 CV (clib_cuckoo_bucket_version_bump_and_lock) (empty_bucket);
612 CV (clib_cuckoo_set_locked_elt)
613 (empty_bucket, empty_elt, elt, b->reduced_hashes[elt - b->elts]);
614 if (i == path->length - 1)
616 /* we only need to increase the use count for the bucket with
617 * free element - all other buckets' use counts won't change */
618 empty_aux = clib_cuckoo_bucket_aux_set_use_count (empty_aux,
619 clib_cuckoo_bucket_aux_get_use_count
623 CV (clib_cuckoo_bucket_unlock) (empty_bucket, empty_aux);
625 * the element now exists in both places - in the previously empty
626 * element and in its original bucket - we can now safely overwrite
627 * the element in the original bucket with previous element in path
628 * without loosing data (and we don't need to modify the use count)
633 /* now we have a place to put the kvp in ... */
634 CV (clib_cuckoo_set_elt) (empty_bucket, empty_elt, kvp, reduced_hash);
635 CLIB_CUCKOO_DBG ("Slow insert success, bucket: %p\n%U", empty_bucket,
636 CV (format_cuckoo_bucket), empty_bucket);
637 #if CLIB_CUCKOO_DEBUG_COUNTERS
644 static int CV (clib_cuckoo_add_fast) (CVT (clib_cuckoo) * h,
645 clib_cuckoo_lookup_info_t * lookup,
646 CVT (clib_cuckoo_kv) * kvp,
649 CVT (clib_cuckoo_kv) * elt;
650 CVT (clib_cuckoo_bucket) * bucket1 =
651 CV (clib_cuckoo_bucket_at_index) (h, lookup->bucket1);
652 if ((elt = CV (clib_cuckoo_bucket_find_empty) (bucket1)))
654 clib_cuckoo_bucket_aux_t aux =
655 CV (clib_cuckoo_bucket_version_bump_and_lock) (bucket1);
656 CV (clib_cuckoo_set_locked_elt) (bucket1, elt, kvp, reduced_hash);
658 clib_cuckoo_bucket_aux_set_use_count (aux,
659 clib_cuckoo_bucket_aux_get_use_count
661 CV (clib_cuckoo_bucket_unlock) (bucket1, aux);
662 #if CLIB_CUCKOO_DEBUG_COUNTERS
665 return CLIB_CUCKOO_ERROR_SUCCESS;
667 CVT (clib_cuckoo_bucket) * bucket2 =
668 CV (clib_cuckoo_bucket_at_index) (h, lookup->bucket2);
670 CV (clib_cuckoo_bucket_find_empty) (CV (clib_cuckoo_bucket_at_index)
671 (h, lookup->bucket2))))
673 clib_cuckoo_bucket_aux_t aux =
674 CV (clib_cuckoo_bucket_version_bump_and_lock) (bucket2);
675 CV (clib_cuckoo_set_locked_elt) (bucket2, elt, kvp, reduced_hash);
677 clib_cuckoo_bucket_aux_set_use_count (aux,
678 clib_cuckoo_bucket_aux_get_use_count
680 CV (clib_cuckoo_bucket_unlock) (bucket2, aux);
681 #if CLIB_CUCKOO_DEBUG_COUNTERS
684 return CLIB_CUCKOO_ERROR_SUCCESS;
686 return CLIB_CUCKOO_ERROR_AGAIN;
690 * perform garbage collection
692 * this function assumes there is no other thread touching the cuckoo hash,
693 * not even a reader, it's meant to be called from main thread
694 * in a stop-the-world situation
696 void CV (clib_cuckoo_garbage_collect) (CVT (clib_cuckoo) * h)
698 CLIB_MEMORY_BARRIER ();
699 CVT (clib_cuckoo_bucket) * *b;
701 vec_foreach (b, h->to_be_freed)
703 if (*b == h->buckets)
707 #if CLIB_CUCKOO_DEBUG_GC
708 fformat (stdout, "gc: free %p\n", *b);
713 vec_free (h->to_be_freed);
714 CLIB_MEMORY_BARRIER ();
718 * expand and rehash a cuckoo hash
720 * 1. double the size of the hash table
721 * 2. move items to new locations derived from the new size
723 static void CV (clib_cuckoo_rehash) (CVT (clib_cuckoo) * h)
725 CVT (clib_cuckoo_bucket) * old = h->buckets;
726 uword old_nbuckets = vec_len (old);
727 uword new_nbuckets = 2 * old_nbuckets;
728 CVT (clib_cuckoo_bucket) * new =
729 vec_dup_aligned (old, CLIB_CACHE_LINE_BYTES);
731 vec_validate_aligned (new, new_nbuckets - 1, CLIB_CACHE_LINE_BYTES);
732 ASSERT (new_nbuckets == vec_len (new));
733 /* store old pointer in to-be-freed list */
734 vec_add1 (h->to_be_freed, old);
735 /* mark new elements as free */
736 CVT (clib_cuckoo_bucket) * bucket;
737 for (bucket = new + old_nbuckets; bucket < vec_end (new); ++bucket)
739 clib_memset (bucket->elts, 0xff, sizeof (bucket->elts));
742 * this for loop manipulates the new (unseen) memory, so no locks
745 uword old_bucket_idx;
746 for (old_bucket_idx = 0; old_bucket_idx < old_nbuckets; ++old_bucket_idx)
748 /* items in old bucket might be moved to new bucket */
749 uword new_bucket_idx = old_bucket_idx + old_nbuckets;
750 CVT (clib_cuckoo_bucket) * old_bucket = new + old_bucket_idx;
751 CVT (clib_cuckoo_bucket) * new_bucket = new + new_bucket_idx;
754 clib_cuckoo_bucket_aux_t aux = old_bucket->aux;
755 for (i = 0; i < clib_cuckoo_bucket_aux_get_use_count (aux); ++i)
757 CVT (clib_cuckoo_kv) * elt = old_bucket->elts + i;
758 u64 hash = CV (clib_cuckoo_hash) (elt);
759 clib_cuckoo_lookup_info_t old_lookup =
760 CV (clib_cuckoo_calc_lookup) (old, hash);
761 clib_cuckoo_lookup_info_t new_lookup =
762 CV (clib_cuckoo_calc_lookup) (new, hash);
763 if ((old_bucket_idx == old_lookup.bucket1 &&
764 new_bucket_idx == new_lookup.bucket1) ||
765 (old_bucket_idx == old_lookup.bucket2 &&
766 new_bucket_idx == new_lookup.bucket2))
768 /* move the item to new bucket */
769 CVT (clib_cuckoo_kv) * empty_elt = new_bucket->elts + moved;
771 CV (clib_cuckoo_set_locked_elt)
772 (new_bucket, empty_elt, elt, old_bucket->reduced_hashes[i]);
773 CV (clib_cuckoo_free_locked_elt) (elt);
779 CV (clib_cuckoo_bucket_tidy) (old_bucket);
781 clib_cuckoo_bucket_aux_set_use_count (aux,
782 clib_cuckoo_bucket_aux_get_use_count
784 old_bucket->aux = aux;
785 aux = new_bucket->aux;
787 clib_cuckoo_bucket_aux_set_use_count (aux,
788 clib_cuckoo_bucket_aux_get_use_count
790 new_bucket->aux = aux;
794 #if CLIB_CUCKOO_DEBUG_COUNTERS
797 h->garbage_callback (h, h->garbage_ctx);
800 static int CV (clib_cuckoo_bucket_search_internal) (CVT (clib_cuckoo) * h,
802 CVT (clib_cuckoo_kv) *
804 CVT (clib_cuckoo_kv) *
807 CVT (clib_cuckoo_bucket) * b = CV (clib_cuckoo_bucket_at_index) (h, bucket);
810 clib_cuckoo_bucket_foreach_idx_unrolled (i, {
811 CVT (clib_cuckoo_kv) *elt = &b->elts[i];
812 if (CV (clib_cuckoo_key_compare) (elt->key, kvp->key))
815 return CLIB_CUCKOO_ERROR_SUCCESS;
819 return CLIB_CUCKOO_ERROR_NOT_FOUND;
822 int CV (clib_cuckoo_add_del) (CVT (clib_cuckoo) * h,
823 CVT (clib_cuckoo_kv) * kvp, int is_add)
825 CLIB_CUCKOO_DBG ("%s %U", is_add ? "Add" : "Del", CV (format_cuckoo_kvp),
827 clib_cuckoo_lookup_info_t lookup;
828 u64 hash = CV (clib_cuckoo_hash) (kvp);
829 clib_spinlock_lock (&h->writer_lock);
830 u8 reduced_hash = clib_cuckoo_reduce_hash (hash);
832 lookup = CV (clib_cuckoo_calc_lookup) (h->buckets, hash);
833 CVT (clib_cuckoo_bucket) * b =
834 CV (clib_cuckoo_bucket_at_index) (h, lookup.bucket1);
835 CVT (clib_cuckoo_kv) * found;
837 CV (clib_cuckoo_bucket_search_internal) (h, lookup.bucket1, kvp, &found);
838 if (CLIB_CUCKOO_ERROR_SUCCESS != rv)
840 ASSERT (CLIB_CUCKOO_ERROR_NOT_FOUND == rv);
841 b = CV (clib_cuckoo_bucket_at_index) (h, lookup.bucket2);
842 rv = CV (clib_cuckoo_bucket_search_internal) (h, lookup.bucket2, kvp,
845 if (CLIB_CUCKOO_ERROR_SUCCESS == rv)
849 /* prevent readers reading this bucket while we switch the values */
850 clib_cuckoo_bucket_aux_t aux =
851 CV (clib_cuckoo_bucket_version_bump_and_lock) (b);
852 clib_memcpy (&found->value, &kvp->value, sizeof (found->value));
853 CLIB_CUCKOO_DBG ("Replaced existing %U", CV (format_cuckoo_elt),
854 found, b->reduced_hashes[found - b->elts]);
855 CV (clib_cuckoo_bucket_unlock) (b, aux);
859 CV (clib_cuckoo_free_elt_in_bucket) (b, found);
861 rv = CLIB_CUCKOO_ERROR_SUCCESS;
862 CLIB_CUCKOO_DEEP_SELF_CHECK (h);
867 CLIB_CUCKOO_DBG ("%U not present in table", CV (format_cuckoo_kvp),
869 rv = CLIB_CUCKOO_ERROR_NOT_FOUND;
872 /* from this point on, it's add code only */
873 ASSERT (CLIB_CUCKOO_ERROR_NOT_FOUND == rv);
874 /* fast path: try to search for unoccupied slot in one of the buckets */
875 rv = CV (clib_cuckoo_add_fast) (h, &lookup, kvp, reduced_hash);
876 CLIB_CUCKOO_DEEP_SELF_CHECK (h);
877 if (CLIB_CUCKOO_ERROR_SUCCESS != rv)
879 CLIB_CUCKOO_DBG ("Fast insert failed, bucket 1: %wu, bucket 2: %wu\n%U%U", aux.bucket1, aux.bucket2, CV (format_cuckoo_bucket), CV (clib_cuckoo_bucindent: Standaindent: Standard input: 903: Error: Unmatched 'else' rd input: 865: Error:Unmatched 'else' ket_at_index) (h, aux.bucket1),
880 CV (format_cuckoo_bucket),
881 CV (clib_cuckoo_bucket_at_index) (h, aux.bucket2));
883 rv = CV (clib_cuckoo_add_slow) (h, kvp, &lookup, reduced_hash);
884 CLIB_CUCKOO_DEEP_SELF_CHECK (h);
885 if (CLIB_CUCKOO_ERROR_SUCCESS != rv)
887 CLIB_CUCKOO_DBG ("Slow insert failed, rehash required:\n%U",
888 CV (format_cuckoo), h, 1);
889 /* ultra slow path */
890 CV (clib_cuckoo_rehash) (h);
891 CLIB_CUCKOO_DEEP_SELF_CHECK (h);
892 CLIB_CUCKOO_DBG ("Restarting add after rehash...");
897 clib_spinlock_unlock (&h->writer_lock);
901 u8 *CV (format_cuckoo) (u8 * s, va_list * args)
903 CVT (clib_cuckoo) * h = va_arg (*args, CVT (clib_cuckoo) *);
904 int verbose = va_arg (*args, int);
906 s = format (s, "Hash table %s\n", h->name ? h->name : "(unnamed)");
910 uword use_count_total = 0;
912 CVT (clib_cuckoo_bucket) * b;
914 clib_cuckoo_foreach_bucket (b, h, {
917 s = format (s, "%U", CV (format_cuckoo_bucket), b);
920 clib_cuckoo_bucket_foreach_idx (i)
922 CVT (clib_cuckoo_kv) *elt = &b->elts[i];
923 if (CV (clib_cuckoo_kv_is_free) (elt))
932 clib_cuckoo_bucket_aux_t aux = b->aux;
933 use_count_total += clib_cuckoo_bucket_aux_get_use_count (aux);
936 s = format (s, "Used slots: %wu\n", used);
937 s = format (s, "Use count total: %wu\n", use_count_total);
938 s = format (s, "Free slots: %wu\n", free);
939 if (free + used != 0)
940 load_factor = ((float) used) / ((float) (free + used));
943 s = format (s, "Load factor: %.2f\n", load_factor);
944 #if CLIB_CUCKOO_DEBUG_COUNTERS
945 s = format (s, "BFS attempts limited by max steps: %lld\n",
947 s = format (s, "BFS cutoffs due to empty queue: %lld\n",
948 h->bfs_queue_emptied);
949 s = format (s, "Fast adds: %lld\n", h->fast_adds);
950 s = format (s, "Slow adds: %lld\n", h->slow_adds);
951 s = format (s, "Rehashes: %lld\n", h->rehashes);
956 float CV (clib_cuckoo_calculate_load_factor) (CVT (clib_cuckoo) * h)
960 CVT (clib_cuckoo_bucket) * bucket;
962 clib_cuckoo_foreach_bucket (bucket, h, {
964 clib_cuckoo_bucket_foreach_idx (i)
966 CVT (clib_cuckoo_kv) *elt = bucket->elts + i;
968 if (!CV (clib_cuckoo_kv_is_free) (elt))
976 return (float) nonfree / (float) all;
984 * fd.io coding-style-patch-verification: ON
987 * eval: (c-set-style "gnu")