mirror of
https://github.com/gonum/gonum.git
synced 2025-10-15 19:50:48 +08:00

Both algorithms are included since the LTA appears to beat the SLTA for all normal uses, but the SLTA beats the LTA for very large dense graphs. Leave tools in the benchmark code to allow users to determine which one they want to use for their data.
273 lines
6.7 KiB
Go
273 lines
6.7 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.
|
|
|
|
// +build go1.7
|
|
|
|
package path
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"math"
|
|
"math/rand"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"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/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, v) {
|
|
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 g.Edges() {
|
|
if rnd.Intn(2) == 0 {
|
|
g.RemoveEdge(e)
|
|
}
|
|
}
|
|
return g.DirectedGraph
|
|
}
|
|
}
|
|
|
|
type undirected struct {
|
|
*simple.DirectedGraph
|
|
}
|
|
|
|
func (g undirected) From(n graph.Node) []graph.Node {
|
|
return append(g.DirectedGraph.From(n), g.DirectedGraph.To(n)...)
|
|
}
|
|
|
|
func (g undirected) HasEdgeBetween(x, y graph.Node) bool {
|
|
return g.DirectedGraph.HasEdgeFromTo(x, y)
|
|
}
|
|
|
|
func (g undirected) EdgeBetween(x, y graph.Node) graph.Edge {
|
|
return g.DirectedGraph.Edge(x, y)
|
|
}
|
|
|
|
func (g undirected) SetEdge(e graph.Edge) {
|
|
g.DirectedGraph.SetEdge(e)
|
|
g.DirectedGraph.SetEdge(g.DirectedGraph.NewEdge(e.To(), e.From()))
|
|
}
|