From dae0ea9b0e475d49849875c738cf00b939b698e1 Mon Sep 17 00:00:00 2001 From: Jan Maria Matejka Date: Wed, 12 Sep 2018 15:31:13 +0200 Subject: [PATCH] Trie optimizer This patch optimizes prefix sets like [ 10.0.0.0/24, 10.0.1.0/24, 10.0.2.0/24, 10.0.3.0/24 ] into [ 10.0.0.0/22{24,24} ] This should improve config loading speed with large config files. Works only in config-check mode; the appropriate flag is -O. Beware. The output is written directly on stdout. This patch is not intended to be ever merged with mainline in this form. Instead, we should fix bad lexer performance and also merge the merge-able prefixes on-the-fly during trie creation. Ultimately, we should have better possibility to feed database-like structures into BIRD instead of config file. --- filter/config.Y | 8 ++++- filter/filter.h | 2 ++ filter/trie.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++ sysdep/unix/main.c | 8 ++++- sysdep/unix/unix.h | 1 + 5 files changed, 96 insertions(+), 2 deletions(-) diff --git a/filter/config.Y b/filter/config.Y index 6328ba09..37d93f46 100644 --- a/filter/config.Y +++ b/filter/config.Y @@ -727,7 +727,13 @@ constant: | fprefix_s {NEW_F_VAL; $$ = f_new_inst(FI_CONSTANT_INDIRECT); $$->a1.p = val; *val = $1; } | RTRID { $$ = f_new_inst(FI_CONSTANT); $$->aux = T_QUAD; $$->a2.i = $1; } | '[' set_items ']' { DBG( "We've got a set here..." ); $$ = f_new_inst(FI_CONSTANT); $$->aux = T_SET; $$->a2.p = build_tree($2); DBG( "ook\n" ); } - | '[' fprefix_set ']' { $$ = f_new_inst(FI_CONSTANT); $$->aux = T_PREFIX_SET; $$->a2.p = $2; } + | '[' fprefix_set ']' { + $$ = f_new_inst(FI_CONSTANT); + $$->aux = T_PREFIX_SET; + $$->a2.p = $2; + if (trie_optimize_flag) + trie_optimize($$); + } | ENUM { $$ = f_new_inst(FI_CONSTANT); $$->aux = $1 >> 16; $$->a2.i = $1 & 0xffff; } ; diff --git a/filter/filter.h b/filter/filter.h index 572459d8..046e5526 100644 --- a/filter/filter.h +++ b/filter/filter.h @@ -298,6 +298,8 @@ struct f_trie struct f_trie_node root[0]; /* Root trie node follows */ }; +void trie_optimize(struct f_inst *); + #define NEW_F_VAL struct f_val * val; val = cfg_alloc(sizeof(struct f_val)); #define FF_FORCE_TMPATTR 1 /* Force all attributes to be temporary */ diff --git a/filter/trie.c b/filter/trie.c index 6477fbbf..2978cc3f 100644 --- a/filter/trie.c +++ b/filter/trie.c @@ -70,10 +70,14 @@ */ #include "nest/bird.h" +#include "lib/resource.h" #include "lib/string.h" #include "conf/conf.h" #include "filter/filter.h" +#include +#include + /** * f_new_trie - allocates and returns a new empty trie * @lp: linear pool to allocate items from @@ -181,6 +185,10 @@ trie_add_prefix(struct f_trie *t, ip_addr px, int plen, int l, int h) return n; } + /* All additional prefixes are already covered by this node. */ + if (ipa_equal(ipa_and(n->accept, amask), amask)) + return n; + /* Update accept mask part M2 and go deeper */ n->accept = ipa_or(n->accept, ipa_and(amask, n->mask)); @@ -258,6 +266,77 @@ trie_node_same(struct f_trie_node *t1, struct f_trie_node *t2) return trie_node_same(t1->c[0], t2->c[0]) && trie_node_same(t1->c[1], t2->c[1]); } +static int +trie_node_optimize(struct f_trie_node *t) +{ + int ret = 0; + if (t->c[0]) ret |= trie_node_optimize(t->c[0]); + if (t->c[1]) ret |= trie_node_optimize(t->c[1]); + + if ((!t->c[0]) || (!t->c[1])) return ret; + + if (t->c[0]->plen != t->plen + 1) return ret; + if (t->c[1]->plen != t->plen + 1) return ret; + + ip_addr cmask = ipa_and(t->c[0]->accept, t->c[1]->accept); + if (ipa_zero(cmask)) return ret; + + ip_addr lmask = ipa_xor(t->c[0]->accept, cmask); + ip_addr rmask = ipa_xor(t->c[1]->accept, cmask); + + if (!ipa_zero(lmask) && !ipa_zero(rmask)) + return ret; + + t->c[0]->accept = lmask; + t->c[1]->accept = rmask; + + t->accept = ipa_or(t->accept, cmask); + return 1; +} + +static int +trie_node_count(struct f_trie_node *t) +{ + int ret = 0; + if (t->c[0]) ret += trie_node_count(t->c[0]); + if (t->c[1]) ret += trie_node_count(t->c[1]); + + for ( + ip_addr amask = t->accept; + ipa_nonzero(amask); + ret++, + amask = ipa_xor(amask, ipa_bitrange(amask, NULL, NULL)) + ); + + return ret; +} + +void +trie_optimize(struct f_inst *what) +{ + struct f_trie *t = what->a2.p; + if (!t || !t->root) + return; + + if (!trie_node_optimize(t->root)) + return; + + int size = trie_node_count(t->root) * (STD_ADDRESS_P_LENGTH + 11); + char *buf = xmalloc(size); + buffer b = { + .start = buf, + .pos = buf, + .end = buf + size + }; + + printf("Prefix set in file %s at line %d: ", ifs->file_name, ifs->lino); + + trie_format(t, &b); + buffer_puts(&b, "\n"); + fputs(b.start, stdout); + xfree(buf); +} + /** * trie_same * @t1: first trie to be compared diff --git a/sysdep/unix/main.c b/sysdep/unix/main.c index 99cfab17..8e60a7f1 100644 --- a/sysdep/unix/main.c +++ b/sysdep/unix/main.c @@ -619,12 +619,13 @@ signal_init(void) * Parsing of command-line arguments */ -static char *opt_list = "c:dD:ps:P:u:g:flRh"; +static char *opt_list = "c:dD:pOs:P:u:g:flRh"; static int parse_and_exit; char *bird_name; static char *use_user; static char *use_group; static int run_in_foreground = 0; +int trie_optimize_flag = 0; static void display_usage(void) @@ -649,6 +650,7 @@ display_help(void) " -h, --help Display this information\n" " -l Look for a configuration file and a communication socket\n" " file in the current working directory\n" + " -O Optimize prefix sets; implies -p\n" " -p Test configuration file and exit without start\n" " -P Create a PID file with given filename\n" " -R Apply graceful restart recovery after start\n" @@ -758,6 +760,10 @@ parse_args(int argc, char **argv) case 'p': parse_and_exit = 1; break; + case 'O': + parse_and_exit = 1; + trie_optimize_flag = 1; + break; case 's': path_control_socket = optarg; socket_changed = 1; diff --git a/sysdep/unix/unix.h b/sysdep/unix/unix.h index 3ef2e3ef..4acced58 100644 --- a/sysdep/unix/unix.h +++ b/sysdep/unix/unix.h @@ -18,6 +18,7 @@ struct birdsock; /* main.c */ extern char *bird_name; +extern int trie_optimize_flag; void async_config(void); void async_dump(void); void async_shutdown(void);