graph/flow: new package for (control) flow analysis

This is broken out from path to make room for the additional control
flow analysis routines that are currently being worked on.
This commit is contained in:
Dan Kortschak
2019-03-12 07:27:05 +10:30
parent 6747f05f2d
commit 60a68a3a7f
5 changed files with 10 additions and 4 deletions

View File

@@ -0,0 +1,274 @@
// 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 flow
import (
"flag"
"fmt"
"io/ioutil"
"math"
"path/filepath"
"strings"
"testing"
"golang.org/x/exp/rand"
"gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/encoding"
"gonum.org/v1/gonum/graph/encoding/dot"
"gonum.org/v1/gonum/graph/graphs/gen"
"gonum.org/v1/gonum/graph/iterator"
"gonum.org/v1/gonum/graph/simple"
"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")
fis, err := ioutil.ReadDir(testdata)
if err != nil {
b.Fatalf("failed to open control flow testdata: %v", err)
}
for _, fi := range fis {
name := fi.Name()
ext := filepath.Ext(name)
if ext != ".dot" {
continue
}
test := name[:len(name)-len(ext)]
data, err := ioutil.ReadFile(filepath.Join(testdata, name))
if err != nil {
b.Errorf("failed to open control flow case: %v", err)
continue
}
g := &labeled{DirectedGraph: simple.NewDirectedGraph()}
err = dot.Unmarshal(data, g)
if err != nil {
b.Errorf("failed to unmarshal graph data: %v", err)
continue
}
want := g.root
if want == nil {
b.Error("no entry node label for graph")
continue
}
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())
}
}
})
}
}
}
type labeled struct {
*simple.DirectedGraph
root *node
}
func (g *labeled) NewNode() graph.Node {
return &node{Node: g.DirectedGraph.NewNode(), g: g}
}
func (g *labeled) SetEdge(e graph.Edge) {
if e.To().ID() == e.From().ID() {
// Do not attempt to add self edges.
return
}
g.DirectedGraph.SetEdge(e)
}
type node struct {
graph.Node
name string
g *labeled
}
func (n *node) SetDOTID(id string) {
n.name = id
}
func (n *node) SetAttribute(attr encoding.Attribute) error {
if attr.Key != "label" {
return nil
}
switch attr.Value {
default:
if attr.Value != `"{%0}"` && !strings.HasPrefix(attr.Value, `"{%0|`) {
return nil
}
fallthrough
case "entry", "root":
if n.g.root != nil {
return fmt.Errorf("set root for graph with existing root: old=%q new=%q", n.g.root.name, n.name)
}
n.g.root = n
}
return nil
}
func BenchmarkRandomGraphDominators(b *testing.B) {
tests := []struct {
name string
g func() *simple.DirectedGraph
}{
{name: "gnm-n=1e3-m=1e3", g: gnm(1e3, 1e3)},
{name: "gnm-n=1e3-m=3e3", g: gnm(1e3, 3e3)},
{name: "gnm-n=1e3-m=1e4", g: gnm(1e3, 1e4)},
{name: "gnm-n=1e3-m=3e4", g: gnm(1e3, 3e4)},
{name: "gnm-n=1e4-m=1e4", g: gnm(1e4, 1e4)},
{name: "gnm-n=1e4-m=3e4", g: gnm(1e4, 3e4)},
{name: "gnm-n=1e4-m=1e5", g: gnm(1e4, 1e5)},
{name: "gnm-n=1e4-m=3e5", g: gnm(1e4, 3e5)},
{name: "gnm-n=1e5-m=1e5", g: gnm(1e5, 1e5)},
{name: "gnm-n=1e5-m=3e5", g: gnm(1e5, 3e5)},
{name: "gnm-n=1e5-m=1e6", g: gnm(1e5, 1e6)},
{name: "gnm-n=1e5-m=3e6", g: gnm(1e5, 3e6)},
{name: "gnm-n=1e6-m=1e6", g: gnm(1e6, 1e6)},
{name: "gnm-n=1e6-m=3e6", g: gnm(1e6, 3e6)},
{name: "gnm-n=1e6-m=1e7", g: gnm(1e6, 1e7)},
{name: "gnm-n=1e6-m=3e7", g: gnm(1e6, 3e7)},
{name: "dup-n=1e3-d=0.8-a=0.1", g: duplication(1e3, 0.8, 0.1, math.NaN())},
{name: "dup-n=1e3-d=0.5-a=0.2", g: duplication(1e3, 0.5, 0.2, math.NaN())},
{name: "dup-n=1e4-d=0.8-a=0.1", g: duplication(1e4, 0.8, 0.1, math.NaN())},
{name: "dup-n=1e4-d=0.5-a=0.2", g: duplication(1e4, 0.5, 0.2, math.NaN())},
{name: "dup-n=1e5-d=0.8-a=0.1", g: duplication(1e5, 0.8, 0.1, math.NaN())},
{name: "dup-n=1e5-d=0.5-a=0.2", g: duplication(1e5, 0.5, 0.2, math.NaN())},
}
for _, test := range tests {
rnd := rand.New(rand.NewSource(1))
g := test.g()
// Guess a maximally expensive entry to the graph.
sort, err := topo.Sort(g)
root := sort[0]
if root == nil {
// If we did not get a node in the first position
// then there must be an unorderable set of nodes
// in the first position of the error. Pick one
// of the nodes at random.
unordered := err.(topo.Unorderable)
root = unordered[0][rnd.Intn(len(unordered[0]))]
}
if root == nil {
b.Error("no entry node label for graph")
continue
}
if len(sort) > 1 {
// Ensure that the graph has a complete path
// through the sorted nodes.
// unordered will only be accessed if there is
// a sort element that is nil, in which case
// unordered will contain a set of nodes from
// an SCC.
unordered, _ := err.(topo.Unorderable)
var ui int
for i, v := range sort[1:] {
u := sort[i]
if u == nil {
u = unordered[ui][rnd.Intn(len(unordered[ui]))]
ui++
}
if v == nil {
v = unordered[ui][rnd.Intn(len(unordered[ui]))]
}
if !g.HasEdgeFromTo(u.ID(), v.ID()) {
g.SetEdge(g.NewEdge(u, v))
}
}
}
b.Run(test.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
d := Dominators(root, g)
if got := d.Root(); got.ID() != root.ID() {
b.Fatalf("unexpected root node: got:%d want:%d", got.ID(), root.ID())
}
}
})
}
}
// gnm returns a directed G(n,m) Erdõs-Rényi graph.
func gnm(n, m int) func() *simple.DirectedGraph {
return func() *simple.DirectedGraph {
dg := simple.NewDirectedGraph()
err := gen.Gnm(dg, n, m, rand.New(rand.NewSource(1)))
if err != nil {
panic(err)
}
return dg
}
}
// duplication returns an edge-induced directed subgraph of a
// duplication graph.
func duplication(n int, delta, alpha, sigma float64) func() *simple.DirectedGraph {
return func() *simple.DirectedGraph {
g := undirected{simple.NewDirectedGraph()}
rnd := rand.New(rand.NewSource(1))
err := gen.Duplication(g, n, delta, alpha, sigma, rnd)
if err != nil {
panic(err)
}
for _, e := range graph.EdgesOf(g.Edges()) {
if rnd.Intn(2) == 0 {
g.RemoveEdge(e.From().ID(), e.To().ID())
}
}
return g.DirectedGraph
}
}
type undirected struct {
*simple.DirectedGraph
}
func (g undirected) From(id int64) graph.Nodes {
return iterator.NewOrderedNodes(append(
graph.NodesOf(g.DirectedGraph.From(id)),
graph.NodesOf(g.DirectedGraph.To(id))...))
}
func (g undirected) HasEdgeBetween(xid, yid int64) bool {
return g.DirectedGraph.HasEdgeFromTo(xid, yid)
}
func (g undirected) EdgeBetween(xid, yid int64) graph.Edge {
return g.DirectedGraph.Edge(xid, yid)
}
func (g undirected) SetEdge(e graph.Edge) {
g.DirectedGraph.SetEdge(e)
g.DirectedGraph.SetEdge(g.DirectedGraph.NewEdge(e.To(), e.From()))
}