mirror of
https://github.com/golang/go
synced 2024-11-23 21:40:05 -07:00
[dev.ssa] cmd/compile/ssa: dominator tests and benchmarks
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>
This commit is contained in:
parent
7d10a2c04a
commit
41dafe6ecc
@ -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
|
||||
|
321
src/cmd/compile/internal/ssa/dom_test.go
Normal file
321
src/cmd/compile/internal/ssa/dom_test.go
Normal file
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user