mirror of
https://github.com/gonum/gonum.git
synced 2025-10-05 23:26:52 +08:00

This is broken out from path to make room for the additional control flow analysis routines that are currently being worked on.
275 lines
6.8 KiB
Go
275 lines
6.8 KiB
Go
// 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()))
|
|
}
|