gonum.org/v1/gonum@v0.14.0/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  	"golang.org/x/exp/rand"
    17  
    18  	"gonum.org/v1/gonum/graph"
    19  	"gonum.org/v1/gonum/graph/encoding"
    20  	"gonum.org/v1/gonum/graph/encoding/dot"
    21  	"gonum.org/v1/gonum/graph/graphs/gen"
    22  	"gonum.org/v1/gonum/graph/iterator"
    23  	"gonum.org/v1/gonum/graph/simple"
    24  	"gonum.org/v1/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  }