Commit d610d304 authored by Keith Randall's avatar Keith Randall

cmd/compile: reorg copyelim to avoid O(n^2) problem

Make sure we don't do O(n^2) work to eliminate a chain
of n copies.

benchmark                     old ns/op       new ns/op     delta
BenchmarkCopyElim1-8          1418            1406          -0.85%
BenchmarkCopyElim10-8         5289            5162          -2.40%
BenchmarkCopyElim100-8        52618           41684         -20.78%
BenchmarkCopyElim1000-8       2473878         424339        -82.85%
BenchmarkCopyElim10000-8      269373954       6367971       -97.64%
BenchmarkCopyElim100000-8     31272781165     104357244     -99.67%

Change-Id: I680f906f70f2ee1a8615cb1046bc510c77d59284
Reviewed-on: https://go-review.googlesource.com/22535Reviewed-by: 's avatarAlexandru Moșoi <alexandru@mosoi.ro>
parent 5ec87ba5
...@@ -4,18 +4,21 @@ ...@@ -4,18 +4,21 @@
package ssa package ssa
// copyelim removes all copies from f. // copyelim removes all uses of OpCopy values from f.
// A subsequent deadcode pass is needed to actually remove the copies.
func copyelim(f *Func) { func copyelim(f *Func) {
// Modify all values so no arg (including args
// of OpCopy) is a copy.
for _, b := range f.Blocks { for _, b := range f.Blocks {
for _, v := range b.Values { for _, v := range b.Values {
copyelimValue(v) copyelimValue(v)
} }
v := b.Control }
if v != nil && v.Op == OpCopy {
for v.Op == OpCopy { // Update block control values.
v = v.Args[0] for _, b := range f.Blocks {
} if v := b.Control; v != nil && v.Op == OpCopy {
b.SetControl(v) b.SetControl(v.Args[0])
} }
} }
...@@ -23,41 +26,57 @@ func copyelim(f *Func) { ...@@ -23,41 +26,57 @@ func copyelim(f *Func) {
for _, name := range f.Names { for _, name := range f.Names {
values := f.NamedValues[name] values := f.NamedValues[name]
for i, v := range values { for i, v := range values {
x := v if v.Op == OpCopy {
for x.Op == OpCopy { values[i] = v.Args[0]
x = x.Args[0]
}
if x != v {
values[i] = x
} }
} }
} }
} }
func copyelimValue(v *Value) bool { // copySource returns the (non-copy) op which is the
// elide any copies generated during rewriting // ultimate source of v. v must be a copy op.
changed := false func copySource(v *Value) *Value {
for i, a := range v.Args { w := v.Args[0]
if a.Op != OpCopy {
continue // This loop is just:
// for w.Op == OpCopy {
// w = w.Args[0]
// }
// but we take some extra care to make sure we
// don't get stuck in an infinite loop.
// Infinite copy loops may happen in unreachable code.
// (TODO: or can they? Needs a test.)
slow := w
var advance bool
for w.Op == OpCopy {
w = w.Args[0]
if w == slow {
w.reset(OpUnknown)
break
} }
// Rewriting can generate OpCopy loops. if advance {
// They are harmless (see removePredecessor), slow = slow.Args[0]
// but take care to stop if we find a cycle. }
slow := a // advances every other iteration advance = !advance
var advance bool }
for a.Op == OpCopy {
a = a.Args[0] // The answer is w. Update all the copies we saw
if slow == a { // to point directly to w. Doing this update makes
break // sure that we don't end up doing O(n^2) work
} // for a chain of n copies.
if advance { for v != w {
slow = slow.Args[0] x := v.Args[0]
} v.SetArg(0, w)
advance = !advance v = x
}
return w
}
// copyelimValue ensures that no args of v are copies.
func copyelimValue(v *Value) {
for i, a := range v.Args {
if a.Op == OpCopy {
v.SetArg(i, copySource(a))
} }
v.SetArg(i, a)
changed = true
} }
return changed
} }
// Copyright 2016 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 (
"fmt"
"testing"
)
func BenchmarkCopyElim1(b *testing.B) { benchmarkCopyElim(b, 1) }
func BenchmarkCopyElim10(b *testing.B) { benchmarkCopyElim(b, 10) }
func BenchmarkCopyElim100(b *testing.B) { benchmarkCopyElim(b, 100) }
func BenchmarkCopyElim1000(b *testing.B) { benchmarkCopyElim(b, 1000) }
func BenchmarkCopyElim10000(b *testing.B) { benchmarkCopyElim(b, 10000) }
func BenchmarkCopyElim100000(b *testing.B) { benchmarkCopyElim(b, 100000) }
func benchmarkCopyElim(b *testing.B, n int) {
c := testConfig(b)
values := make([]interface{}, 0, n+2)
values = append(values, Valu("mem", OpInitMem, TypeMem, 0, nil))
last := "mem"
for i := 0; i < n; i++ {
name := fmt.Sprintf("copy%d", i)
values = append(values, Valu(name, OpCopy, TypeMem, 0, nil, last))
last = name
}
values = append(values, Exit(last))
// Reverse values array to make it hard
for i := 0; i < len(values)/2; i++ {
values[i], values[len(values)-1-i] = values[len(values)-1-i], values[i]
}
for i := 0; i < b.N; i++ {
fun := Fun(c, "entry", Bloc("entry", values...))
Copyelim(fun.f)
fun.f.Free()
}
}
...@@ -13,8 +13,9 @@ var CheckFunc = checkFunc ...@@ -13,8 +13,9 @@ var CheckFunc = checkFunc
var PrintFunc = printFunc var PrintFunc = printFunc
var Opt = opt var Opt = opt
var Deadcode = deadcode var Deadcode = deadcode
var Copyelim = copyelim
func testConfig(t *testing.T) *Config { func testConfig(t testing.TB) *Config {
testCtxt := &obj.Link{} testCtxt := &obj.Link{}
return NewConfig("amd64", DummyFrontend{t}, testCtxt, true) return NewConfig("amd64", DummyFrontend{t}, testCtxt, true)
} }
......
...@@ -53,23 +53,7 @@ func applyRewrite(f *Func, rb func(*Block) bool, rv func(*Value, *Config) bool) ...@@ -53,23 +53,7 @@ func applyRewrite(f *Func, rb func(*Block) bool, rv func(*Value, *Config) bool)
if a.Op != OpCopy { if a.Op != OpCopy {
continue continue
} }
x := a.Args[0] v.SetArg(i, copySource(a))
// Rewriting can generate OpCopy loops.
// They are harmless (see removePredecessor),
// but take care to stop if we find a cycle.
slow := x // advances every other iteration
var advance bool
for x.Op == OpCopy {
x = x.Args[0]
if slow == x {
break
}
if advance {
slow = slow.Args[0]
}
advance = !advance
}
v.SetArg(i, x)
change = true change = true
for a.Uses == 0 { for a.Uses == 0 {
b := a.Args[0] b := a.Args[0]
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment