89ae847c03626fbd41d0e0ebeb8366ca65ac844d
[vpp.git] / src / vppinfra / bihash_template.c
1 /*
2  * Copyright (c) 2015 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:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
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.
14  */
15
16 /** @cond DOCUMENTATION_IS_IN_BIHASH_DOC_H */
17
18 static inline void *BV (alloc_aligned) (BVT (clib_bihash) * h, uword nbytes)
19 {
20   uword rv;
21
22   /* Round to an even number of cache lines */
23   nbytes += CLIB_CACHE_LINE_BYTES - 1;
24   nbytes &= ~(CLIB_CACHE_LINE_BYTES - 1);
25
26   rv = h->alloc_arena_next;
27   h->alloc_arena_next += nbytes;
28
29   if (rv >= (h->alloc_arena + h->alloc_arena_size))
30     os_out_of_memory ();
31
32   return (void *) rv;
33 }
34
35
36 void BV (clib_bihash_init)
37   (BVT (clib_bihash) * h, char *name, u32 nbuckets, uword memory_size)
38 {
39   uword bucket_size;
40   int i;
41
42   nbuckets = 1 << (max_log2 (nbuckets));
43
44   h->name = (u8 *) name;
45   h->nbuckets = nbuckets;
46   h->log2_nbuckets = max_log2 (nbuckets);
47   h->cache_hits = 0;
48   h->cache_misses = 0;
49
50   h->alloc_arena = (uword) clib_mem_vm_alloc (memory_size);
51   h->alloc_arena_next = h->alloc_arena;
52   h->alloc_arena_size = memory_size;
53
54   bucket_size = nbuckets * sizeof (h->buckets[0]);
55   h->buckets = BV (alloc_aligned) (h, bucket_size);
56
57   h->writer_lock = BV (alloc_aligned) (h, CLIB_CACHE_LINE_BYTES);
58   h->writer_lock[0] = 0;
59
60   for (i = 0; i < nbuckets; i++)
61     BV (clib_bihash_reset_cache) (h->buckets + i);
62
63   h->fmt_fn = NULL;
64 }
65
66 void BV (clib_bihash_set_kvp_format_fn) (BVT (clib_bihash) * h,
67                                          format_function_t * fmt_fn)
68 {
69   h->fmt_fn = fmt_fn;
70 }
71
72 void BV (clib_bihash_free) (BVT (clib_bihash) * h)
73 {
74   vec_free (h->working_copies);
75   vec_free (h->freelists);
76   clib_mem_vm_free ((void *) (h->alloc_arena), h->alloc_arena_size);
77   memset (h, 0, sizeof (*h));
78 }
79
80 static
81 BVT (clib_bihash_value) *
82 BV (value_alloc) (BVT (clib_bihash) * h, u32 log2_pages)
83 {
84   BVT (clib_bihash_value) * rv = 0;
85
86   ASSERT (h->writer_lock[0]);
87   if (log2_pages >= vec_len (h->freelists) || h->freelists[log2_pages] == 0)
88     {
89       vec_validate_init_empty (h->freelists, log2_pages, 0);
90       rv = BV (alloc_aligned) (h, (sizeof (*rv) * (1 << log2_pages)));
91       goto initialize;
92     }
93   rv = h->freelists[log2_pages];
94   h->freelists[log2_pages] = rv->next_free;
95
96 initialize:
97   ASSERT (rv);
98   /*
99    * Latest gcc complains that the length arg is zero
100    * if we replace (1<<log2_pages) with vec_len(rv).
101    * No clue.
102    */
103   memset (rv, 0xff, sizeof (*rv) * (1 << log2_pages));
104   return rv;
105 }
106
107 static void
108 BV (value_free) (BVT (clib_bihash) * h, BVT (clib_bihash_value) * v,
109                  u32 log2_pages)
110 {
111   ASSERT (h->writer_lock[0]);
112
113   ASSERT (vec_len (h->freelists) > log2_pages);
114
115   v->next_free = h->freelists[log2_pages];
116   h->freelists[log2_pages] = v;
117 }
118
119 static inline void
120 BV (make_working_copy) (BVT (clib_bihash) * h, BVT (clib_bihash_bucket) * b)
121 {
122   BVT (clib_bihash_value) * v;
123   BVT (clib_bihash_bucket) working_bucket __attribute__ ((aligned (8)));
124   BVT (clib_bihash_value) * working_copy;
125   u32 thread_index = os_get_thread_index ();
126   int log2_working_copy_length;
127
128   if (thread_index >= vec_len (h->working_copies))
129     {
130       vec_validate (h->working_copies, thread_index);
131       vec_validate_init_empty (h->working_copy_lengths, thread_index, ~0);
132     }
133
134   /*
135    * working_copies are per-cpu so that near-simultaneous
136    * updates from multiple threads will not result in sporadic, spurious
137    * lookup failures.
138    */
139   working_copy = h->working_copies[thread_index];
140   log2_working_copy_length = h->working_copy_lengths[thread_index];
141
142   h->saved_bucket.as_u64 = b->as_u64;
143
144   if (b->log2_pages > log2_working_copy_length)
145     {
146       /*
147        * It's not worth the bookkeeping to free working copies
148        *   if (working_copy)
149        *     clib_mem_free (working_copy);
150        */
151       working_copy = BV (alloc_aligned)
152         (h, sizeof (working_copy[0]) * (1 << b->log2_pages));
153       h->working_copy_lengths[thread_index] = b->log2_pages;
154       h->working_copies[thread_index] = working_copy;
155     }
156
157   /* Lock the bucket... */
158   while (BV (clib_bihash_lock_bucket) (b) == 0)
159     ;
160
161   v = BV (clib_bihash_get_value) (h, b->offset);
162
163   clib_memcpy (working_copy, v, sizeof (*v) * (1 << b->log2_pages));
164   working_bucket.as_u64 = b->as_u64;
165   working_bucket.offset = BV (clib_bihash_get_offset) (h, working_copy);
166   CLIB_MEMORY_BARRIER ();
167   b->as_u64 = working_bucket.as_u64;
168   h->working_copies[thread_index] = working_copy;
169 }
170
171 static
172 BVT (clib_bihash_value) *
173 BV (split_and_rehash)
174   (BVT (clib_bihash) * h,
175    BVT (clib_bihash_value) * old_values, u32 old_log2_pages,
176    u32 new_log2_pages)
177 {
178   BVT (clib_bihash_value) * new_values, *new_v;
179   int i, j, length_in_kvs;
180
181   new_values = BV (value_alloc) (h, new_log2_pages);
182   length_in_kvs = (1 << old_log2_pages) * BIHASH_KVP_PER_PAGE;
183
184   for (i = 0; i < length_in_kvs; i++)
185     {
186       u64 new_hash;
187
188       /* Entry not in use? Forget it */
189       if (BV (clib_bihash_is_free) (&(old_values->kvp[i])))
190         continue;
191
192       /* rehash the item onto its new home-page */
193       new_hash = BV (clib_bihash_hash) (&(old_values->kvp[i]));
194       new_hash >>= h->log2_nbuckets;
195       new_hash &= (1 << new_log2_pages) - 1;
196       new_v = &new_values[new_hash];
197
198       /* Across the new home-page */
199       for (j = 0; j < BIHASH_KVP_PER_PAGE; j++)
200         {
201           /* Empty slot */
202           if (BV (clib_bihash_is_free) (&(new_v->kvp[j])))
203             {
204               clib_memcpy (&(new_v->kvp[j]), &(old_values->kvp[i]),
205                            sizeof (new_v->kvp[j]));
206               goto doublebreak;
207             }
208         }
209       /* Crap. Tell caller to try again */
210       BV (value_free) (h, new_values, new_log2_pages);
211       return 0;
212     doublebreak:;
213     }
214
215   return new_values;
216 }
217
218 static
219 BVT (clib_bihash_value) *
220 BV (split_and_rehash_linear)
221   (BVT (clib_bihash) * h,
222    BVT (clib_bihash_value) * old_values, u32 old_log2_pages,
223    u32 new_log2_pages)
224 {
225   BVT (clib_bihash_value) * new_values;
226   int i, j, new_length, old_length;
227
228   new_values = BV (value_alloc) (h, new_log2_pages);
229   new_length = (1 << new_log2_pages) * BIHASH_KVP_PER_PAGE;
230   old_length = (1 << old_log2_pages) * BIHASH_KVP_PER_PAGE;
231
232   j = 0;
233   /* Across the old value array */
234   for (i = 0; i < old_length; i++)
235     {
236       /* Find a free slot in the new linear scan bucket */
237       for (; j < new_length; j++)
238         {
239           /* Old value not in use? Forget it. */
240           if (BV (clib_bihash_is_free) (&(old_values->kvp[i])))
241             goto doublebreak;
242
243           /* New value should never be in use */
244           if (BV (clib_bihash_is_free) (&(new_values->kvp[j])))
245             {
246               /* Copy the old value and move along */
247               clib_memcpy (&(new_values->kvp[j]), &(old_values->kvp[i]),
248                            sizeof (new_values->kvp[j]));
249               j++;
250               goto doublebreak;
251             }
252         }
253       /* This should never happen... */
254       clib_warning ("BUG: linear rehash failed!");
255       BV (value_free) (h, new_values, new_log2_pages);
256       return 0;
257
258     doublebreak:;
259     }
260   return new_values;
261 }
262
263 int BV (clib_bihash_add_del)
264   (BVT (clib_bihash) * h, BVT (clib_bihash_kv) * add_v, int is_add)
265 {
266   u32 bucket_index;
267   BVT (clib_bihash_bucket) * b, tmp_b;
268   BVT (clib_bihash_value) * v, *new_v, *save_new_v, *working_copy;
269   int rv = 0;
270   int i, limit;
271   u64 hash, new_hash;
272   u32 new_log2_pages, old_log2_pages;
273   u32 thread_index = os_get_thread_index ();
274   int mark_bucket_linear;
275   int resplit_once;
276
277   hash = BV (clib_bihash_hash) (add_v);
278
279   bucket_index = hash & (h->nbuckets - 1);
280   b = &h->buckets[bucket_index];
281
282   hash >>= h->log2_nbuckets;
283
284   tmp_b.linear_search = 0;
285
286   while (__sync_lock_test_and_set (h->writer_lock, 1))
287     ;
288
289   /* First elt in the bucket? */
290   if (b->offset == 0)
291     {
292       if (is_add == 0)
293         {
294           rv = -1;
295           goto unlock;
296         }
297
298       v = BV (value_alloc) (h, 0);
299
300       *v->kvp = *add_v;
301       tmp_b.as_u64 = 0;
302       tmp_b.offset = BV (clib_bihash_get_offset) (h, v);
303       tmp_b.refcnt = 1;
304
305       b->as_u64 = tmp_b.as_u64;
306       goto unlock;
307     }
308
309   /* Note: this leaves the cache disabled */
310   BV (make_working_copy) (h, b);
311
312   v = BV (clib_bihash_get_value) (h, h->saved_bucket.offset);
313
314   limit = BIHASH_KVP_PER_PAGE;
315   v += (b->linear_search == 0) ? hash & ((1 << b->log2_pages) - 1) : 0;
316   if (b->linear_search)
317     limit <<= b->log2_pages;
318
319   if (is_add)
320     {
321       /*
322        * For obvious (in hindsight) reasons, see if we're supposed to
323        * replace an existing key, then look for an empty slot.
324        */
325       for (i = 0; i < limit; i++)
326         {
327           if (!memcmp (&(v->kvp[i]), &add_v->key, sizeof (add_v->key)))
328             {
329               clib_memcpy (&(v->kvp[i]), add_v, sizeof (*add_v));
330               CLIB_MEMORY_BARRIER ();
331               /* Restore the previous (k,v) pairs */
332               b->as_u64 = h->saved_bucket.as_u64;
333               goto unlock;
334             }
335         }
336       for (i = 0; i < limit; i++)
337         {
338           if (BV (clib_bihash_is_free) (&(v->kvp[i])))
339             {
340               clib_memcpy (&(v->kvp[i]), add_v, sizeof (*add_v));
341               CLIB_MEMORY_BARRIER ();
342               b->as_u64 = h->saved_bucket.as_u64;
343               b->refcnt++;
344               goto unlock;
345             }
346         }
347       /* no room at the inn... split case... */
348     }
349   else
350     {
351       for (i = 0; i < limit; i++)
352         {
353           if (!memcmp (&(v->kvp[i]), &add_v->key, sizeof (add_v->key)))
354             {
355               memset (&(v->kvp[i]), 0xff, sizeof (*(add_v)));
356               CLIB_MEMORY_BARRIER ();
357               if (PREDICT_TRUE (h->saved_bucket.refcnt > 1))
358                 {
359                   h->saved_bucket.refcnt -= 1;
360                   b->as_u64 = h->saved_bucket.as_u64;
361                   goto unlock;
362                 }
363               else
364                 {
365                   tmp_b.as_u64 = 0;
366                   goto free_old_bucket;
367                 }
368             }
369         }
370       rv = -3;
371       b->as_u64 = h->saved_bucket.as_u64;
372       goto unlock;
373     }
374
375   old_log2_pages = h->saved_bucket.log2_pages;
376   new_log2_pages = old_log2_pages + 1;
377   mark_bucket_linear = 0;
378
379   working_copy = h->working_copies[thread_index];
380   resplit_once = 0;
381
382   new_v = BV (split_and_rehash) (h, working_copy, old_log2_pages,
383                                  new_log2_pages);
384   if (new_v == 0)
385     {
386     try_resplit:
387       resplit_once = 1;
388       new_log2_pages++;
389       /* Try re-splitting. If that fails, fall back to linear search */
390       new_v = BV (split_and_rehash) (h, working_copy, old_log2_pages,
391                                      new_log2_pages);
392       if (new_v == 0)
393         {
394         mark_linear:
395           new_log2_pages--;
396           /* pinned collisions, use linear search */
397           new_v =
398             BV (split_and_rehash_linear) (h, working_copy, old_log2_pages,
399                                           new_log2_pages);
400           mark_bucket_linear = 1;
401         }
402     }
403
404   /* Try to add the new entry */
405   save_new_v = new_v;
406   new_hash = BV (clib_bihash_hash) (add_v);
407   limit = BIHASH_KVP_PER_PAGE;
408   if (mark_bucket_linear)
409     limit <<= new_log2_pages;
410   new_hash >>= h->log2_nbuckets;
411   new_hash &= (1 << new_log2_pages) - 1;
412   new_v += mark_bucket_linear ? 0 : new_hash;
413
414   for (i = 0; i < limit; i++)
415     {
416       if (BV (clib_bihash_is_free) (&(new_v->kvp[i])))
417         {
418           clib_memcpy (&(new_v->kvp[i]), add_v, sizeof (*add_v));
419           goto expand_ok;
420         }
421     }
422
423   /* Crap. Try again */
424   BV (value_free) (h, save_new_v, new_log2_pages);
425   /*
426    * If we've already doubled the size of the bucket once,
427    * fall back to linear search now.
428    */
429   if (resplit_once)
430     goto mark_linear;
431   else
432     goto try_resplit;
433
434 expand_ok:
435   tmp_b.log2_pages = new_log2_pages;
436   tmp_b.offset = BV (clib_bihash_get_offset) (h, save_new_v);
437   tmp_b.linear_search = mark_bucket_linear;
438   tmp_b.refcnt = h->saved_bucket.refcnt + 1;
439
440 free_old_bucket:
441
442   CLIB_MEMORY_BARRIER ();
443   b->as_u64 = tmp_b.as_u64;
444   v = BV (clib_bihash_get_value) (h, h->saved_bucket.offset);
445
446   BV (value_free) (h, v, h->saved_bucket.log2_pages);
447
448 unlock:
449   BV (clib_bihash_reset_cache) (b);
450   BV (clib_bihash_unlock_bucket) (b);
451   CLIB_MEMORY_BARRIER ();
452   h->writer_lock[0] = 0;
453   return rv;
454 }
455
456 int BV (clib_bihash_search)
457   (BVT (clib_bihash) * h,
458    BVT (clib_bihash_kv) * search_key, BVT (clib_bihash_kv) * valuep)
459 {
460   u64 hash;
461   u32 bucket_index;
462   BVT (clib_bihash_value) * v;
463 #if BIHASH_KVP_CACHE_SIZE > 0
464   BVT (clib_bihash_kv) * kvp;
465 #endif
466   BVT (clib_bihash_bucket) * b;
467   int i, limit;
468
469   ASSERT (valuep);
470
471   hash = BV (clib_bihash_hash) (search_key);
472
473   bucket_index = hash & (h->nbuckets - 1);
474   b = &h->buckets[bucket_index];
475
476   if (b->offset == 0)
477     return -1;
478
479 #if BIHASH_KVP_CACHE_SIZE > 0
480   /* Check the cache, if currently enabled */
481   if (PREDICT_TRUE ((b->cache_lru & (1 << 15)) == 0))
482     {
483       limit = BIHASH_KVP_CACHE_SIZE;
484       kvp = b->cache;
485       for (i = 0; i < limit; i++)
486         {
487           if (BV (clib_bihash_key_compare) (kvp[i].key, search_key->key))
488             {
489               *valuep = kvp[i];
490               h->cache_hits++;
491               return 0;
492             }
493         }
494     }
495 #endif
496
497   hash >>= h->log2_nbuckets;
498
499   v = BV (clib_bihash_get_value) (h, b->offset);
500   limit = BIHASH_KVP_PER_PAGE;
501   v += (b->linear_search == 0) ? hash & ((1 << b->log2_pages) - 1) : 0;
502   if (PREDICT_FALSE (b->linear_search))
503     limit <<= b->log2_pages;
504
505   for (i = 0; i < limit; i++)
506     {
507       if (BV (clib_bihash_key_compare) (v->kvp[i].key, search_key->key))
508         {
509           *valuep = v->kvp[i];
510
511 #if BIHASH_KVP_CACHE_SIZE > 0
512           u8 cache_slot;
513           /* Shut off the cache */
514           if (BV (clib_bihash_lock_bucket) (b))
515             {
516               cache_slot = BV (clib_bihash_get_lru) (b);
517               b->cache[cache_slot] = v->kvp[i];
518               BV (clib_bihash_update_lru) (b, cache_slot);
519
520               /* Reenable the cache */
521               BV (clib_bihash_unlock_bucket) (b);
522               h->cache_misses++;
523             }
524 #endif
525           return 0;
526         }
527     }
528   return -1;
529 }
530
531 u8 *BV (format_bihash_lru) (u8 * s, va_list * args)
532 {
533 #if BIHASH_KVP_SIZE > 0
534   int i;
535   BVT (clib_bihash_bucket) * b = va_arg (*args, BVT (clib_bihash_bucket) *);
536   u16 cache_lru = b->cache_lru;
537
538   s = format (s, "cache %s, order ", cache_lru & (1 << 15) ? "on" : "off");
539
540   for (i = 0; i < BIHASH_KVP_CACHE_SIZE; i++)
541     s = format (s, "[%d] ", ((cache_lru >> (3 * i)) & 7));
542
543   return (s);
544 #else
545   return format (s, "cache not configured");
546 #endif
547 }
548
549 void
550 BV (clib_bihash_update_lru_not_inline) (BVT (clib_bihash_bucket) * b, u8 slot)
551 {
552 #if BIHASH_KVP_SIZE > 0
553   BV (clib_bihash_update_lru) (b, slot);
554 #endif
555 }
556
557 u8 *BV (format_bihash) (u8 * s, va_list * args)
558 {
559   BVT (clib_bihash) * h = va_arg (*args, BVT (clib_bihash) *);
560   int verbose = va_arg (*args, int);
561   BVT (clib_bihash_bucket) * b;
562   BVT (clib_bihash_value) * v;
563   int i, j, k;
564   u64 active_elements = 0;
565   u64 active_buckets = 0;
566   u64 linear_buckets = 0;
567   u64 used_bytes;
568
569   s = format (s, "Hash table %s\n", h->name ? h->name : (u8 *) "(unnamed)");
570
571   for (i = 0; i < h->nbuckets; i++)
572     {
573       b = &h->buckets[i];
574       if (b->offset == 0)
575         {
576           if (verbose > 1)
577             s = format (s, "[%d]: empty\n", i);
578           continue;
579         }
580
581       active_buckets++;
582
583       if (b->linear_search)
584         linear_buckets++;
585
586       if (verbose)
587         {
588           s = format (s, "[%d]: heap offset %d, len %d, linear %d\n", i,
589                       b->offset, (1 << b->log2_pages), b->linear_search);
590         }
591
592       v = BV (clib_bihash_get_value) (h, b->offset);
593       for (j = 0; j < (1 << b->log2_pages); j++)
594         {
595           for (k = 0; k < BIHASH_KVP_PER_PAGE; k++)
596             {
597               if (BV (clib_bihash_is_free) (&v->kvp[k]))
598                 {
599                   if (verbose > 1)
600                     s = format (s, "    %d: empty\n",
601                                 j * BIHASH_KVP_PER_PAGE + k);
602                   continue;
603                 }
604               if (verbose)
605                 {
606                   if (h->fmt_fn)
607                     {
608                       s = format (s, "    %d: %U\n",
609                                   j * BIHASH_KVP_PER_PAGE + k,
610                                   h->fmt_fn, &(v->kvp[k]));
611                     }
612                   else
613                     {
614                       s = format (s, "    %d: %U\n",
615                                   j * BIHASH_KVP_PER_PAGE + k,
616                                   BV (format_bihash_kvp), &(v->kvp[k]));
617                     }
618                 }
619               active_elements++;
620             }
621           v++;
622         }
623     }
624
625   s = format (s, "    %lld active elements %lld active buckets\n",
626               active_elements, active_buckets);
627   s = format (s, "    %d free lists\n", vec_len (h->freelists));
628
629   for (i = 0; i < vec_len (h->freelists); i++)
630     {
631       u32 nfree = 0;
632       BVT (clib_bihash_value) * free_elt;
633
634       free_elt = h->freelists[i];
635       while (free_elt)
636         {
637           nfree++;
638           free_elt = free_elt->next_free;
639         }
640
641       s = format (s, "       [len %d] %u free elts\n", 1 << i, nfree);
642     }
643
644   s = format (s, "    %lld linear search buckets\n", linear_buckets);
645   s = format (s, "    %lld cache hits, %lld cache misses\n",
646               h->cache_hits, h->cache_misses);
647   used_bytes = h->alloc_arena_next - h->alloc_arena;
648   s = format (s,
649               "    arena: base %llx, next %llx\n"
650               "           used %lld b (%lld Mbytes) of %lld b (%lld Mbytes)\n",
651               h->alloc_arena, h->alloc_arena_next,
652               used_bytes, used_bytes >> 20,
653               h->alloc_arena_size, h->alloc_arena_size >> 20);
654   return s;
655 }
656
657 void BV (clib_bihash_foreach_key_value_pair)
658   (BVT (clib_bihash) * h, void *callback, void *arg)
659 {
660   int i, j, k;
661   BVT (clib_bihash_bucket) * b;
662   BVT (clib_bihash_value) * v;
663   void (*fp) (BVT (clib_bihash_kv) *, void *) = callback;
664
665   for (i = 0; i < h->nbuckets; i++)
666     {
667       b = &h->buckets[i];
668       if (b->offset == 0)
669         continue;
670
671       v = BV (clib_bihash_get_value) (h, b->offset);
672       for (j = 0; j < (1 << b->log2_pages); j++)
673         {
674           for (k = 0; k < BIHASH_KVP_PER_PAGE; k++)
675             {
676               if (BV (clib_bihash_is_free) (&v->kvp[k]))
677                 continue;
678
679               (*fp) (&v->kvp[k], arg);
680             }
681           v++;
682         }
683     }
684 }
685
686 /** @endcond */
687
688 /*
689  * fd.io coding-style-patch-verification: ON
690  *
691  * Local Variables:
692  * eval: (c-set-style "gnu")
693  * End:
694  */