diff --git a/bfs.go b/bfs.go index 91fe3d0..c364d51 100644 --- a/bfs.go +++ b/bfs.go @@ -7,17 +7,20 @@ package graph func BFS(g Iterator, v int, do func(v, w int, c int64)) { visited := make([]bool, g.Order()) visited[v] = true - for queue := []int{v}; len(queue) > 0; { - v := queue[0] - queue = queue[1:] - g.Visit(v, func(w int, c int64) (skip bool) { - if visited[w] { - return - } - do(v, w, c) - visited[w] = true - queue = append(queue, w) + queue := []int{v} + + fn := func(w int, c int64) (skip bool) { + if visited[w] { return - }) + } + do(v, w, c) + visited[w] = true + queue = append(queue, w) + return + } + for len(queue) > 0 { + v = queue[0] + queue = queue[1:] + g.Visit(v, fn) } } diff --git a/bfs_test.go b/bfs_test.go index a7f5a45..c54c46f 100644 --- a/bfs_test.go +++ b/bfs_test.go @@ -1,6 +1,7 @@ package graph import ( + "math/rand" "strconv" "testing" ) @@ -26,3 +27,19 @@ func TestBFS(t *testing.T) { t.Errorf("BFS: %s", mess) } } + +func BenchmarkBFS(b *testing.B) { + n := 1000 + g := New(n) + for i := 0; i < 5*n; i++ { + g.AddBoth(rand.Intn(n), rand.Intn(n)) + } + + fn := func(v, w int, c int64) {} + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + BFS(g, 0, fn) + } +} diff --git a/bipart.go b/bipart.go index 566157e..d02697e 100644 --- a/bipart.go +++ b/bipart.go @@ -12,31 +12,36 @@ func Bipartition(g Iterator) (part []int, ok bool) { ) colors := make([]color, g.Order()) whiteCount := 0 + + var src int + var queue []int + do := func(w int, _ int64) (skip bool) { + v := src + switch { + case colors[w] != none: + if colors[v] == colors[w] { + skip = true + } + return + case colors[v] == white: + colors[w] = black + default: + colors[w] = white + whiteCount++ + } + queue = append(queue, w) + return + } for v := range colors { if colors[v] != none { continue } colors[v] = white whiteCount++ - for queue := []int{v}; len(queue) > 0; { - v := queue[0] + for queue = []int{v}; len(queue) > 0; { + src = queue[0] queue = queue[1:] - if g.Visit(v, func(w int, _ int64) (skip bool) { - switch { - case colors[w] != none: - if colors[v] == colors[w] { - skip = true - } - return - case colors[v] == white: - colors[w] = black - default: - colors[w] = white - whiteCount++ - } - queue = append(queue, w) - return - }) { + if g.Visit(src, do) { return []int{}, false } } diff --git a/bipart_test.go b/bipart_test.go index 2b2f644..eb22301 100644 --- a/bipart_test.go +++ b/bipart_test.go @@ -1,6 +1,7 @@ package graph import ( + "math/rand" "testing" ) @@ -93,3 +94,18 @@ func TestBipartition(t *testing.T) { t.Errorf("Bipartition: %s", mess) } } + +func BenchmarkBipartition(b *testing.B) { + n := 1000 + g := New(n) + for i := 0; i < 3*n; i++ { + g.AddBoth(rand.Intn(n), rand.Intn(n)) + } + h := Sort(g) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = Bipartition(h) + } +} diff --git a/euler.go b/euler.go index 52f959a..3fb8fc6 100644 --- a/euler.go +++ b/euler.go @@ -6,13 +6,18 @@ func EulerDirected(g Iterator) (walk []int, ok bool) { n := g.Order() degree := make([]int, n) // outdegree - indegree for each vertex edgeCount := 0 + + var src int + degreeFn := func(w int, _ int64) (skip bool) { + v := src + edgeCount++ + degree[v]++ + degree[w]-- + return + } for v := range degree { - g.Visit(v, func(w int, _ int64) (skip bool) { - edgeCount++ - degree[v]++ - degree[w]-- - return - }) + src = v + g.Visit(src, degreeFn) } if edgeCount == 0 { return []int{}, true @@ -33,11 +38,14 @@ func EulerDirected(g Iterator) (walk []int, ok bool) { // Make a copy of g h := make([][]int, n) + copyFn := func(w int, _ int64) (skip bool) { + v := src + h[v] = append(h[v], w) + return + } for v := range h { - g.Visit(v, func(w int, _ int64) (skip bool) { - h[v] = append(h[v], w) - return - }) + src = v + g.Visit(src, copyFn) } // Find a starting point with neighbors. @@ -77,14 +85,19 @@ func EulerUndirected(g Iterator) (walk []int, ok bool) { n := g.Order() out := make([]int, n) // outdegree for each vertex edgeCount := 0 + + var src int + outDegreeFn := func(w int, _ int64) (skip bool) { + v := src + edgeCount++ + if v != w { + out[v]++ + } + return + } for v := range out { - g.Visit(v, func(w int, _ int64) (skip bool) { - edgeCount++ - if v != w { - out[v]++ - } - return - }) + src = v + g.Visit(src, outDegreeFn) } if edgeCount == 0 { return []int{}, true diff --git a/euler_test.go b/euler_test.go index 193bd1c..60b694d 100644 --- a/euler_test.go +++ b/euler_test.go @@ -1,6 +1,7 @@ package graph import ( + "math/rand" "testing" ) @@ -155,3 +156,39 @@ func TestEulerUndirected(t *testing.T) { t.Errorf("EulerUndirected: %s", mess) } } + +func BenchmarkEulerDirected(b *testing.B) { + n := 100 + g := New(n) + for i := 0; i < n-1; i++ { + g.Add(i, i+1) + } + for i := 0; i < 3*n; i++ { + g.Add(rand.Intn(n), rand.Intn(n)) + } + h := Sort(g) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = EulerDirected(h) + } +} + +func BenchmarkEulerUndirected(b *testing.B) { + n := 100 + g := New(n) + for i := 0; i < n-1; i++ { + g.AddBoth(i, i+1) + } + for i := 0; i < 3*n; i++ { + g.Add(rand.Intn(n), rand.Intn(n)) + } + h := Sort(g) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = EulerUndirected(h) + } +} diff --git a/immutable.go b/immutable.go index 8f66979..e547e76 100644 --- a/immutable.go +++ b/immutable.go @@ -41,18 +41,21 @@ func Transpose(g Iterator) *Immutable { func build(g Iterator, transpose bool) *Immutable { n := g.Order() h := &Immutable{edges: make([][]neighbor, n)} - for v := range h.edges { - g.Visit(v, func(w int, c int64) (skip bool) { - if w < 0 || w >= n { - panic("vertex out of range: " + strconv.Itoa(w)) - } - if transpose { - h.edges[w] = append(h.edges[w], neighbor{v, c}) - } else { - h.edges[v] = append(h.edges[v], neighbor{w, c}) - } - return - }) + + var v int + do := func(w int, c int64) (skip bool) { + if w < 0 || w >= n { + panic("vertex out of range: " + strconv.Itoa(w)) + } + if transpose { + h.edges[w] = append(h.edges[w], neighbor{v, c}) + } else { + h.edges[v] = append(h.edges[v], neighbor{w, c}) + } + return + } + for v = range h.edges { + g.Visit(v, do) sort.Slice(h.edges[v], func(i, j int) bool { if e := h.edges[v]; e[i].vertex == e[j].vertex { return e[i].cost < e[j].cost diff --git a/immutable_test.go b/immutable_test.go index f530ab7..e2e9b5b 100644 --- a/immutable_test.go +++ b/immutable_test.go @@ -197,3 +197,29 @@ func TestDegreeImm(t *testing.T) { t.Errorf("g5c.Degree(1) %s", mess) } } + +func BenchmarkSort(b *testing.B) { + n := 1000 + g := New(n) + for i := 0; i < 3*n; i++ { + g.Add(rand.Intn(n), rand.Intn(n)) + } + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = Sort(g) + } +} + +func BenchmarkTranspose(b *testing.B) { + n := 1000 + g := New(n) + for i := 0; i < 3*n; i++ { + g.Add(rand.Intn(n), rand.Intn(n)) + } + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = Transpose(g) + } +} diff --git a/mst.go b/mst.go index dc72292..7a04f89 100644 --- a/mst.go +++ b/mst.go @@ -19,16 +19,18 @@ func MST(g Iterator) (parent []int) { // Prim's algorithm Q := newPrioQueue(cost) + var v int + do := func(w int, c int64) (skip bool) { + if Q.Contains(w) && c < cost[w] { + cost[w] = c + Q.Fix(w) + parent[w] = v + } + return + } for Q.Len() > 0 { - v := Q.Pop() - g.Visit(v, func(w int, c int64) (skip bool) { - if Q.Contains(w) && c < cost[w] { - cost[w] = c - Q.Fix(w) - parent[w] = v - } - return - }) + v = Q.Pop() + g.Visit(v, do) } return } diff --git a/mst_test.go b/mst_test.go index dff0c47..092b461 100644 --- a/mst_test.go +++ b/mst_test.go @@ -34,6 +34,7 @@ func TestMST(t *testing.T) { func BenchmarkMST(b *testing.B) { n := 1000 + b.ReportAllocs() b.StopTimer() g := New(n) for i := 0; i < 2*n; i++ { diff --git a/path.go b/path.go index 850ddec..df86072 100644 --- a/path.go +++ b/path.go @@ -7,7 +7,7 @@ package graph // The time complexity is O((|E| + |V|)⋅log|V|), where |E| is the number of edges // and |V| the number of vertices in the graph. func ShortestPath(g Iterator, v, w int) (path []int, dist int64) { - parent, distances := ShortestPaths(g, v) + parent, distances := shortestPath(g, v, w) path, dist = []int{}, distances[w] if dist == -1 { return @@ -31,6 +31,12 @@ func ShortestPath(g Iterator, v, w int) (path []int, dist int64) { // The time complexity is O((|E| + |V|)⋅log|V|), where |E| is the number of edges // and |V| the number of vertices in the graph. func ShortestPaths(g Iterator, v int) (parent []int, dist []int64) { + // Use -1 to search for a vertex that doesn't exists so it will + // search for all the shortest paths from v. + return shortestPath(g, v, -1) +} + +func shortestPath(g Iterator, v, w int) (parent []int, dist []int64) { n := g.Order() dist = make([]int64, n) parent = make([]int, n) @@ -42,23 +48,28 @@ func ShortestPaths(g Iterator, v int) (parent []int, dist []int64) { // Dijkstra's algorithm Q := emptyPrioQueue(dist) Q.Push(v) + + do := func(w int, d int64) (skip bool) { + if d < 0 { + return + } + alt := dist[v] + d + switch { + case dist[w] == -1: + dist[w], parent[w] = alt, v + Q.Push(w) + case alt < dist[w]: + dist[w], parent[w] = alt, v + Q.Fix(w) + } + return + } for Q.Len() > 0 { - v := Q.Pop() - g.Visit(v, func(w int, d int64) (skip bool) { - if d < 0 { - return - } - alt := dist[v] + d - switch { - case dist[w] == -1: - dist[w], parent[w] = alt, v - Q.Push(w) - case alt < dist[w]: - dist[w], parent[w] = alt, v - Q.Fix(w) - } + v = Q.Pop() + if v == w { return - }) + } + g.Visit(v, do) } return } diff --git a/path_test.go b/path_test.go index 2350c8c..a3642f9 100644 --- a/path_test.go +++ b/path_test.go @@ -50,6 +50,21 @@ func TestShortestPath(t *testing.T) { } } +func BenchmarkShortestPath(b *testing.B) { + n := 1000 + b.StopTimer() + g := New(n) + for i := 0; i < n; i++ { + g.Add(0, rand.Intn(n)) + g.Add(rand.Intn(n), rand.Intn(n)) + } + b.ReportAllocs() + b.StartTimer() + for i := 0; i < b.N; i++ { + _, _ = ShortestPath(g, 0, n-1) + } +} + func BenchmarkShortestPaths(b *testing.B) { n := 1000 b.StopTimer() @@ -58,6 +73,7 @@ func BenchmarkShortestPaths(b *testing.B) { g.Add(0, rand.Intn(n)) g.Add(rand.Intn(n), rand.Intn(n)) } + b.ReportAllocs() b.StartTimer() for i := 0; i < b.N; i++ { _, _ = ShortestPaths(g, 0) diff --git a/testdata/bench_after/Acyclic b/testdata/bench_after/Acyclic new file mode 100644 index 0000000..4681931 --- /dev/null +++ b/testdata/bench_after/Acyclic @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkAcyclic-8 16905 66498 ns/op 12386 B/op 13 allocs/op +PASS +ok github.com/rschio/graph 1.848s diff --git a/testdata/bench_after/BFS b/testdata/bench_after/BFS new file mode 100644 index 0000000..4d9cdb8 --- /dev/null +++ b/testdata/bench_after/BFS @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkBFS-8 5128 206326 ns/op 23213 B/op 17 allocs/op +PASS +ok github.com/rschio/graph 1.089s diff --git a/testdata/bench_after/Bipartition b/testdata/bench_after/Bipartition new file mode 100644 index 0000000..72dd884 --- /dev/null +++ b/testdata/bench_after/Bipartition @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkBipartition-8 2384719 1398 ns/op 2792 B/op 14 allocs/op +PASS +ok github.com/rschio/graph 3.860s diff --git a/testdata/bench_after/Components b/testdata/bench_after/Components new file mode 100644 index 0000000..41b1268 --- /dev/null +++ b/testdata/bench_after/Components @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkComponents-8 7056 148831 ns/op 54846 B/op 235 allocs/op +PASS +ok github.com/rschio/graph 1.072s diff --git a/testdata/bench_after/Connected b/testdata/bench_after/Connected new file mode 100644 index 0000000..b247c3c --- /dev/null +++ b/testdata/bench_after/Connected @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkConnected-8 11476 103912 ns/op 8267 B/op 5 allocs/op +PASS +ok github.com/rschio/graph 1.986s diff --git a/testdata/bench_after/EulerDirected b/testdata/bench_after/EulerDirected new file mode 100644 index 0000000..613ae4e --- /dev/null +++ b/testdata/bench_after/EulerDirected @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkEulerDirected-8 589666 2005 ns/op 960 B/op 4 allocs/op +PASS +ok github.com/rschio/graph 2.041s diff --git a/testdata/bench_after/EulerUndirected b/testdata/bench_after/EulerUndirected new file mode 100644 index 0000000..1c55a66 --- /dev/null +++ b/testdata/bench_after/EulerUndirected @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkEulerUndirected-8 508767 2387 ns/op 968 B/op 5 allocs/op +PASS +ok github.com/rschio/graph 2.188s diff --git a/testdata/bench_after/MST b/testdata/bench_after/MST new file mode 100644 index 0000000..52f14dc --- /dev/null +++ b/testdata/bench_after/MST @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkMST-8 5379 189717 ns/op 32951 B/op 8 allocs/op +PASS +ok github.com/rschio/graph 1.048s diff --git a/testdata/bench_after/ShortestPath b/testdata/bench_after/ShortestPath new file mode 100644 index 0000000..63b0cc1 --- /dev/null +++ b/testdata/bench_after/ShortestPath @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkShortestPath-8 26695 60170 ns/op 39753 B/op 21 allocs/op +PASS +ok github.com/rschio/graph 3.108s diff --git a/testdata/bench_after/ShortestPaths b/testdata/bench_after/ShortestPaths new file mode 100644 index 0000000..8c6f9c9 --- /dev/null +++ b/testdata/bench_after/ShortestPaths @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkShortestPaths-8 14500 79967 ns/op 39729 B/op 19 allocs/op +PASS +ok github.com/rschio/graph 2.003s diff --git a/testdata/bench_after/Sort b/testdata/bench_after/Sort new file mode 100644 index 0000000..fb298dd --- /dev/null +++ b/testdata/bench_after/Sort @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkSort-8 2433 499979 ns/op 201219 B/op 5031 allocs/op +PASS +ok github.com/rschio/graph 1.272s diff --git a/testdata/bench_after/TopSort b/testdata/bench_after/TopSort new file mode 100644 index 0000000..5edc163 --- /dev/null +++ b/testdata/bench_after/TopSort @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkTopSort-8 17028 69394 ns/op 16474 B/op 22 allocs/op +PASS +ok github.com/rschio/graph 1.900s diff --git a/testdata/bench_after/Transpose b/testdata/bench_after/Transpose new file mode 100644 index 0000000..3b2db5e --- /dev/null +++ b/testdata/bench_after/Transpose @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkTranspose-8 3045 401361 ns/op 169624 B/op 4002 allocs/op +PASS +ok github.com/rschio/graph 1.267s diff --git a/testdata/bench_before/Acyclic b/testdata/bench_before/Acyclic new file mode 100644 index 0000000..27afc9b --- /dev/null +++ b/testdata/bench_before/Acyclic @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkAcyclic-8 9379 127766 ns/op 51988 B/op 1171 allocs/op +PASS +ok github.com/rschio/graph 2.072s diff --git a/testdata/bench_before/BFS b/testdata/bench_before/BFS new file mode 100644 index 0000000..469edd6 --- /dev/null +++ b/testdata/bench_before/BFS @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkBFS-8 3776 269224 ns/op 86849 B/op 1015 allocs/op +PASS +ok github.com/rschio/graph 1.057s diff --git a/testdata/bench_before/Bipartition b/testdata/bench_before/Bipartition new file mode 100644 index 0000000..142510f --- /dev/null +++ b/testdata/bench_before/Bipartition @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkBipartition-8 2253801 1875 ns/op 3616 B/op 26 allocs/op +PASS +ok github.com/rschio/graph 4.708s diff --git a/testdata/bench_before/Components b/testdata/bench_before/Components new file mode 100644 index 0000000..15719cf --- /dev/null +++ b/testdata/bench_before/Components @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkComponents-8 5728 195865 ns/op 86816 B/op 1234 allocs/op +PASS +ok github.com/rschio/graph 1.148s diff --git a/testdata/bench_before/Connected b/testdata/bench_before/Connected new file mode 100644 index 0000000..89109a9 --- /dev/null +++ b/testdata/bench_before/Connected @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkConnected-8 7894 151161 ns/op 40237 B/op 1004 allocs/op +PASS +ok github.com/rschio/graph 2.047s diff --git a/testdata/bench_before/EulerDirected b/testdata/bench_before/EulerDirected new file mode 100644 index 0000000..8b1c551 --- /dev/null +++ b/testdata/bench_before/EulerDirected @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkEulerDirected-8 147134 7276 ns/op 5712 B/op 103 allocs/op +PASS +ok github.com/rschio/graph 1.159s diff --git a/testdata/bench_before/EulerUndirected b/testdata/bench_before/EulerUndirected new file mode 100644 index 0000000..490999f --- /dev/null +++ b/testdata/bench_before/EulerUndirected @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkEulerUndirected-8 152704 7065 ns/op 5720 B/op 104 allocs/op +PASS +ok github.com/rschio/graph 1.164s diff --git a/testdata/bench_before/MST b/testdata/bench_before/MST new file mode 100644 index 0000000..f261fba --- /dev/null +++ b/testdata/bench_before/MST @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkMST-8 4191 249128 ns/op 96883 B/op 1006 allocs/op +PASS +ok github.com/rschio/graph 1.078s diff --git a/testdata/bench_before/ShortestPath b/testdata/bench_before/ShortestPath new file mode 100644 index 0000000..f63c789 --- /dev/null +++ b/testdata/bench_before/ShortestPath @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkShortestPath-8 8378 140683 ns/op 79875 B/op 856 allocs/op +PASS +ok github.com/rschio/graph 1.198s diff --git a/testdata/bench_before/ShortestPaths b/testdata/bench_before/ShortestPaths new file mode 100644 index 0000000..b578115 --- /dev/null +++ b/testdata/bench_before/ShortestPaths @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkShortestPaths-8 10394 121272 ns/op 79226 B/op 841 allocs/op +PASS +ok github.com/rschio/graph 2.118s diff --git a/testdata/bench_before/Sort b/testdata/bench_before/Sort new file mode 100644 index 0000000..117ff1f --- /dev/null +++ b/testdata/bench_before/Sort @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkSort-8 2175 608159 ns/op 249174 B/op 6030 allocs/op +PASS +ok github.com/rschio/graph 1.383s diff --git a/testdata/bench_before/TopSort b/testdata/bench_before/TopSort new file mode 100644 index 0000000..2126721 --- /dev/null +++ b/testdata/bench_before/TopSort @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkTopSort-8 9980 134713 ns/op 56076 B/op 1180 allocs/op +PASS +ok github.com/rschio/graph 2.332s diff --git a/testdata/bench_before/Transpose b/testdata/bench_before/Transpose new file mode 100644 index 0000000..95e07c0 --- /dev/null +++ b/testdata/bench_before/Transpose @@ -0,0 +1,7 @@ +goos: linux +goarch: amd64 +pkg: github.com/rschio/graph +cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz +BenchmarkTranspose-8 2698 404269 ns/op 217577 B/op 5001 allocs/op +PASS +ok github.com/rschio/graph 1.140s diff --git a/testdata/bench_results/Acyclic b/testdata/bench_results/Acyclic new file mode 100644 index 0000000..f057162 --- /dev/null +++ b/testdata/bench_results/Acyclic @@ -0,0 +1,8 @@ +name old time/op new time/op delta +Acyclic-8 128µs ± 0% 66µs ± 0% -47.95% + +name old alloc/op new alloc/op delta +Acyclic-8 52.0kB ± 0% 12.4kB ± 0% -76.18% + +name old allocs/op new allocs/op delta +Acyclic-8 1.17k ± 0% 0.01k ± 0% -98.89% diff --git a/testdata/bench_results/BFS b/testdata/bench_results/BFS new file mode 100644 index 0000000..357c0ca --- /dev/null +++ b/testdata/bench_results/BFS @@ -0,0 +1,8 @@ +name old time/op new time/op delta +BFS-8 269µs ± 0% 206µs ± 0% -23.36% + +name old alloc/op new alloc/op delta +BFS-8 86.8kB ± 0% 23.2kB ± 0% -73.27% + +name old allocs/op new allocs/op delta +BFS-8 1.01k ± 0% 0.02k ± 0% -98.33% diff --git a/testdata/bench_results/Bipartition b/testdata/bench_results/Bipartition new file mode 100644 index 0000000..f8a3cab --- /dev/null +++ b/testdata/bench_results/Bipartition @@ -0,0 +1,8 @@ +name old time/op new time/op delta +Bipartition-8 1.88µs ± 0% 1.40µs ± 0% -25.44% + +name old alloc/op new alloc/op delta +Bipartition-8 3.62kB ± 0% 2.79kB ± 0% -22.79% + +name old allocs/op new allocs/op delta +Bipartition-8 26.0 ± 0% 14.0 ± 0% -46.15% diff --git a/testdata/bench_results/Components b/testdata/bench_results/Components new file mode 100644 index 0000000..f9d7a86 --- /dev/null +++ b/testdata/bench_results/Components @@ -0,0 +1,8 @@ +name old time/op new time/op delta +Components-8 196µs ± 0% 149µs ± 0% -24.01% + +name old alloc/op new alloc/op delta +Components-8 86.8kB ± 0% 54.8kB ± 0% -36.83% + +name old allocs/op new allocs/op delta +Components-8 1.23k ± 0% 0.23k ± 0% -80.96% diff --git a/testdata/bench_results/Connected b/testdata/bench_results/Connected new file mode 100644 index 0000000..adc21e3 --- /dev/null +++ b/testdata/bench_results/Connected @@ -0,0 +1,8 @@ +name old time/op new time/op delta +Connected-8 151µs ± 0% 104µs ± 0% -31.26% + +name old alloc/op new alloc/op delta +Connected-8 40.2kB ± 0% 8.3kB ± 0% -79.45% + +name old allocs/op new allocs/op delta +Connected-8 1.00k ± 0% 0.01k ± 0% -99.50% diff --git a/testdata/bench_results/EulerDirected b/testdata/bench_results/EulerDirected new file mode 100644 index 0000000..0ef476c --- /dev/null +++ b/testdata/bench_results/EulerDirected @@ -0,0 +1,8 @@ +name old time/op new time/op delta +EulerDirected-8 7.28µs ± 0% 2.00µs ± 0% -72.44% + +name old alloc/op new alloc/op delta +EulerDirected-8 5.71kB ± 0% 0.96kB ± 0% -83.19% + +name old allocs/op new allocs/op delta +EulerDirected-8 103 ± 0% 4 ± 0% -96.12% diff --git a/testdata/bench_results/EulerUndirected b/testdata/bench_results/EulerUndirected new file mode 100644 index 0000000..c750c1c --- /dev/null +++ b/testdata/bench_results/EulerUndirected @@ -0,0 +1,8 @@ +name old time/op new time/op delta +EulerUndirected-8 7.07µs ± 0% 2.39µs ± 0% -66.21% + +name old alloc/op new alloc/op delta +EulerUndirected-8 5.72kB ± 0% 0.97kB ± 0% -83.08% + +name old allocs/op new allocs/op delta +EulerUndirected-8 104 ± 0% 5 ± 0% -95.19% diff --git a/testdata/bench_results/MST b/testdata/bench_results/MST new file mode 100644 index 0000000..d3c706b --- /dev/null +++ b/testdata/bench_results/MST @@ -0,0 +1,8 @@ +name old time/op new time/op delta +MST-8 249µs ± 0% 190µs ± 0% -23.85% + +name old alloc/op new alloc/op delta +MST-8 96.9kB ± 0% 33.0kB ± 0% -65.99% + +name old allocs/op new allocs/op delta +MST-8 1.01k ± 0% 0.01k ± 0% -99.20% diff --git a/testdata/bench_results/ShortestPath b/testdata/bench_results/ShortestPath new file mode 100644 index 0000000..94d9b58 --- /dev/null +++ b/testdata/bench_results/ShortestPath @@ -0,0 +1,8 @@ +name old time/op new time/op delta +ShortestPath-8 141µs ± 0% 60µs ± 0% -57.23% + +name old alloc/op new alloc/op delta +ShortestPath-8 79.9kB ± 0% 39.8kB ± 0% -50.23% + +name old allocs/op new allocs/op delta +ShortestPath-8 856 ± 0% 21 ± 0% -97.55% diff --git a/testdata/bench_results/ShortestPaths b/testdata/bench_results/ShortestPaths new file mode 100644 index 0000000..9407000 --- /dev/null +++ b/testdata/bench_results/ShortestPaths @@ -0,0 +1,8 @@ +name old time/op new time/op delta +ShortestPaths-8 121µs ± 0% 80µs ± 0% -34.06% + +name old alloc/op new alloc/op delta +ShortestPaths-8 79.2kB ± 0% 39.7kB ± 0% -49.85% + +name old allocs/op new allocs/op delta +ShortestPaths-8 841 ± 0% 19 ± 0% -97.74% diff --git a/testdata/bench_results/Sort b/testdata/bench_results/Sort new file mode 100644 index 0000000..c71f8f9 --- /dev/null +++ b/testdata/bench_results/Sort @@ -0,0 +1,8 @@ +name old time/op new time/op delta +Sort-8 608µs ± 0% 500µs ± 0% -17.79% + +name old alloc/op new alloc/op delta +Sort-8 249kB ± 0% 201kB ± 0% -19.25% + +name old allocs/op new allocs/op delta +Sort-8 6.03k ± 0% 5.03k ± 0% -16.57% diff --git a/testdata/bench_results/TopSort b/testdata/bench_results/TopSort new file mode 100644 index 0000000..0f97727 --- /dev/null +++ b/testdata/bench_results/TopSort @@ -0,0 +1,8 @@ +name old time/op new time/op delta +TopSort-8 135µs ± 0% 69µs ± 0% -48.49% + +name old alloc/op new alloc/op delta +TopSort-8 56.1kB ± 0% 16.5kB ± 0% -70.62% + +name old allocs/op new allocs/op delta +TopSort-8 1.18k ± 0% 0.02k ± 0% -98.14% diff --git a/testdata/bench_results/Transpose b/testdata/bench_results/Transpose new file mode 100644 index 0000000..85583fc --- /dev/null +++ b/testdata/bench_results/Transpose @@ -0,0 +1,8 @@ +name old time/op new time/op delta +Transpose-8 404µs ± 0% 401µs ± 0% -0.72% + +name old alloc/op new alloc/op delta +Transpose-8 218kB ± 0% 170kB ± 0% -22.04% + +name old allocs/op new allocs/op delta +Transpose-8 5.00k ± 0% 4.00k ± 0% -19.98% diff --git a/testdata/benchdiff.sh b/testdata/benchdiff.sh new file mode 100755 index 0000000..4b9914f --- /dev/null +++ b/testdata/benchdiff.sh @@ -0,0 +1,2 @@ +#!/bin/sh +benchstat -delta-test none testdata/bench_before/${1} testdata/bench_after/${1} | tee testdata/bench_results/${1} diff --git a/top.go b/top.go index e03d97f..dc85611 100644 --- a/top.go +++ b/top.go @@ -19,11 +19,12 @@ func Acyclic(g Iterator) bool { // Kahn's algorithm func topsort(g Iterator, output bool) (order []int, acyclic bool) { indegree := make([]int, g.Order()) + addIndegree := func(w int, _ int64) (skip bool) { + indegree[w]++ + return + } for v := range indegree { - g.Visit(v, func(w int, _ int64) (skip bool) { - indegree[w]++ - return - }) + g.Visit(v, addIndegree) } // Invariant: this queue holds all vertices with indegree 0. @@ -34,6 +35,13 @@ func topsort(g Iterator, output bool) (order []int, acyclic bool) { } } + subIndegree := func(w int, _ int64) (skip bool) { + indegree[w]-- + if indegree[w] == 0 { + queue = append(queue, w) + } + return + } order = []int{} vertexCount := 0 for len(queue) > 0 { @@ -43,13 +51,7 @@ func topsort(g Iterator, output bool) (order []int, acyclic bool) { order = append(order, v) } vertexCount++ - g.Visit(v, func(w int, _ int64) (skip bool) { - indegree[w]-- - if indegree[w] == 0 { - queue = append(queue, w) - } - return - }) + g.Visit(v, subIndegree) } if vertexCount != g.Order() { return diff --git a/top_test.go b/top_test.go index 319c0ee..c042165 100644 --- a/top_test.go +++ b/top_test.go @@ -84,6 +84,7 @@ func TestAcyclic(t *testing.T) { func BenchmarkAcyclic(b *testing.B) { n := 1000 + b.ReportAllocs() b.StopTimer() g := New(n) for i := 0; i < 2*n; i++ { @@ -100,6 +101,7 @@ func BenchmarkAcyclic(b *testing.B) { func BenchmarkTopSort(b *testing.B) { n := 1000 + b.ReportAllocs() b.StopTimer() g := New(n) for i := 0; i < 2*n; i++ { diff --git a/weak.go b/weak.go index c07f033..43ffeb1 100644 --- a/weak.go +++ b/weak.go @@ -29,18 +29,21 @@ func Components(g Iterator) [][]int { func components(g Iterator) (sets disjointSets, count int) { n := g.Order() sets, count = makeSingletons(n), n - for v := 0; v < n && count > 1; v++ { - g.Visit(v, func(w int, _ int64) (skip bool) { - x, y := sets.find(v), sets.find(w) - if x != y { - sets.union(x, y) - count-- - if count == 1 { - skip = true - } + + var v int + do := func(w int, _ int64) (skip bool) { + x, y := sets.find(v), sets.find(w) + if x != y { + sets.union(x, y) + count-- + if count == 1 { + skip = true } - return - }) + } + return + } + for v = 0; v < n && count > 1; v++ { + g.Visit(v, do) } return } diff --git a/weak_test.go b/weak_test.go index 581a834..95fb78b 100644 --- a/weak_test.go +++ b/weak_test.go @@ -54,6 +54,7 @@ func TestComponents(t *testing.T) { func BenchmarkConnected(b *testing.B) { n := 1000 + b.ReportAllocs() b.StopTimer() g := New(n) for i := 0; i < n; i++ { @@ -67,6 +68,7 @@ func BenchmarkConnected(b *testing.B) { func BenchmarkComponents(b *testing.B) { n := 1000 + b.ReportAllocs() b.StopTimer() g := New(n) for i := 0; i < n; i++ {