github.com/gopherd/gonum@v0.0.4/graph/flow/control_flow_bench_test.go (about) 1 // Copyright ©2017 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package flow 6 7 import ( 8 "flag" 9 "fmt" 10 "math" 11 "os" 12 "path/filepath" 13 "strings" 14 "testing" 15 16 "math/rand" 17 18 "github.com/gopherd/gonum/graph" 19 "github.com/gopherd/gonum/graph/encoding" 20 "github.com/gopherd/gonum/graph/encoding/dot" 21 "github.com/gopherd/gonum/graph/graphs/gen" 22 "github.com/gopherd/gonum/graph/iterator" 23 "github.com/gopherd/gonum/graph/simple" 24 "github.com/gopherd/gonum/graph/topo" 25 ) 26 27 var slta = flag.Bool("slta", false, "specify DominatorsSLT benchmark") 28 29 func BenchmarkDominators(b *testing.B) { 30 testdata := filepath.FromSlash("./testdata/flow") 31 32 fis, err := os.ReadDir(testdata) 33 if err != nil { 34 if os.IsNotExist(err) { 35 b.Skipf("no control flow testdata: %v", err) 36 } 37 b.Fatalf("failed to open control flow testdata: %v", err) 38 } 39 for _, fi := range fis { 40 name := fi.Name() 41 ext := filepath.Ext(name) 42 if ext != ".dot" { 43 continue 44 } 45 test := name[:len(name)-len(ext)] 46 47 data, err := os.ReadFile(filepath.Join(testdata, name)) 48 if err != nil { 49 b.Errorf("failed to open control flow case: %v", err) 50 continue 51 } 52 g := &labeled{DirectedGraph: simple.NewDirectedGraph()} 53 err = dot.Unmarshal(data, g) 54 if err != nil { 55 b.Errorf("failed to unmarshal graph data: %v", err) 56 continue 57 } 58 want := g.root 59 if want == nil { 60 b.Error("no entry node label for graph") 61 continue 62 } 63 64 if *slta { 65 b.Run(test, func(b *testing.B) { 66 for i := 0; i < b.N; i++ { 67 d := DominatorsSLT(g.root, g) 68 if got := d.Root(); got.ID() != want.ID() { 69 b.Fatalf("unexpected root node: got:%d want:%d", got.ID(), want.ID()) 70 } 71 } 72 }) 73 } else { 74 b.Run(test, func(b *testing.B) { 75 for i := 0; i < b.N; i++ { 76 d := Dominators(g.root, g) 77 if got := d.Root(); got.ID() != want.ID() { 78 b.Fatalf("unexpected root node: got:%d want:%d", got.ID(), want.ID()) 79 } 80 } 81 }) 82 } 83 } 84 } 85 86 type labeled struct { 87 *simple.DirectedGraph 88 89 root *node 90 } 91 92 func (g *labeled) NewNode() graph.Node { 93 return &node{Node: g.DirectedGraph.NewNode(), g: g} 94 } 95 96 func (g *labeled) SetEdge(e graph.Edge) { 97 if e.To().ID() == e.From().ID() { 98 // Do not attempt to add self edges. 99 return 100 } 101 g.DirectedGraph.SetEdge(e) 102 } 103 104 type node struct { 105 graph.Node 106 name string 107 g *labeled 108 } 109 110 func (n *node) SetDOTID(id string) { 111 n.name = id 112 } 113 114 func (n *node) SetAttribute(attr encoding.Attribute) error { 115 if attr.Key != "label" { 116 return nil 117 } 118 switch attr.Value { 119 default: 120 if attr.Value != `"{%0}"` && !strings.HasPrefix(attr.Value, `"{%0|`) { 121 return nil 122 } 123 fallthrough 124 case "entry", "root": 125 if n.g.root != nil { 126 return fmt.Errorf("set root for graph with existing root: old=%q new=%q", n.g.root.name, n.name) 127 } 128 n.g.root = n 129 } 130 return nil 131 } 132 133 func BenchmarkRandomGraphDominators(b *testing.B) { 134 tests := []struct { 135 name string 136 g func() *simple.DirectedGraph 137 }{ 138 {name: "gnm-n=1e3-m=1e3", g: gnm(1e3, 1e3)}, 139 {name: "gnm-n=1e3-m=3e3", g: gnm(1e3, 3e3)}, 140 {name: "gnm-n=1e3-m=1e4", g: gnm(1e3, 1e4)}, 141 {name: "gnm-n=1e3-m=3e4", g: gnm(1e3, 3e4)}, 142 143 {name: "gnm-n=1e4-m=1e4", g: gnm(1e4, 1e4)}, 144 {name: "gnm-n=1e4-m=3e4", g: gnm(1e4, 3e4)}, 145 {name: "gnm-n=1e4-m=1e5", g: gnm(1e4, 1e5)}, 146 {name: "gnm-n=1e4-m=3e5", g: gnm(1e4, 3e5)}, 147 148 {name: "gnm-n=1e5-m=1e5", g: gnm(1e5, 1e5)}, 149 {name: "gnm-n=1e5-m=3e5", g: gnm(1e5, 3e5)}, 150 {name: "gnm-n=1e5-m=1e6", g: gnm(1e5, 1e6)}, 151 {name: "gnm-n=1e5-m=3e6", g: gnm(1e5, 3e6)}, 152 153 {name: "gnm-n=1e6-m=1e6", g: gnm(1e6, 1e6)}, 154 {name: "gnm-n=1e6-m=3e6", g: gnm(1e6, 3e6)}, 155 {name: "gnm-n=1e6-m=1e7", g: gnm(1e6, 1e7)}, 156 {name: "gnm-n=1e6-m=3e7", g: gnm(1e6, 3e7)}, 157 158 {name: "dup-n=1e3-d=0.8-a=0.1", g: duplication(1e3, 0.8, 0.1, math.NaN())}, 159 {name: "dup-n=1e3-d=0.5-a=0.2", g: duplication(1e3, 0.5, 0.2, math.NaN())}, 160 161 {name: "dup-n=1e4-d=0.8-a=0.1", g: duplication(1e4, 0.8, 0.1, math.NaN())}, 162 {name: "dup-n=1e4-d=0.5-a=0.2", g: duplication(1e4, 0.5, 0.2, math.NaN())}, 163 164 {name: "dup-n=1e5-d=0.8-a=0.1", g: duplication(1e5, 0.8, 0.1, math.NaN())}, 165 {name: "dup-n=1e5-d=0.5-a=0.2", g: duplication(1e5, 0.5, 0.2, math.NaN())}, 166 } 167 168 for _, test := range tests { 169 rnd := rand.New(rand.NewSource(1)) 170 g := test.g() 171 172 // Guess a maximally expensive entry to the graph. 173 sort, err := topo.Sort(g) 174 root := sort[0] 175 if root == nil { 176 // If we did not get a node in the first position 177 // then there must be an unorderable set of nodes 178 // in the first position of the error. Pick one 179 // of the nodes at random. 180 unordered := err.(topo.Unorderable) 181 root = unordered[0][rnd.Intn(len(unordered[0]))] 182 } 183 if root == nil { 184 b.Error("no entry node label for graph") 185 continue 186 } 187 188 if len(sort) > 1 { 189 // Ensure that the graph has a complete path 190 // through the sorted nodes. 191 192 // unordered will only be accessed if there is 193 // a sort element that is nil, in which case 194 // unordered will contain a set of nodes from 195 // an SCC. 196 unordered, _ := err.(topo.Unorderable) 197 198 var ui int 199 for i, v := range sort[1:] { 200 u := sort[i] 201 if u == nil { 202 u = unordered[ui][rnd.Intn(len(unordered[ui]))] 203 ui++ 204 } 205 if v == nil { 206 v = unordered[ui][rnd.Intn(len(unordered[ui]))] 207 } 208 if !g.HasEdgeFromTo(u.ID(), v.ID()) { 209 g.SetEdge(g.NewEdge(u, v)) 210 } 211 } 212 } 213 214 b.Run(test.name, func(b *testing.B) { 215 for i := 0; i < b.N; i++ { 216 d := Dominators(root, g) 217 if got := d.Root(); got.ID() != root.ID() { 218 b.Fatalf("unexpected root node: got:%d want:%d", got.ID(), root.ID()) 219 } 220 } 221 }) 222 } 223 } 224 225 // gnm returns a directed G(n,m) Erdõs-Rényi graph. 226 func gnm(n, m int) func() *simple.DirectedGraph { 227 return func() *simple.DirectedGraph { 228 dg := simple.NewDirectedGraph() 229 err := gen.Gnm(dg, n, m, rand.New(rand.NewSource(1))) 230 if err != nil { 231 panic(err) 232 } 233 return dg 234 } 235 } 236 237 // duplication returns an edge-induced directed subgraph of a 238 // duplication graph. 239 func duplication(n int, delta, alpha, sigma float64) func() *simple.DirectedGraph { 240 return func() *simple.DirectedGraph { 241 g := undirected{simple.NewDirectedGraph()} 242 rnd := rand.New(rand.NewSource(1)) 243 err := gen.Duplication(g, n, delta, alpha, sigma, rnd) 244 if err != nil { 245 panic(err) 246 } 247 for _, e := range graph.EdgesOf(g.Edges()) { 248 if rnd.Intn(2) == 0 { 249 g.RemoveEdge(e.From().ID(), e.To().ID()) 250 } 251 } 252 return g.DirectedGraph 253 } 254 } 255 256 type undirected struct { 257 *simple.DirectedGraph 258 } 259 260 func (g undirected) From(id int64) graph.Nodes { 261 return iterator.NewOrderedNodes(append( 262 graph.NodesOf(g.DirectedGraph.From(id)), 263 graph.NodesOf(g.DirectedGraph.To(id))...)) 264 } 265 266 func (g undirected) HasEdgeBetween(xid, yid int64) bool { 267 return g.DirectedGraph.HasEdgeFromTo(xid, yid) 268 } 269 270 func (g undirected) EdgeBetween(xid, yid int64) graph.Edge { 271 return g.DirectedGraph.Edge(xid, yid) 272 } 273 274 func (g undirected) SetEdge(e graph.Edge) { 275 g.DirectedGraph.SetEdge(e) 276 g.DirectedGraph.SetEdge(g.DirectedGraph.NewEdge(e.To(), e.From())) 277 }