diff --git a/graph/path/control_flow_bench_test.go b/graph/path/control_flow_bench_test.go index 8aedbe70..379238c7 100644 --- a/graph/path/control_flow_bench_test.go +++ b/graph/path/control_flow_bench_test.go @@ -7,6 +7,7 @@ package path import ( + "flag" "fmt" "io/ioutil" "math" @@ -23,6 +24,8 @@ import ( "gonum.org/v1/gonum/graph/topo" ) +var slta = flag.Bool("slta", false, "specify DominatorsSLT benchmark") + func BenchmarkDominators(b *testing.B) { testdata := filepath.FromSlash("./testdata/flow") @@ -55,14 +58,25 @@ func BenchmarkDominators(b *testing.B) { continue } - b.Run(test, func(b *testing.B) { - for i := 0; i < b.N; i++ { - d := Dominators(g.root, g) - if got := d.Root(); got.ID() != want.ID() { - b.Fatalf("unexpected root node: got:%d want:%d", got.ID(), want.ID()) + if *slta { + b.Run(test, func(b *testing.B) { + for i := 0; i < b.N; i++ { + d := DominatorsSLT(g.root, g) + if got := d.Root(); got.ID() != want.ID() { + b.Fatalf("unexpected root node: got:%d want:%d", got.ID(), want.ID()) + } } - } - }) + }) + } else { + b.Run(test, func(b *testing.B) { + for i := 0; i < b.N; i++ { + d := Dominators(g.root, g) + if got := d.Root(); got.ID() != want.ID() { + b.Fatalf("unexpected root node: got:%d want:%d", got.ID(), want.ID()) + } + } + }) + } } } diff --git a/graph/path/control_flow.go b/graph/path/control_flow_lt.go similarity index 100% rename from graph/path/control_flow.go rename to graph/path/control_flow_lt.go diff --git a/graph/path/control_flow_slt.go b/graph/path/control_flow_slt.go new file mode 100644 index 00000000..96051e97 --- /dev/null +++ b/graph/path/control_flow_slt.go @@ -0,0 +1,232 @@ +// Copyright ©2017 The gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package path + +import "gonum.org/v1/gonum/graph" + +// DominatorsSLT returns a dominator tree for all nodes in the flow graph +// g starting from the given root node using the sophisticated version of +// the Lengauer-Tarjan algorithm. The SLT algorithm may outperform the +// simple LT algorithm for very large dense graphs. +func DominatorsSLT(root graph.Node, g graph.Directed) DominatorTree { + // The algorithm used here is essentially the + // sophisticated Lengauer and Tarjan algorithm + // described in + // https://doi.org/10.1145%2F357062.357071 + + lt := sLengauerTarjan{ + indexOf: make(map[int64]int), + base: sltNode{semi: -1}, + } + lt.base.label = <.base + + // step 1. + lt.dfs(g, root) + + for i := len(lt.nodes) - 1; i > 0; i-- { + w := lt.nodes[i] + + // step 2. + for _, v := range w.pred { + u := lt.eval(v) + + if u.semi < w.semi { + w.semi = u.semi + } + } + + lt.nodes[w.semi].bucket[w] = struct{}{} + lt.link(w.parent, w) + + // step 3. + for v := range w.parent.bucket { + delete(w.parent.bucket, v) + + u := lt.eval(v) + if u.semi < v.semi { + v.dom = u + } else { + v.dom = w.parent + } + } + } + + // step 4. + for _, w := range lt.nodes[1:] { + if w.dom.node.ID() != lt.nodes[w.semi].node.ID() { + w.dom = w.dom.dom + } + } + + // Construct the public-facing dominator tree structure. + dominatorOf := make(map[int64]graph.Node) + dominatedBy := make(map[int64][]graph.Node) + for _, w := range lt.nodes[1:] { + dominatorOf[w.node.ID()] = w.dom.node + did := w.dom.node.ID() + dominatedBy[did] = append(dominatedBy[did], w.node) + } + return DominatorTree{root: root, dominatorOf: dominatorOf, dominatedBy: dominatedBy} +} + +// sLengauerTarjan holds global state of the Lengauer-Tarjan algorithm. +// This is a mapping between nodes and the postordering of the nodes. +type sLengauerTarjan struct { + // nodes is the nodes traversed during the + // Lengauer-Tarjan depth-first-search. + nodes []*sltNode + // indexOf contains a mapping between + // the id-dense representation of the + // graph and the potentially id-sparse + // nodes held in nodes. + // + // This corresponds to the vertex + // number of the node in the Lengauer- + // Tarjan algorithm. + indexOf map[int64]int + + // base is the base label for balanced + // tree path compression used in the + // sophisticated Lengauer-Tarjan + // algorith, + base sltNode +} + +// sltNode is a graph node with accounting for the Lengauer-Tarjan +// algorithm. +// +// For the purposes of documentation the ltNode is given the name w. +type sltNode struct { + node graph.Node + + // parent is vertex which is the parent of w + // in the spanning tree generated by the search. + parent *sltNode + + // pred is the set of vertices v such that (v, w) + // is an edge of the graph. + pred []*sltNode + + // semi is a number defined as follows: + // (i) After w is numbered but before its semidominator + // is computed, semi is the number of w. + // (ii) After the semidominator of w is computed, semi + // is the number of the semidominator of w. + semi int + + // size is the tree size of w used in the + // sophisticated algorithm. + size int + + // child is the child node of w used in the + // sophisticated algorithm. + child *sltNode + + // bucket is the set of vertices whose + // semidominator is w. + bucket map[*sltNode]struct{} + + // dom is vertex defined as follows: + // (i) After step 3, if the semidominator of w is its + // immediate dominator, then dom is the immediate + // dominator of w. Otherwise dom is a vertex v + // whose number is smaller than w and whose immediate + // dominator is also w's immediate dominator. + // (ii) After step 4, dom is the immediate dominator of w. + dom *sltNode + + // In general ancestor is nil only if w is a tree root + // in the forest; otherwise ancestor is an ancestor + // of w in the forest. + ancestor *sltNode + + // Initially label is w. It is adjusted during + // the algorithm to maintain invariant (3) in the + // Lengauer and Tarjan paper. + label *sltNode +} + +// dfs is the Sophisticated Lengauer-Tarjan DFS procedure. +func (lt *sLengauerTarjan) dfs(g graph.Directed, v graph.Node) { + i := len(lt.nodes) + lt.indexOf[v.ID()] = i + ltv := &sltNode{ + node: v, + semi: i, + size: 1, + child: <.base, + bucket: make(map[*sltNode]struct{}), + } + ltv.label = ltv + lt.nodes = append(lt.nodes, ltv) + + for _, w := range g.From(v) { + wid := w.ID() + + idx, ok := lt.indexOf[wid] + if !ok { + lt.dfs(g, w) + + // We place this below the recursive call + // in contrast to the original algorithm + // since w needs to be initialised, and + // this happens in the child call to dfs. + idx, ok = lt.indexOf[wid] + if !ok { + panic("path: unintialized node") + } + lt.nodes[idx].parent = ltv + } + ltw := lt.nodes[idx] + ltw.pred = append(ltw.pred, ltv) + } +} + +// compress is the Sophisticated Lengauer-Tarjan COMPRESS procedure. +func (lt *sLengauerTarjan) compress(v *sltNode) { + if v.ancestor.ancestor != nil { + lt.compress(v.ancestor) + if v.ancestor.label.semi < v.label.semi { + v.label = v.ancestor.label + } + v.ancestor = v.ancestor.ancestor + } +} + +// eval is the Sophisticated Lengauer-Tarjan EVAL function. +func (lt *sLengauerTarjan) eval(v *sltNode) *sltNode { + if v.ancestor == nil { + return v.label + } + lt.compress(v) + if v.ancestor.label.semi >= v.label.semi { + return v.label + } + return v.ancestor.label +} + +// link is the Sophisticated Lengauer-Tarjan LINK procedure. +func (*sLengauerTarjan) link(v, w *sltNode) { + s := w + for w.label.semi < s.child.label.semi { + if s.size+s.child.child.size >= 2*s.child.size { + s.child.ancestor = s + s.child = s.child.child + } else { + s.child.size = s.size + s.ancestor = s.child + s = s.child + } + } + s.label = w.label + v.size += w.size + if v.size < 2*w.size { + s, v.child = v.child, s + } + for s != nil { + s.ancestor = v + s = s.child + } +} diff --git a/graph/path/control_flow_test.go b/graph/path/control_flow_test.go index 8bd7cb40..af914502 100644 --- a/graph/path/control_flow_test.go +++ b/graph/path/control_flow_test.go @@ -138,20 +138,31 @@ func TestDominators(t *testing.T) { g.SetEdge(e) } - got := Dominators(test.n, g) - if !reflect.DeepEqual(got.root, test.want.root) { - t.Errorf("unexpected dominator tree root: got:%v want:%v", got.root, test.want.root) - } + for _, alg := range []struct { + name string + fn func(graph.Node, graph.Directed) DominatorTree + }{ + {"Dominators", Dominators}, + {"DominatorsSLT", DominatorsSLT}, + } { + got := alg.fn(test.n, g) + if !reflect.DeepEqual(got.root, test.want.root) { + t.Errorf("unexpected dominator tree root from %s: got:%v want:%v", + alg.name, got.root, test.want.root) + } - if !reflect.DeepEqual(got.dominatorOf, test.want.dominatorOf) { - t.Errorf("unexpected dominator tree: got:%v want:%v", got.dominatorOf, test.want.dominatorOf) - } + if !reflect.DeepEqual(got.dominatorOf, test.want.dominatorOf) { + t.Errorf("unexpected dominator tree from %s: got:%v want:%v", + alg.name, got.dominatorOf, test.want.dominatorOf) + } - for _, nodes := range got.dominatedBy { - sort.Sort(ordered.ByID(nodes)) - } - if !reflect.DeepEqual(got.dominatedBy, test.want.dominatedBy) { - t.Errorf("unexpected dominator tree: got:%v want:%v", got.dominatedBy, test.want.dominatedBy) + for _, nodes := range got.dominatedBy { + sort.Sort(ordered.ByID(nodes)) + } + if !reflect.DeepEqual(got.dominatedBy, test.want.dominatedBy) { + t.Errorf("unexpected dominator tree from %s: got:%v want:%v", + alg.name, got.dominatedBy, test.want.dominatedBy) + } } } }