]> Cypherpunks repositories - gostls13.git/commitdiff
[dev.ssa] cmd/compile/ssa: dominator tests and benchmarks
authorTodd Neal <todd@tneal.org>
Fri, 26 Jun 2015 04:13:57 +0000 (23:13 -0500)
committerKeith Randall <khr@golang.org>
Tue, 7 Jul 2015 19:42:35 +0000 (19:42 +0000)
This change has some tests verifying functionality and an assortment of
benchmarks of various block lists. It modifies NewBlock to allocate in
contiguous blocks improving the performance of intersect() for extremely
large graphs by 30-40%.

benchmark                           old ns/op      new ns/op     delta
BenchmarkDominatorsLinear-8         1185619        901154        -23.99%
BenchmarkDominatorsFwdBack-8        1302138        863537        -33.68%
BenchmarkDominatorsManyPred-8       404670521      247450911     -38.85%
BenchmarkDominatorsMaxPred-8        455809002      471675119     +3.48%
BenchmarkDominatorsMaxPredVal-8     819315864      468257300     -42.85%

BenchmarkNilCheckDeep1-8            766            706           -7.83%
BenchmarkNilCheckDeep10-8           2553           2209          -13.47%
BenchmarkNilCheckDeep100-8          58606          57545         -1.81%
BenchmarkNilCheckDeep1000-8         7753012        8025750       +3.52%
BenchmarkNilCheckDeep10000-8        1224165946     789995184     -35.47%

Change-Id: Id3d6bc9cb1138e8177934441073ac7873ddf7ade
Reviewed-on: https://go-review.googlesource.com/11716
Reviewed-by: Keith Randall <khr@golang.org>
src/cmd/compile/internal/ssa/dom.go
src/cmd/compile/internal/ssa/dom_test.go [new file with mode: 0644]
src/cmd/compile/internal/ssa/func.go

index 6f700ec7e90b70c063910fb451951bef34da0caf..b4d47c1350eee18d454a734a1ac7ed8c096f25bf 100644 (file)
@@ -55,7 +55,6 @@ func postorder(f *Func) []*Block {
 // which maps block ID to the immediate dominator of that block.
 // Unreachable blocks map to nil.  The entry block maps to nil.
 func dominators(f *Func) []*Block {
-       // TODO: Benchmarks. See BenchmarkNilCheckDeep* for an example.
 
        // A simple algorithm for now
        // Cooper, Harvey, Kennedy
diff --git a/src/cmd/compile/internal/ssa/dom_test.go b/src/cmd/compile/internal/ssa/dom_test.go
new file mode 100644 (file)
index 0000000..3197a5c
--- /dev/null
@@ -0,0 +1,321 @@
+// Copyright 2015 The Go 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 ssa
+
+import (
+       "testing"
+)
+
+func BenchmarkDominatorsLinear(b *testing.B)     { benchmarkDominators(b, 10000, genLinear) }
+func BenchmarkDominatorsFwdBack(b *testing.B)    { benchmarkDominators(b, 10000, genFwdBack) }
+func BenchmarkDominatorsManyPred(b *testing.B)   { benchmarkDominators(b, 10000, genManyPred) }
+func BenchmarkDominatorsMaxPred(b *testing.B)    { benchmarkDominators(b, 10000, genMaxPred) }
+func BenchmarkDominatorsMaxPredVal(b *testing.B) { benchmarkDominators(b, 10000, genMaxPredValue) }
+
+type blockGen func(size int) []bloc
+
+// genLinear creates an array of blocks that succeed one another
+// b_n -> [b_n+1].
+func genLinear(size int) []bloc {
+       var blocs []bloc
+       blocs = append(blocs,
+               Bloc("entry",
+                       Valu("mem", OpArg, TypeMem, 0, ".mem"),
+                       Goto(blockn(0)),
+               ),
+       )
+       for i := 0; i < size; i++ {
+               blocs = append(blocs, Bloc(blockn(i),
+                       Goto(blockn(i+1))))
+       }
+
+       blocs = append(blocs,
+               Bloc(blockn(size), Goto("exit")),
+               Bloc("exit", Exit("mem")),
+       )
+
+       return blocs
+}
+
+// genLinear creates an array of blocks that alternate between
+// b_n -> [b_n+1], b_n -> [b_n+1, b_n-1] , b_n -> [b_n+1, b_n+2]
+func genFwdBack(size int) []bloc {
+       var blocs []bloc
+       blocs = append(blocs,
+               Bloc("entry",
+                       Valu("mem", OpArg, TypeMem, 0, ".mem"),
+                       Valu("p", OpConst, TypeBool, 0, true),
+                       Goto(blockn(0)),
+               ),
+       )
+       for i := 0; i < size; i++ {
+               switch i % 2 {
+               case 0:
+                       blocs = append(blocs, Bloc(blockn(i),
+                               If("p", blockn(i+1), blockn(i+2))))
+               case 1:
+                       blocs = append(blocs, Bloc(blockn(i),
+                               If("p", blockn(i+1), blockn(i-1))))
+               }
+       }
+
+       blocs = append(blocs,
+               Bloc(blockn(size), Goto("exit")),
+               Bloc("exit", Exit("mem")),
+       )
+
+       return blocs
+}
+
+// genManyPred creates an array of blocks where 1/3rd have a sucessor of the
+// first block, 1/3rd the last block, and the remaining third are plain.
+func genManyPred(size int) []bloc {
+       var blocs []bloc
+       blocs = append(blocs,
+               Bloc("entry",
+                       Valu("mem", OpArg, TypeMem, 0, ".mem"),
+                       Valu("p", OpConst, TypeBool, 0, true),
+                       Goto(blockn(0)),
+               ),
+       )
+
+       // We want predecessor lists to be long, so 2/3rds of the blocks have a
+       // sucessor of the first or last block.
+       for i := 0; i < size; i++ {
+               switch i % 3 {
+               case 0:
+                       blocs = append(blocs, Bloc(blockn(i),
+                               Valu("a", OpConst, TypeBool, 0, true),
+                               Goto(blockn(i+1))))
+               case 1:
+                       blocs = append(blocs, Bloc(blockn(i),
+                               Valu("a", OpConst, TypeBool, 0, true),
+                               If("p", blockn(i+1), blockn(0))))
+               case 2:
+                       blocs = append(blocs, Bloc(blockn(i),
+                               Valu("a", OpConst, TypeBool, 0, true),
+                               If("p", blockn(i+1), blockn(size))))
+               }
+       }
+
+       blocs = append(blocs,
+               Bloc(blockn(size), Goto("exit")),
+               Bloc("exit", Exit("mem")),
+       )
+
+       return blocs
+}
+
+// genMaxPred maximizes the size of the 'exit' predecessor list.
+func genMaxPred(size int) []bloc {
+       var blocs []bloc
+       blocs = append(blocs,
+               Bloc("entry",
+                       Valu("mem", OpArg, TypeMem, 0, ".mem"),
+                       Valu("p", OpConst, TypeBool, 0, true),
+                       Goto(blockn(0)),
+               ),
+       )
+
+       for i := 0; i < size; i++ {
+               blocs = append(blocs, Bloc(blockn(i),
+                       If("p", blockn(i+1), "exit")))
+       }
+
+       blocs = append(blocs,
+               Bloc(blockn(size), Goto("exit")),
+               Bloc("exit", Exit("mem")),
+       )
+
+       return blocs
+}
+
+// genMaxPredValue is identical to genMaxPred but contains an
+// additional value.
+func genMaxPredValue(size int) []bloc {
+       var blocs []bloc
+       blocs = append(blocs,
+               Bloc("entry",
+                       Valu("mem", OpArg, TypeMem, 0, ".mem"),
+                       Valu("p", OpConst, TypeBool, 0, true),
+                       Goto(blockn(0)),
+               ),
+       )
+
+       for i := 0; i < size; i++ {
+               blocs = append(blocs, Bloc(blockn(i),
+                       Valu("a", OpConst, TypeBool, 0, true),
+                       If("p", blockn(i+1), "exit")))
+       }
+
+       blocs = append(blocs,
+               Bloc(blockn(size), Goto("exit")),
+               Bloc("exit", Exit("mem")),
+       )
+
+       return blocs
+}
+
+// sink for benchmark
+var domBenchRes []*Block
+
+func benchmarkDominators(b *testing.B, size int, bg blockGen) {
+       c := NewConfig("amd64", DummyFrontend{b})
+       fun := Fun(c, "entry", bg(size)...)
+
+       CheckFunc(fun.f)
+       b.SetBytes(int64(size))
+       b.ResetTimer()
+       for i := 0; i < b.N; i++ {
+               domBenchRes = dominators(fun.f)
+       }
+}
+
+func verifyDominators(t *testing.T, f fun, doms map[string]string) {
+       blockNames := map[*Block]string{}
+       for n, b := range f.blocks {
+               blockNames[b] = n
+       }
+
+       calcDom := dominators(f.f)
+
+       for n, d := range doms {
+               nblk, ok := f.blocks[n]
+               if !ok {
+                       t.Errorf("invalid block name %s", n)
+               }
+               dblk, ok := f.blocks[d]
+               if !ok {
+                       t.Errorf("invalid block name %s", d)
+               }
+
+               domNode := calcDom[nblk.ID]
+               switch {
+               case calcDom[nblk.ID] == dblk:
+                       calcDom[nblk.ID] = nil
+                       continue
+               case calcDom[nblk.ID] != dblk:
+                       t.Errorf("expected %s as dominator of %s, found %s", d, n, blockNames[domNode])
+               default:
+                       t.Fatal("unexpected dominator condition")
+               }
+       }
+
+       for id, d := range calcDom {
+               // If nil, we've already verified it
+               if d == nil {
+                       continue
+               }
+               for _, b := range f.blocks {
+                       if int(b.ID) == id {
+                               t.Errorf("unexpected dominator of %s for %s", blockNames[d], blockNames[b])
+                       }
+               }
+       }
+
+}
+
+func TestDominatorsSimple(t *testing.T) {
+       c := NewConfig("amd64", DummyFrontend{t})
+       fun := Fun(c, "entry",
+               Bloc("entry",
+                       Valu("mem", OpArg, TypeMem, 0, ".mem"),
+                       Goto("a")),
+               Bloc("a",
+                       Goto("b")),
+               Bloc("b",
+                       Goto("c")),
+               Bloc("c",
+                       Goto("exit")),
+               Bloc("exit",
+                       Exit("mem")))
+
+       doms := map[string]string{
+               "a":    "entry",
+               "b":    "a",
+               "c":    "b",
+               "exit": "c",
+       }
+
+       verifyDominators(t, fun, doms)
+
+}
+
+func TestDominatorsMultPredFwd(t *testing.T) {
+       c := NewConfig("amd64", DummyFrontend{t})
+       fun := Fun(c, "entry",
+               Bloc("entry",
+                       Valu("mem", OpArg, TypeMem, 0, ".mem"),
+                       Valu("p", OpConst, TypeBool, 0, true),
+                       If("p", "a", "c")),
+               Bloc("a",
+                       If("p", "b", "c")),
+               Bloc("b",
+                       Goto("c")),
+               Bloc("c",
+                       Goto("exit")),
+               Bloc("exit",
+                       Exit("mem")))
+
+       doms := map[string]string{
+               "a":    "entry",
+               "b":    "a",
+               "c":    "entry",
+               "exit": "c",
+       }
+
+       verifyDominators(t, fun, doms)
+
+}
+
+func TestDominatorsMultPredRev(t *testing.T) {
+       c := NewConfig("amd64", DummyFrontend{t})
+       fun := Fun(c, "entry",
+               Bloc("entry",
+                       Valu("mem", OpArg, TypeMem, 0, ".mem"),
+                       Valu("p", OpConst, TypeBool, 0, true),
+                       Goto("a")),
+               Bloc("a",
+                       If("p", "b", "entry")),
+               Bloc("b",
+                       Goto("c")),
+               Bloc("c",
+                       If("p", "exit", "b")),
+               Bloc("exit",
+                       Exit("mem")))
+
+       doms := map[string]string{
+               "a":    "entry",
+               "b":    "a",
+               "c":    "b",
+               "exit": "c",
+       }
+       verifyDominators(t, fun, doms)
+}
+
+func TestDominatorsMultPred(t *testing.T) {
+       c := NewConfig("amd64", DummyFrontend{t})
+       fun := Fun(c, "entry",
+               Bloc("entry",
+                       Valu("mem", OpArg, TypeMem, 0, ".mem"),
+                       Valu("p", OpConst, TypeBool, 0, true),
+                       If("p", "a", "c")),
+               Bloc("a",
+                       If("p", "b", "c")),
+               Bloc("b",
+                       Goto("c")),
+               Bloc("c",
+                       If("p", "b", "exit")),
+               Bloc("exit",
+                       Exit("mem")))
+
+       doms := map[string]string{
+               "a":    "entry",
+               "b":    "entry",
+               "c":    "entry",
+               "exit": "c",
+       }
+       verifyDominators(t, fun, doms)
+}
index f7468610501f735330cf5a3394154ab124e7b601..bd2b74c1511eb685dd2a6ec0178cad47329285c9 100644 (file)
@@ -4,6 +4,8 @@
 
 package ssa
 
+import "sync"
+
 // A Func represents a Go func declaration (or function literal) and
 // its body.  This package compiles each Func independently.
 type Func struct {
@@ -31,13 +33,38 @@ func (f *Func) NumValues() int {
        return f.vid.num()
 }
 
+const (
+       blockSize = 100
+)
+
+// blockPool provides a contiguous array of Blocks which
+// improves the speed of traversing dominator trees.
+type blockPool struct {
+       blocks []Block
+       mu     sync.Mutex
+}
+
+func (bp *blockPool) newBlock() *Block {
+       bp.mu.Lock()
+       defer bp.mu.Unlock()
+
+       if len(bp.blocks) <= 0 {
+               bp.blocks = make([]Block, blockSize, blockSize)
+       }
+
+       res := &bp.blocks[0]
+       bp.blocks = bp.blocks[1:]
+       return res
+}
+
+var bp blockPool
+
 // NewBlock returns a new block of the given kind and appends it to f.Blocks.
 func (f *Func) NewBlock(kind BlockKind) *Block {
-       b := &Block{
-               ID:   f.bid.get(),
-               Kind: kind,
-               Func: f,
-       }
+       b := bp.newBlock()
+       b.ID = f.bid.get()
+       b.Kind = kind
+       b.Func = f
        f.Blocks = append(f.Blocks, b)
        return b
 }