cmd/compile: speed up compiling with -S

Compiling with -S was not implemented with performance in mind.
It allocates profligately. Compiling with -S is ~58% slower,
allocates ~47% more memory, and does ~183% more allocations.

compilecmp now uses -S to do finer-grained comparisons between
compiler versions, so I now care about its performance.

This change picks some of the lowest hanging fruit,
mostly by modifying printing routines to print directly to a writer,
rather than constructing a string first.

I have confirmed that compiling std+cmd with "-gcflags=all=-S -p=1"
and CGO_ENABLED=0 yields identical results before/after this change.
(-p=1 makes package compilation order deterministic. CGO_ENABLED=0
prevents cgo temp workdirs from showing up in filenames.)

Using the -S flag, the compiler performance impact is:

name        old time/op       new time/op       delta
Template          344ms ± 2%        301ms ± 2%  -12.45%  (p=0.000 n=22+24)
Unicode           136ms ± 3%        121ms ± 3%  -11.40%  (p=0.000 n=24+25)
GoTypes           1.24s ± 5%        1.09s ± 3%  -12.58%  (p=0.000 n=25+25)
Compiler          5.66s ± 4%        5.06s ± 2%  -10.56%  (p=0.000 n=25+20)
SSA               19.9s ± 3%        17.2s ± 4%  -13.64%  (p=0.000 n=25+25)
Flate             212ms ± 2%        188ms ± 2%  -11.33%  (p=0.000 n=25+24)
GoParser          278ms ± 3%        242ms ± 1%  -12.84%  (p=0.000 n=23+24)
Reflect           743ms ± 3%        657ms ± 5%  -11.56%  (p=0.000 n=24+25)
Tar               295ms ± 2%        263ms ± 2%  -10.78%  (p=0.000 n=25+25)
XML               409ms ± 2%        360ms ± 3%  -12.03%  (p=0.000 n=24+25)
[Geo mean]        714ms             629ms       -11.92%

name        old user-time/op  new user-time/op  delta
Template          430ms ± 5%        388ms ± 3%   -9.76%  (p=0.000 n=21+24)
Unicode           202ms ±12%        171ms ± 5%  -15.21%  (p=0.000 n=25+23)
GoTypes           1.58s ± 3%        1.42s ± 3%   -9.58%  (p=0.000 n=24+24)
Compiler          7.42s ± 3%        6.68s ± 8%   -9.93%  (p=0.000 n=25+25)
SSA               26.9s ± 3%        22.9s ± 3%  -14.85%  (p=0.000 n=25+25)
Flate             260ms ± 6%        234ms ± 3%   -9.69%  (p=0.000 n=23+25)
GoParser          354ms ± 1%        296ms ± 3%  -16.46%  (p=0.000 n=23+25)
Reflect           953ms ± 2%        865ms ± 4%   -9.14%  (p=0.000 n=24+24)
Tar               380ms ± 2%        348ms ± 2%   -8.28%  (p=0.000 n=25+22)
XML               530ms ± 3%        451ms ± 3%  -15.01%  (p=0.000 n=24+23)
[Geo mean]        929ms             819ms       -11.84%

name        old alloc/op      new alloc/op      delta
Template         54.1MB ± 0%       44.3MB ± 0%  -18.24%  (p=0.000 n=24+24)
Unicode          33.5MB ± 0%       30.6MB ± 0%   -8.57%  (p=0.000 n=25+25)
GoTypes           189MB ± 0%        152MB ± 0%  -19.55%  (p=0.000 n=25+23)
Compiler          875MB ± 0%        703MB ± 0%  -19.70%  (p=0.000 n=25+25)
SSA              3.19GB ± 0%       2.51GB ± 0%  -21.50%  (p=0.000 n=25+25)
Flate            32.9MB ± 0%       27.3MB ± 0%  -17.04%  (p=0.000 n=25+25)
GoParser         43.9MB ± 0%       35.1MB ± 0%  -20.19%  (p=0.000 n=25+25)
Reflect           117MB ± 0%         96MB ± 0%  -18.22%  (p=0.000 n=24+23)
Tar              48.6MB ± 0%       40.6MB ± 0%  -16.39%  (p=0.000 n=25+24)
XML              65.7MB ± 0%       53.9MB ± 0%  -17.93%  (p=0.000 n=25+23)
[Geo mean]        118MB              97MB       -17.80%

name        old allocs/op     new allocs/op     delta
Template          1.07M ± 0%        0.60M ± 0%  -43.90%  (p=0.000 n=25+24)
Unicode            539k ± 0%         398k ± 0%  -26.20%  (p=0.000 n=23+25)
GoTypes           3.97M ± 0%        2.19M ± 0%  -44.90%  (p=0.000 n=25+24)
Compiler          17.6M ± 0%         9.5M ± 0%  -46.39%  (p=0.000 n=22+23)
SSA               66.1M ± 0%        34.1M ± 0%  -48.41%  (p=0.000 n=25+22)
Flate              629k ± 0%         365k ± 0%  -41.95%  (p=0.000 n=25+25)
GoParser           929k ± 0%         500k ± 0%  -46.11%  (p=0.000 n=25+25)
Reflect           2.49M ± 0%        1.47M ± 0%  -41.00%  (p=0.000 n=24+25)
Tar                919k ± 0%         534k ± 0%  -41.94%  (p=0.000 n=25+24)
XML               1.28M ± 0%        0.71M ± 0%  -44.72%  (p=0.000 n=25+24)
[Geo mean]        2.32M             1.33M       -42.82%

This change also speeds up cmd/objdump a modest amount, ~4%.

Change-Id: I7c7aa2b365688bc44b3ef6e1d03bcf934699cabc
Reviewed-on: https://go-review.googlesource.com/c/go/+/216857
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
Josh Bleecher Snyder 2020-01-26 09:59:25 -08:00
parent 04040ec9f9
commit 1dcf34f2ec
4 changed files with 134 additions and 94 deletions

View file

@ -8,6 +8,7 @@ import (
"bytes"
"cmd/internal/objabi"
"fmt"
"io"
"strings"
)
@ -17,8 +18,8 @@ const REG_NONE = 0
func (p *Prog) Line() string {
return p.Ctxt.OutermostPos(p.Pos).Format(false, true)
}
func (p *Prog) InnermostLine() string {
return p.Ctxt.InnermostPos(p.Pos).Format(false, true)
func (p *Prog) InnermostLine(w io.Writer) {
p.Ctxt.InnermostPos(p.Pos).WriteTo(w, false, true)
}
// InnermostLineNumber returns a string containing the line number for the
@ -121,45 +122,61 @@ func (p *Prog) String() string {
return fmt.Sprintf("%.5d (%v)\t%s", p.Pc, p.Line(), p.InstructionString())
}
func (p *Prog) InnermostString() string {
func (p *Prog) InnermostString(w io.Writer) {
if p == nil {
return "<nil Prog>"
io.WriteString(w, "<nil Prog>")
return
}
if p.Ctxt == nil {
return "<Prog without ctxt>"
io.WriteString(w, "<Prog without ctxt>")
return
}
return fmt.Sprintf("%.5d (%v)\t%s", p.Pc, p.InnermostLine(), p.InstructionString())
fmt.Fprintf(w, "%.5d (", p.Pc)
p.InnermostLine(w)
io.WriteString(w, ")\t")
p.WriteInstructionString(w)
}
// InstructionString returns a string representation of the instruction without preceding
// program counter or file and line number.
func (p *Prog) InstructionString() string {
buf := new(bytes.Buffer)
p.WriteInstructionString(buf)
return buf.String()
}
// WriteInstructionString writes a string representation of the instruction without preceding
// program counter or file and line number.
func (p *Prog) WriteInstructionString(w io.Writer) {
if p == nil {
return "<nil Prog>"
io.WriteString(w, "<nil Prog>")
return
}
if p.Ctxt == nil {
return "<Prog without ctxt>"
io.WriteString(w, "<Prog without ctxt>")
return
}
sc := CConv(p.Scond)
var buf bytes.Buffer
fmt.Fprintf(&buf, "%v%s", p.As, sc)
io.WriteString(w, p.As.String())
io.WriteString(w, sc)
sep := "\t"
if p.From.Type != TYPE_NONE {
fmt.Fprintf(&buf, "%s%v", sep, Dconv(p, &p.From))
io.WriteString(w, sep)
WriteDconv(w, p, &p.From)
sep = ", "
}
if p.Reg != REG_NONE {
// Should not happen but might as well show it if it does.
fmt.Fprintf(&buf, "%s%v", sep, Rconv(int(p.Reg)))
fmt.Fprintf(w, "%s%v", sep, Rconv(int(p.Reg)))
sep = ", "
}
for i := range p.RestArgs {
fmt.Fprintf(&buf, "%s%v", sep, Dconv(p, &p.RestArgs[i]))
io.WriteString(w, sep)
WriteDconv(w, p, &p.RestArgs[i])
sep = ", "
}
@ -170,17 +187,17 @@ func (p *Prog) InstructionString() string {
// TEXT foo(SB), $0
s := p.From.Sym.Attribute.TextAttrString()
if s != "" {
fmt.Fprintf(&buf, "%s%s", sep, s)
fmt.Fprintf(w, "%s%s", sep, s)
sep = ", "
}
}
if p.To.Type != TYPE_NONE {
fmt.Fprintf(&buf, "%s%v", sep, Dconv(p, &p.To))
io.WriteString(w, sep)
WriteDconv(w, p, &p.To)
}
if p.RegTo2 != REG_NONE {
fmt.Fprintf(&buf, "%s%v", sep, Rconv(int(p.RegTo2)))
fmt.Fprintf(w, "%s%v", sep, Rconv(int(p.RegTo2)))
}
return buf.String()
}
func (ctxt *Link) NewProg() *Prog {
@ -194,16 +211,20 @@ func (ctxt *Link) CanReuseProgs() bool {
}
func Dconv(p *Prog, a *Addr) string {
var str string
buf := new(bytes.Buffer)
WriteDconv(buf, p, a)
return buf.String()
}
func WriteDconv(w io.Writer, p *Prog, a *Addr) {
switch a.Type {
default:
str = fmt.Sprintf("type=%d", a.Type)
fmt.Fprintf(w, "type=%d", a.Type)
case TYPE_NONE:
str = ""
if a.Name != NAME_NONE || a.Reg != 0 || a.Sym != nil {
str = fmt.Sprintf("%v(%v)(NONE)", Mconv(a), Rconv(int(a.Reg)))
a.WriteNameTo(w)
fmt.Fprintf(w, "(%v)(NONE)", Rconv(int(a.Reg)))
}
case TYPE_REG:
@ -212,71 +233,75 @@ func Dconv(p *Prog, a *Addr) string {
// where the $1 is included in the p->to Addr.
// Move into a new field.
if a.Offset != 0 && (a.Reg < RBaseARM64 || a.Reg >= RBaseMIPS) {
str = fmt.Sprintf("$%d,%v", a.Offset, Rconv(int(a.Reg)))
break
fmt.Fprintf(w, "$%d,%v", a.Offset, Rconv(int(a.Reg)))
return
}
str = Rconv(int(a.Reg))
if a.Name != NAME_NONE || a.Sym != nil {
str = fmt.Sprintf("%v(%v)(REG)", Mconv(a), Rconv(int(a.Reg)))
a.WriteNameTo(w)
fmt.Fprintf(w, "(%v)(REG)", Rconv(int(a.Reg)))
} else {
io.WriteString(w, Rconv(int(a.Reg)))
}
if (RBaseARM64+1<<10+1<<9) /* arm64.REG_ELEM */ <= a.Reg &&
a.Reg < (RBaseARM64+1<<11) /* arm64.REG_ELEM_END */ {
str += fmt.Sprintf("[%d]", a.Index)
fmt.Fprintf(w, "[%d]", a.Index)
}
case TYPE_BRANCH:
if a.Sym != nil {
str = fmt.Sprintf("%s(SB)", a.Sym.Name)
fmt.Fprintf(w, "%s(SB)", a.Sym.Name)
} else if p != nil && p.Pcond != nil {
str = fmt.Sprint(p.Pcond.Pc)
fmt.Fprint(w, p.Pcond.Pc)
} else if a.Val != nil {
str = fmt.Sprint(a.Val.(*Prog).Pc)
fmt.Fprint(w, a.Val.(*Prog).Pc)
} else {
str = fmt.Sprintf("%d(PC)", a.Offset)
fmt.Fprintf(w, "%d(PC)", a.Offset)
}
case TYPE_INDIR:
str = fmt.Sprintf("*%s", Mconv(a))
io.WriteString(w, "*")
a.WriteNameTo(w)
case TYPE_MEM:
str = Mconv(a)
a.WriteNameTo(w)
if a.Index != REG_NONE {
if a.Scale == 0 {
// arm64 shifted or extended register offset, scale = 0.
str += fmt.Sprintf("(%v)", Rconv(int(a.Index)))
fmt.Fprintf(w, "(%v)", Rconv(int(a.Index)))
} else {
str += fmt.Sprintf("(%v*%d)", Rconv(int(a.Index)), int(a.Scale))
fmt.Fprintf(w, "(%v*%d)", Rconv(int(a.Index)), int(a.Scale))
}
}
case TYPE_CONST:
io.WriteString(w, "$")
a.WriteNameTo(w)
if a.Reg != 0 {
str = fmt.Sprintf("$%v(%v)", Mconv(a), Rconv(int(a.Reg)))
} else {
str = fmt.Sprintf("$%v", Mconv(a))
fmt.Fprintf(w, "(%v)", Rconv(int(a.Reg)))
}
case TYPE_TEXTSIZE:
if a.Val.(int32) == objabi.ArgsSizeUnknown {
str = fmt.Sprintf("$%d", a.Offset)
fmt.Fprintf(w, "$%d", a.Offset)
} else {
str = fmt.Sprintf("$%d-%d", a.Offset, a.Val.(int32))
fmt.Fprintf(w, "$%d-%d", a.Offset, a.Val.(int32))
}
case TYPE_FCONST:
str = fmt.Sprintf("%.17g", a.Val.(float64))
str := fmt.Sprintf("%.17g", a.Val.(float64))
// Make sure 1 prints as 1.0
if !strings.ContainsAny(str, ".e") {
str += ".0"
}
str = fmt.Sprintf("$(%s)", str)
fmt.Fprintf(w, "$(%s)", str)
case TYPE_SCONST:
str = fmt.Sprintf("$%q", a.Val.(string))
fmt.Fprintf(w, "$%q", a.Val.(string))
case TYPE_ADDR:
str = fmt.Sprintf("$%s", Mconv(a))
io.WriteString(w, "$")
a.WriteNameTo(w)
case TYPE_SHIFT:
v := int(a.Offset)
@ -285,49 +310,45 @@ func Dconv(p *Prog, a *Addr) string {
case "arm":
op := ops[((v>>5)&3)<<1:]
if v&(1<<4) != 0 {
str = fmt.Sprintf("R%d%c%cR%d", v&15, op[0], op[1], (v>>8)&15)
fmt.Fprintf(w, "R%d%c%cR%d", v&15, op[0], op[1], (v>>8)&15)
} else {
str = fmt.Sprintf("R%d%c%c%d", v&15, op[0], op[1], (v>>7)&31)
fmt.Fprintf(w, "R%d%c%c%d", v&15, op[0], op[1], (v>>7)&31)
}
if a.Reg != 0 {
str += fmt.Sprintf("(%v)", Rconv(int(a.Reg)))
fmt.Fprintf(w, "(%v)", Rconv(int(a.Reg)))
}
case "arm64":
op := ops[((v>>22)&3)<<1:]
r := (v >> 16) & 31
str = fmt.Sprintf("%s%c%c%d", Rconv(r+RBaseARM64), op[0], op[1], (v>>10)&63)
fmt.Fprintf(w, "%s%c%c%d", Rconv(r+RBaseARM64), op[0], op[1], (v>>10)&63)
default:
panic("TYPE_SHIFT is not supported on " + objabi.GOARCH)
}
case TYPE_REGREG:
str = fmt.Sprintf("(%v, %v)", Rconv(int(a.Reg)), Rconv(int(a.Offset)))
fmt.Fprintf(w, "(%v, %v)", Rconv(int(a.Reg)), Rconv(int(a.Offset)))
case TYPE_REGREG2:
str = fmt.Sprintf("%v, %v", Rconv(int(a.Offset)), Rconv(int(a.Reg)))
fmt.Fprintf(w, "%v, %v", Rconv(int(a.Offset)), Rconv(int(a.Reg)))
case TYPE_REGLIST:
str = RLconv(a.Offset)
io.WriteString(w, RLconv(a.Offset))
}
return str
}
func Mconv(a *Addr) string {
var str string
func (a *Addr) WriteNameTo(w io.Writer) {
switch a.Name {
default:
str = fmt.Sprintf("name=%d", a.Name)
fmt.Fprintf(w, "name=%d", a.Name)
case NAME_NONE:
switch {
case a.Reg == REG_NONE:
str = fmt.Sprint(a.Offset)
fmt.Fprint(w, a.Offset)
case a.Offset == 0:
str = fmt.Sprintf("(%v)", Rconv(int(a.Reg)))
fmt.Fprintf(w, "(%v)", Rconv(int(a.Reg)))
case a.Offset != 0:
str = fmt.Sprintf("%d(%v)", a.Offset, Rconv(int(a.Reg)))
fmt.Fprintf(w, "%d(%v)", a.Offset, Rconv(int(a.Reg)))
}
// Note: a.Reg == REG_NONE encodes the default base register for the NAME_ type.
@ -337,9 +358,9 @@ func Mconv(a *Addr) string {
reg = Rconv(int(a.Reg))
}
if a.Sym != nil {
str = fmt.Sprintf("%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
fmt.Fprintf(w, "%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
} else {
str = fmt.Sprintf("%s(%s)", offConv(a.Offset), reg)
fmt.Fprintf(w, "%s(%s)", offConv(a.Offset), reg)
}
case NAME_GOTREF:
@ -348,9 +369,9 @@ func Mconv(a *Addr) string {
reg = Rconv(int(a.Reg))
}
if a.Sym != nil {
str = fmt.Sprintf("%s%s@GOT(%s)", a.Sym.Name, offConv(a.Offset), reg)
fmt.Fprintf(w, "%s%s@GOT(%s)", a.Sym.Name, offConv(a.Offset), reg)
} else {
str = fmt.Sprintf("%s@GOT(%s)", offConv(a.Offset), reg)
fmt.Fprintf(w, "%s@GOT(%s)", offConv(a.Offset), reg)
}
case NAME_STATIC:
@ -359,9 +380,9 @@ func Mconv(a *Addr) string {
reg = Rconv(int(a.Reg))
}
if a.Sym != nil {
str = fmt.Sprintf("%s<>%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
fmt.Fprintf(w, "%s<>%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
} else {
str = fmt.Sprintf("<>%s(%s)", offConv(a.Offset), reg)
fmt.Fprintf(w, "<>%s(%s)", offConv(a.Offset), reg)
}
case NAME_AUTO:
@ -370,9 +391,9 @@ func Mconv(a *Addr) string {
reg = Rconv(int(a.Reg))
}
if a.Sym != nil {
str = fmt.Sprintf("%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
fmt.Fprintf(w, "%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
} else {
str = fmt.Sprintf("%s(%s)", offConv(a.Offset), reg)
fmt.Fprintf(w, "%s(%s)", offConv(a.Offset), reg)
}
case NAME_PARAM:
@ -381,9 +402,9 @@ func Mconv(a *Addr) string {
reg = Rconv(int(a.Reg))
}
if a.Sym != nil {
str = fmt.Sprintf("%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
fmt.Fprintf(w, "%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
} else {
str = fmt.Sprintf("%s(%s)", offConv(a.Offset), reg)
fmt.Fprintf(w, "%s(%s)", offConv(a.Offset), reg)
}
case NAME_TOCREF:
reg := "SB"
@ -391,13 +412,11 @@ func Mconv(a *Addr) string {
reg = Rconv(int(a.Reg))
}
if a.Sym != nil {
str = fmt.Sprintf("%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
fmt.Fprintf(w, "%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
} else {
str = fmt.Sprintf("%s(%s)", offConv(a.Offset), reg)
fmt.Fprintf(w, "%s(%s)", offConv(a.Offset), reg)
}
}
return str
}
func offConv(off int64) string {