]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Avoid unnecessary allocation of nodes in the first pass
authorIgor Putovny <igor.putovny@nic.cz>
Wed, 14 Aug 2024 14:47:22 +0000 (16:47 +0200)
committerIgor Putovny <igor.putovny@nic.cz>
Wed, 14 Aug 2024 14:48:35 +0000 (16:48 +0200)
proto/aggregator/aggregator.c

index 61f3621598ddb7c77c6e94735abc359887d99cfc..298598f0fdec0ff5fddae7a08aa1d66c81529005 100644 (file)
@@ -266,14 +266,6 @@ first_pass(struct trie_node *node, linpool *trie_pool)
 
   for (int i = 0; i < 2; i++)
   {
-    if (!node->child[i])
-    {
-      struct trie_node *new = create_new_node(trie_pool);
-      new->parent = node;
-      new->bucket = node->bucket;
-      new->depth = node->depth + 1;
-      node->child[i] = new;
-    }
   }
 
   if (node->child[0])
@@ -281,9 +273,6 @@ first_pass(struct trie_node *node, linpool *trie_pool)
 
   if (node->child[1])
     first_pass(node->child[1], trie_pool);
-
-  /* Discard bucket information in internal nodes */
-  node->bucket = NULL;
 }
 
 static void
@@ -497,24 +486,47 @@ second_pass(struct trie_node *node)
   /* Internal node */
   assert(node->potential_buckets_count == 0);
 
-  struct trie_node * const left = node->child[0];
-  struct trie_node * const right = node->child[1];
-
-  assert(left != NULL);
-  assert(right != NULL);
+  struct trie_node *left  = node->child[0];
+  struct trie_node *right = node->child[1];
 
   /* Postorder traversal */
-  second_pass(left);
-  second_pass(right);
+  if (left)
+    second_pass(left);
+
+  if (right)
+    second_pass(right);
 
   /* Duplicates */
-  for (int i = 0; i < left->potential_buckets_count; i++)
-    for (int j = i + 1; j < left->potential_buckets_count; j++)
-      assert(left->potential_buckets[i] != left->potential_buckets[j]);
+  if (left)
+    for (int i = 0; i < left->potential_buckets_count; i++)
+      for (int j = i + 1; j < left->potential_buckets_count; j++)
+        assert(left->potential_buckets[i] != left->potential_buckets[j]);
+
+  if (right)
+    for (int i = 0; i < right->potential_buckets_count; i++)
+      for (int j = i + 1; j < right->potential_buckets_count; j++)
+        assert(right->potential_buckets[i] != right->potential_buckets[j]);
+
+  assert(node->bucket != NULL);
+
+  struct trie_node artificial_node = {
+    .parent = node,
+    .potential_buckets = { node->bucket },
+    .potential_buckets_count = 1,
+  };
 
-  for (int i = 0; i < right->potential_buckets_count; i++)
-    for (int j = i + 1; j < right->potential_buckets_count; j++)
-      assert(right->potential_buckets[i] != right->potential_buckets[j]);
+  /* Nodes with exactly one child */
+  if ((left && !right) || (!left && right))
+  {
+    if (left && !right)
+      right = &artificial_node;
+    else if (!left && right)
+      left = &artificial_node;
+    else
+      bug("Node does not have only one child");
+  }
+
+  assert(left != NULL && right != NULL);
 
   /*
    * If there are no common buckets among children's buckets, parent's
@@ -550,25 +562,23 @@ remove_potential_buckets(struct trie_node *node)
   node->potential_buckets_count = 0;
 }
 
-/*
- * Third pass of Optimal Route Table Construction (ORTC) algorithm
- */
 static void
-third_pass_helper(struct trie_node *node)
+third_pass_helper(struct aggregator_proto *p, struct trie_node *node)
 {
   assert(node != NULL);
   assert(node->potential_buckets_count <= MAX_POTENTIAL_BUCKETS_COUNT);
 
-  /* Internal nodes should not have a bucket since it was deleted during first pass */
-  if (!is_leaf(node))
-    assert(node->bucket == NULL);
-
   /* Bucket inherited from the closest ancestor with a non-null bucket */
   const struct aggregator_bucket *inherited_bucket = node->parent->ancestor->bucket;
+  assert(inherited_bucket != NULL);
+
+  /* Save bucket of the current node before it is (potentially) deleted in the next step */
+  struct aggregator_bucket * const current_node_bucket = node->bucket;
+  assert(current_node_bucket != NULL);
 
   /*
    * If bucket inherited from ancestor is one of potential buckets of this node,
-   * then this node doesn't need bucket because it inherits one.
+   * then this node doesn't need bucket because it inherits one.
    */
   if (is_bucket_potential(node, inherited_bucket))
   {
@@ -586,21 +596,72 @@ third_pass_helper(struct trie_node *node)
    * Otherwise, it must refer to the closest ancestor of its parent.
    */
   node->ancestor = node->bucket ? node : node->parent->ancestor;
+  assert(node->ancestor != NULL);
+  assert(node->ancestor->bucket != NULL);
+
+  const struct trie_node * const left  = node->child[0];
+  const struct trie_node * const right = node->child[1];
+
+  /* Nodes with exactly one child */
+  if ((left && !right) || (!left && right))
+  {
+    /*
+     * Emulation of node that would have been added in the first pass.
+     * This node has the same bucket as its parent (current node).
+     */
+    struct trie_node artificial_node = {
+      .parent = node,
+      .bucket = current_node_bucket,
+      .potential_buckets = { current_node_bucket },
+      .potential_buckets_count = 1,
+    };
+
+    /*
+     * If the current node (parent of the artificial node) has a bucket,
+     * then the artificial node inherits this bucket.
+     * Otherwise it inherits bucket from the closest ancestor with
+     * a non-null bucket.
+     */
+    const struct aggregator_bucket * const artificial_node_inherited_bucket = node->bucket ? node->bucket : inherited_bucket;
+
+    /*
+     * Nodes that would have been added during first pass are not removed only
+     * if they have a bucket. And they have a bucket only if their potential
+     * bucket is different from the bucket they inherit from their ancestor.
+     * If this condition is met, we need to allocate these nodes and
+     * connect them to the trie.
+     */
+    if (!is_bucket_potential(&artificial_node, artificial_node_inherited_bucket))
+    {
+      struct trie_node *new = create_new_node(p->trie_pool);
+      *new = artificial_node;
+
+      if (left && !right)
+        node->child[1] = new;
+      else if (!left && right)
+        node->child[0] = new;
+      else
+        bug("Node does not have only one child");
+    }
+  }
 
   /* Preorder traversal */
   if (node->child[0])
-    third_pass_helper(node->child[0]);
+    third_pass_helper(p, node->child[0]);
 
   if (node->child[1])
-    third_pass_helper(node->child[1]);
+    third_pass_helper(p, node->child[1]);
 
   /* Leaves with no assigned bucket are removed */
   if (!node->bucket && is_leaf(node))
     remove_node(node);
 }
 
+/*
+ * Third pass of Optimal Route Table Construction (ORTC) algorithm
+ */
 static void
-third_pass(struct trie_node *root)
+third_pass(struct aggregator_proto *p, struct trie_node *root)
 {
   assert(root != NULL);
   assert(root->potential_buckets_count <= MAX_POTENTIAL_BUCKETS_COUNT);
@@ -615,10 +676,10 @@ third_pass(struct trie_node *root)
   root->ancestor = root;
 
   if (root->child[0])
-    third_pass_helper(root->child[0]);
+    third_pass_helper(p, root->child[0]);
 
   if (root->child[1])
-    third_pass_helper(root->child[1]);
+    third_pass_helper(p, root->child[1]);
 }
 
 static void
@@ -954,7 +1015,7 @@ calculate_trie(struct aggregator_proto *p)
     print_prefixes(p->root, p->addr_type);
   }
 
-  third_pass(p->root);
+  third_pass(p, p->root);
 
   if (p->logging)
   {