Source-Changes-HG archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

[src/trunk]: src/common/lib/libc/gen Delete the counter from "struct radix_tr...



details:   https://anonhg.NetBSD.org/src/rev/01f33c808f64
branches:  trunk
changeset: 465866:01f33c808f64
user:      ad <ad%NetBSD.org@localhost>
date:      Thu Dec 05 18:50:41 2019 +0000

description:
Delete the counter from "struct radix_tree_node", and in the one place we
need a non-zero check, substitute with a deterministic bitwise OR of all
values in the node.  The structure then becomes cache line aligned.

For each node we now need only touch 2 cache lines instead of 3, which makes
all the operations faster (measured), amortises the cost of not having a
counter, and will avoid intra-pool-page false sharing on MP.

diffstat:

 common/lib/libc/gen/radixtree.c |  101 +++++++++++++++++++++++++++------------
 1 files changed, 69 insertions(+), 32 deletions(-)

diffs (225 lines):

diff -r e615f756f22b -r 01f33c808f64 common/lib/libc/gen/radixtree.c
--- a/common/lib/libc/gen/radixtree.c   Thu Dec 05 18:32:25 2019 +0000
+++ b/common/lib/libc/gen/radixtree.c   Thu Dec 05 18:50:41 2019 +0000
@@ -1,4 +1,4 @@
-/*     $NetBSD: radixtree.c,v 1.18 2019/12/05 18:32:25 ad Exp $        */
+/*     $NetBSD: radixtree.c,v 1.19 2019/12/05 18:50:41 ad Exp $        */
 
 /*-
  * Copyright (c)2011,2012,2013 YAMAMOTO Takashi,
@@ -112,7 +112,7 @@
 #include <sys/cdefs.h>
 
 #if defined(_KERNEL) || defined(_STANDALONE)
-__KERNEL_RCSID(0, "$NetBSD: radixtree.c,v 1.18 2019/12/05 18:32:25 ad Exp $");
+__KERNEL_RCSID(0, "$NetBSD: radixtree.c,v 1.19 2019/12/05 18:50:41 ad Exp $");
 #include <sys/param.h>
 #include <sys/errno.h>
 #include <sys/pool.h>
@@ -122,7 +122,7 @@
 #include <lib/libsa/stand.h>
 #endif /* defined(_STANDALONE) */
 #else /* defined(_KERNEL) || defined(_STANDALONE) */
-__RCSID("$NetBSD: radixtree.c,v 1.18 2019/12/05 18:32:25 ad Exp $");
+__RCSID("$NetBSD: radixtree.c,v 1.19 2019/12/05 18:50:41 ad Exp $");
 #include <assert.h>
 #include <errno.h>
 #include <stdbool.h>
@@ -185,11 +185,16 @@
  * radix_tree_node: an intermediate node
  *
  * we don't care the type of leaf nodes.  they are just void *.
+ *
+ * we used to maintain a count of non-NULL nodes in this structure, but it
+ * prevented it from being aligned to a cache line boundary; the performance
+ * benefit from being cache friendly is greater than the benefit of having
+ * a dedicated count value, especially in multi-processor situations where
+ * we need to avoid intra-pool-page false sharing.
  */
 
 struct radix_tree_node {
        void *n_ptrs[RADIX_TREE_PTR_PER_NODE];
-       unsigned int n_nptrs;   /* # of non-NULL pointers in n_ptrs */
 };
 
 /*
@@ -340,8 +345,8 @@
 {
 
        radix_tree_node_cache = pool_cache_init(sizeof(struct radix_tree_node),
-           0, 0, 0, "radix_tree_node", NULL, IPL_NONE, radix_tree_node_ctor,
-           NULL, NULL);
+           coherency_unit, 0, 0, "radixnode", NULL, IPL_NONE,
+           radix_tree_node_ctor, NULL, NULL);
        KASSERT(radix_tree_node_cache != NULL);
 }
 #endif /* defined(_KERNEL) */
@@ -349,17 +354,57 @@
 static bool __unused
 radix_tree_node_clean_p(const struct radix_tree_node *n)
 {
+#if RADIX_TREE_PTR_PER_NODE > 16
        unsigned int i;
 
-       if (n->n_nptrs != 0) {
-               return false;
-       }
        for (i = 0; i < RADIX_TREE_PTR_PER_NODE; i++) {
                if (n->n_ptrs[i] != NULL) {
                        return false;
                }
        }
        return true;
+#else /* RADIX_TREE_PTR_PER_NODE > 16 */
+       uintptr_t sum;
+
+       /*
+        * Unrolling the above is much better than a tight loop with two
+        * test+branch pairs.  On x86 with gcc 5.5.0 this compiles into 19
+        * deterministic instructions including the "return" and prologue &
+        * epilogue.
+        */
+       sum = (uintptr_t)n->n_ptrs[0];
+       sum |= (uintptr_t)n->n_ptrs[1];
+       sum |= (uintptr_t)n->n_ptrs[2];
+       sum |= (uintptr_t)n->n_ptrs[3];
+#if RADIX_TREE_PTR_PER_NODE > 4
+       sum |= (uintptr_t)n->n_ptrs[4];
+       sum |= (uintptr_t)n->n_ptrs[5];
+       sum |= (uintptr_t)n->n_ptrs[6];
+       sum |= (uintptr_t)n->n_ptrs[7];
+#endif
+#if RADIX_TREE_PTR_PER_NODE > 8
+       sum |= (uintptr_t)n->n_ptrs[8];
+       sum |= (uintptr_t)n->n_ptrs[9];
+       sum |= (uintptr_t)n->n_ptrs[10];
+       sum |= (uintptr_t)n->n_ptrs[11];
+       sum |= (uintptr_t)n->n_ptrs[12];
+       sum |= (uintptr_t)n->n_ptrs[13];
+       sum |= (uintptr_t)n->n_ptrs[14];
+       sum |= (uintptr_t)n->n_ptrs[15];
+#endif
+       return sum == 0;
+#endif /* RADIX_TREE_PTR_PER_NODE > 16 */
+}
+
+static int __unused
+radix_tree_node_count_ptrs(const struct radix_tree_node *n)
+{
+       unsigned int i, c;
+
+       for (i = c = 0; i < RADIX_TREE_PTR_PER_NODE; i++) {
+               c += (n->n_ptrs[i] != NULL);
+       }
+       return c;
 }
 
 static struct radix_tree_node *
@@ -421,7 +466,6 @@
                         */
                        return ENOMEM;
                }
-               n->n_nptrs = 1;
                n->n_ptrs[0] = t->t_root;
                t->t_root = entry_compose(n, tagmask);
                t->t_height++;
@@ -516,10 +560,6 @@
                                return NULL;
                        }
                        *vpp = c;
-                       if (n != NULL) {
-                               KASSERT(n->n_nptrs < RADIX_TREE_PTR_PER_NODE);
-                               n->n_nptrs++;
-                       }
                }
                n = c;
                vpp = &n->n_ptrs[i];
@@ -531,10 +571,6 @@
        }
        if (alloc) {
                KASSERT(*vpp == NULL);
-               if (n != NULL) {
-                       KASSERT(n->n_nptrs < RADIX_TREE_PTR_PER_NODE);
-                       n->n_nptrs++;
-               }
        }
        if (path != NULL) {
                path->p_lastidx = refs - path->p_refs;
@@ -634,9 +670,7 @@
                entry = *pptr;
                n = entry_ptr(entry);
                KASSERT(n != NULL);
-               KASSERT(n->n_nptrs > 0);
-               n->n_nptrs--;
-               if (n->n_nptrs > 0) {
+               if (!radix_tree_node_clean_p(n)) {
                        break;
                }
                radix_tree_free_node(n);
@@ -663,7 +697,7 @@
                entry = *pptr;
                n = entry_ptr(entry);
                KASSERT(n != NULL);
-               KASSERT(n->n_nptrs > 0);
+               KASSERT(!radix_tree_node_clean_p(n));
                newmask = any_children_tagmask(n);
                if (newmask == entry_tagmask(entry)) {
                        break;
@@ -702,10 +736,7 @@
 {
        void **vpp __unused;
 
-#if defined(DIAGNOSTIC)
-       vpp =
-#endif /* defined(DIAGNOSTIC) */
-       radix_tree_lookup_ptr(t, idx, path, false, tagmask);
+       vpp = radix_tree_lookup_ptr(t, idx, path, false, tagmask);
        KASSERT(vpp == NULL ||
            vpp == path_pptr(t, path, path->p_lastidx));
        KASSERT(&t->t_root == path_pptr(t, path, 0));
@@ -795,7 +826,15 @@
                        break;
                }
                n = path_node(t, path, lastidx - 1);
-               if (*vpp != NULL && n->n_nptrs == 1) {
+               /*
+                * we used to have an integer counter in the node, and this
+                * optimization made sense then, even though marginal.  it
+                * no longer provides benefit with the structure cache line
+                * aligned and the counter replaced by an unrolled sequence
+                * testing the pointers in batch.
+                */
+#if 0
+               if (*vpp != NULL && radix_tree_node_count_ptrs(n) == 1) {
                        /*
                         * optimization; if the node has only a single pointer
                         * and we've already visited it, there's no point to
@@ -803,6 +842,7 @@
                         */
                        goto no_siblings;
                }
+#endif /* 0 */
                for (i = vpp - n->n_ptrs + step; i != guard; i += step) {
                        KASSERT(i < RADIX_TREE_PTR_PER_NODE);
                        if (entry_match_p(n->n_ptrs[i], tagmask)) {
@@ -986,10 +1026,7 @@
        int i;
 
        KASSERT(tagmask != 0);
-#if defined(DIAGNOSTIC)
-       vpp =
-#endif /* defined(DIAGNOSTIC) */
-       radix_tree_lookup_ptr(t, idx, &path, false, 0);
+       vpp = radix_tree_lookup_ptr(t, idx, &path, false, 0);
        KASSERT(vpp != NULL);
        KASSERT(*vpp != NULL);
        KASSERT(path.p_lastidx == t->t_height);
@@ -1087,7 +1124,7 @@
        }
        n = entry_ptr(vp);
        assert(any_children_tagmask(n) == entry_tagmask(vp));
-       printf(" (%u children)\n", n->n_nptrs);
+       printf(" (%u children)\n", radix_tree_node_count_ptrs(n));
        for (i = 0; i < __arraycount(n->n_ptrs); i++) {
                void *c;
 



Home | Main Index | Thread Index | Old Index