Reorganize source tree to use single autotools instance
[vpp.git] / src / vppinfra / slist.c
diff --git a/src/vppinfra/slist.c b/src/vppinfra/slist.c
new file mode 100644 (file)
index 0000000..892517b
--- /dev/null
@@ -0,0 +1,336 @@
+/*
+  Copyright (c) 2012 Cisco and/or its affiliates.
+
+  * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+#include <vppinfra/slist.h>
+
+/*
+ * skip-list implementation
+ *
+ * Good news / bad news. As balanced binary tree schemes go,
+ * this one seems pretty fast and is reasonably simple. There's a very
+ * limited amount that can be done to mitigate sdram read latency.
+ *
+ * Each active clib_slist_elt_t is on from 1 to N lists. Each active element
+ * is always on the "level-0" list. Since most elements are *only* on
+ * level 0, we keep the level 0 (and level 1) in the element. For those
+ * elements on more than two lists, we switch to a vector. Hence, the
+ * "n" union in slib_slist_elt_t.
+ *
+ * The low-order bit of elt->n.next0[0] is 1 for inlined next indices,
+ * 0 for vector indices (since the allocator always aligns to at least
+ * a 4-byte boundary). We can only represent 2e9 items, but since the
+ * practical performance limit is O(1e7), it doesn't matter.
+ *
+ * We create a "head" element which (by construction) is always
+ * lexically lighter than any other element. This makes a large number
+ * of irritating special cases go away.
+ *
+ * User code is in charge of comparing a supplied key with
+ * the key component of a user pool element. The user tells this code
+ * to add or delete (opaque key, 32-bit integer) pairs to the skip-list.
+ *
+ * The algorithm adds new elements to one or more lists.
+ * For levels greater than zero, the probability of a new element landing on
+ * a list is branching_factor**N. Branching_factor = 0.2 seems to work
+ * OK, yielding about 50 compares per search at O(1e7) items.
+ */
+
+clib_error_t *
+clib_slist_init (clib_slist_t * sp, f64 branching_factor,
+                clib_slist_key_compare_function_t compare,
+                format_function_t format_user_element)
+{
+  clib_slist_elt_t *head;
+  memset (sp, 0, sizeof (sp[0]));
+  sp->branching_factor = branching_factor;
+  sp->format_user_element = format_user_element;
+  sp->compare = compare;
+  sp->seed = 0xdeaddabe;
+  pool_get (sp->elts, head);
+  vec_add1 (head->n.nexts, (u32) ~ 0);
+  head->user_pool_index = (u32) ~ 0;
+  vec_validate (sp->path, 1);
+  vec_validate (sp->occupancy, 0);
+
+  return 0;
+}
+
+/*
+ * slist_search_internal
+ */
+static inline clib_slist_search_result_t
+slist_search_internal (clib_slist_t * sp, void *key, int need_full_path)
+{
+  int level, comp_result;
+  clib_slist_elt_t *search_elt, *head_elt;
+
+  sp->ncompares = 0;
+  /*
+   * index 0 is the magic listhead element which is
+   * lexically lighter than / to the left of every element
+   */
+  search_elt = head_elt = pool_elt_at_index (sp->elts, 0);
+
+  /*
+   * Initial negotiating position, only the head_elt is
+   * lighter than the supplied key
+   */
+  memset (sp->path, 0, vec_len (head_elt->n.nexts) * sizeof (u32));
+
+  /* Walk the fastest lane first */
+  level = vec_len (head_elt->n.nexts) - 1;
+  _vec_len (sp->path) = level + 1;
+
+  while (1)
+    {
+      u32 next_index_this_level;
+      clib_slist_elt_t *prefetch_elt;
+
+      /*
+       * Prefetching the next element at this level makes a measurable
+       * difference, but doesn't fix the dependent read stall problem
+       */
+      prefetch_elt = sp->elts +
+       clib_slist_get_next_at_level (search_elt, level);
+
+      CLIB_PREFETCH (prefetch_elt, CLIB_CACHE_LINE_BYTES, READ);
+
+      /* Compare the key with the current element */
+      comp_result = (search_elt == head_elt) ? 1 :
+       sp->compare (key, search_elt->user_pool_index);
+
+      sp->ncompares++;
+      /* key "lighter" than this element */
+      if (comp_result < 0)
+       {
+         /*
+          * Back up to previous item on this list
+          * and search the next finer-grained list
+          * starting there.
+          */
+         search_elt = pool_elt_at_index (sp->elts, sp->path[level]);
+       next_list:
+         if (level > 0)
+           {
+             level--;
+             continue;
+           }
+         else
+           {
+             return CLIB_SLIST_NO_MATCH;
+           }
+       }
+      /* Match */
+      if (comp_result == 0)
+       {
+         /*
+          * If we're trying to delete an element, we need to
+          * track down all of the elements which point at it.
+          * Otherwise, don't bother with it
+          */
+         if (need_full_path && level > 0)
+           {
+             search_elt = pool_elt_at_index (sp->elts, sp->path[level]);
+             level--;
+             continue;
+           }
+         level = vec_len (head_elt->n.nexts);
+         sp->path[level] = search_elt - sp->elts;
+         _vec_len (sp->path) = level + 1;
+         return CLIB_SLIST_MATCH;
+       }
+      /*
+       * comp_result positive, key is to the right of
+       * this element
+       */
+      sp->path[level] = search_elt - sp->elts;
+
+      /* Out of list at this level? */
+      next_index_this_level =
+       clib_slist_get_next_at_level (search_elt, level);
+      if (next_index_this_level == (u32) ~ 0)
+       goto next_list;
+
+      /* No, try the next element */
+      search_elt = pool_elt_at_index (sp->elts, next_index_this_level);
+    }
+  return 0;                    /* notreached */
+}
+
+u32
+clib_slist_search (clib_slist_t * sp, void *key, u32 * ncompares)
+{
+  clib_slist_search_result_t rv;
+
+  rv = slist_search_internal (sp, key, 0 /* dont need full path */ );
+  if (rv == CLIB_SLIST_MATCH)
+    {
+      clib_slist_elt_t *elt;
+      elt = pool_elt_at_index (sp->elts, sp->path[vec_len (sp->path) - 1]);
+      if (ncompares)
+       *ncompares = sp->ncompares;
+      return elt->user_pool_index;
+    }
+  return (u32) ~ 0;
+}
+
+void
+clib_slist_add (clib_slist_t * sp, void *key, u32 user_pool_index)
+{
+  clib_slist_elt_t *new_elt;
+  clib_slist_search_result_t search_result;
+  int level;
+
+  search_result = slist_search_internal (sp, key,
+                                        0 /* don't need full path */ );
+
+  /* Special case: key exists, just replace user_pool_index */
+  if (PREDICT_FALSE (search_result == CLIB_SLIST_MATCH))
+    {
+      clib_slist_elt_t *elt;
+      elt = pool_elt_at_index (sp->elts, sp->path[0]);
+      elt->user_pool_index = user_pool_index;
+      return;
+    }
+
+  pool_get (sp->elts, new_elt);
+  new_elt->n.nexts = 0;
+  new_elt->user_pool_index = user_pool_index;
+
+  /* sp->path lists elements to the left of key, by level */
+  for (level = 0; level < vec_len (sp->path); level++)
+    {
+      clib_slist_elt_t *prev_elt_this_level;
+      u32 prev_elt_next_index_this_level;
+
+      /* Add to list at the current level */
+      prev_elt_this_level = pool_elt_at_index (sp->elts, sp->path[level]);
+      prev_elt_next_index_this_level = clib_slist_get_next_at_level
+       (prev_elt_this_level, level);
+
+      clib_slist_set_next_at_level (new_elt, prev_elt_next_index_this_level,
+                                   level);
+
+      clib_slist_set_next_at_level (prev_elt_this_level, new_elt - sp->elts,
+                                   level);
+      sp->occupancy[level]++;
+
+      /* Randomly add to the next-higher level */
+      if (random_f64 (&sp->seed) > sp->branching_factor)
+       break;
+    }
+  {
+    /* Time to add a new ply? */
+    clib_slist_elt_t *head_elt = pool_elt_at_index (sp->elts, 0);
+    int top_level = vec_len (head_elt->n.nexts) - 1;
+    if (((f64) sp->occupancy[top_level]) * sp->branching_factor > 1.0)
+      {
+       vec_add1 (sp->occupancy, 0);
+       vec_add1 (head_elt->n.nexts, (u32) ~ 0);
+       /* full match case returns n+1 items */
+       vec_validate (sp->path, vec_len (head_elt->n.nexts));
+      }
+  }
+}
+
+clib_slist_search_result_t
+clib_slist_del (clib_slist_t * sp, void *key)
+{
+  clib_slist_search_result_t search_result;
+  clib_slist_elt_t *del_elt;
+  int level;
+
+  search_result = slist_search_internal (sp, key, 1 /* need full path */ );
+
+  if (PREDICT_FALSE (search_result == CLIB_SLIST_NO_MATCH))
+    return search_result;
+
+  del_elt = pool_elt_at_index (sp->elts, sp->path[vec_len (sp->path) - 1]);
+  ASSERT (vec_len (sp->path) > 1);
+
+  for (level = 0; level < vec_len (sp->path) - 1; level++)
+    {
+      clib_slist_elt_t *path_elt;
+      u32 path_elt_next_index;
+
+      path_elt = pool_elt_at_index (sp->elts, sp->path[level]);
+      path_elt_next_index = clib_slist_get_next_at_level (path_elt, level);
+
+      /* Splice the item out of the list if it's adjacent to the victim */
+      if (path_elt_next_index == del_elt - sp->elts)
+       {
+         sp->occupancy[level]--;
+         path_elt_next_index = clib_slist_get_next_at_level (del_elt, level);
+         clib_slist_set_next_at_level (path_elt, path_elt_next_index, level);
+       }
+    }
+
+  /* If this element is on more than two lists it has a vector of nexts */
+  if (!(del_elt->n.next0[0] & 1))
+    vec_free (del_elt->n.nexts);
+  pool_put (sp->elts, del_elt);
+  return CLIB_SLIST_MATCH;
+}
+
+u8 *
+format_slist (u8 * s, va_list * args)
+{
+  clib_slist_t *sl = va_arg (*args, clib_slist_t *);
+  int verbose = va_arg (*args, int);
+  int i;
+  clib_slist_elt_t *head_elt, *elt;
+
+  s = format (s, "slist 0x%x, %u items, branching_factor %.2f\n", sl,
+             sl->occupancy ? sl->occupancy[0] : 0, sl->branching_factor);
+
+  if (pool_elts (sl->elts) == 0)
+    return s;
+
+  head_elt = pool_elt_at_index (sl->elts, 0);
+
+  for (i = 0; i < vec_len (head_elt->n.nexts); i++)
+    {
+      s = format (s, "level %d: %d elts\n", i,
+                 sl->occupancy ? sl->occupancy[i] : 0);
+
+      if (verbose && head_elt->n.nexts[i] != (u32) ~ 0)
+       {
+         elt = pool_elt_at_index (sl->elts, head_elt->n.nexts[i]);
+         while (elt)
+           {
+             u32 next_index;
+             s = format (s, "%U(%d) ", sl->format_user_element,
+                         elt->user_pool_index, elt - sl->elts);
+             next_index = clib_slist_get_next_at_level (elt, i);
+             ASSERT (next_index != 0x7fffffff);
+             if (next_index == (u32) ~ 0)
+               break;
+             else
+               elt = pool_elt_at_index (sl->elts, next_index);
+           }
+       }
+      s = format (s, "\n");
+    }
+  return s;
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */