]> Cypherpunks repositories - gostls13.git/commitdiff
[dev.link] cmd/link/internal/loader: do more bulk allocation
authorThan McIntosh <thanm@google.com>
Thu, 24 Oct 2019 15:27:47 +0000 (11:27 -0400)
committerThan McIntosh <thanm@google.com>
Fri, 1 Nov 2019 18:49:50 +0000 (18:49 +0000)
Change the loader to do more bulk allocation when making slices of
small objects (sym.Reloc, etc) as part of creating and populating
sym.Symbols in loader.LoadFull(). This replaces a large number of
small allocations with a smaller number of large allocations,
improving performace. Compilebench numbers (linker portion) for this
change:

name                      old time/op       new time/op       delta
LinkCompiler                    1.71s ±11%        1.57s ± 9%   -8.35%  (p=0.000 n=19+20)
LinkWithoutDebugCompiler        1.19s ±14%        1.10s ±13%   -7.93%  (p=0.000 n=20+19)

name                      old user-time/op  new user-time/op  delta
LinkCompiler                    1.86s ±15%        1.34s ±10%  -28.02%  (p=0.000 n=20+20)
LinkWithoutDebugCompiler        1.05s ±14%        0.95s ± 9%   -9.17%  (p=0.000 n=19+20)

Hyperkube from kubernetes doesn't show any significant benefit (which
seems a little surprising).

Change-Id: Ide97f78532fb60b08bb6e4cfa097e9058f7ea8ab
Reviewed-on: https://go-review.googlesource.com/c/go/+/203457
Run-TryBot: Than McIntosh <thanm@google.com>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Jeremy Faller <jeremy@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>

src/cmd/link/internal/loader/loader.go

index 38b2c810e3862f947fab114a5ab108adf147adb7..573c25105868b056bd1cee9e751c582be8f07a6f 100644 (file)
@@ -117,6 +117,8 @@ type Loader struct {
        // field tracking is enabled. Reachparent[K] contains the index of
        // the symbol that triggered the marking of symbol K as live.
        Reachparent []Sym
+
+       relocBatch []sym.Reloc // for bulk allocation of relocations
 }
 
 func NewLoader() *Loader {
@@ -229,7 +231,7 @@ func (l *Loader) IsExternal(i Sym) bool {
        return l.extStart != 0 && i >= l.extStart
 }
 
-// Ensure Syms slice als enough space.
+// Ensure Syms slice has enough space.
 func (l *Loader) growSyms(i int) {
        n := len(l.Syms)
        if n > i {
@@ -735,10 +737,15 @@ func preprocess(arch *sys.Arch, s *sym.Symbol) {
 func (l *Loader) LoadFull(arch *sys.Arch, syms *sym.Symbols) {
        // create all Symbols first.
        l.growSyms(l.NSym())
+
+       nr := 0 // total number of sym.Reloc's we'll need
        for _, o := range l.objs[1:] {
-               loadObjSyms(l, syms, o.r)
+               nr += loadObjSyms(l, syms, o.r)
        }
 
+       // allocate a single large slab of relocations for all live symbols
+       l.relocBatch = make([]sym.Reloc, nr)
+
        // external symbols
        for i := l.extStart; i <= l.max; i++ {
                if s := l.Syms[i]; s != nil {
@@ -811,14 +818,19 @@ func (l *Loader) addNewSym(i Sym, syms *sym.Symbols, name string, ver int, unit
        return s
 }
 
-func loadObjSyms(l *Loader, syms *sym.Symbols, r *oReader) {
+// loadObjSyms creates sym.Symbol objects for the live Syms in the
+// object corresponding to object reader "r". Return value is the
+// number of sym.Reloc entries required for all the new symbols.
+func loadObjSyms(l *Loader, syms *sym.Symbols, r *oReader) int {
        istart := l.startIndex(r)
+       nr := 0
 
        for i, n := 0, r.NSym()+r.NNonpkgdef(); i < n; i++ {
                // If it's been previously loaded in host object loading, we don't need to do it again.
                if s := l.Syms[istart+Sym(i)]; s != nil {
                        // Mark symbol as reachable as it wasn't marked as such before.
                        s.Attr.Set(sym.AttrReachable, l.Reachable.Has(istart+Sym(i)))
+                       nr += r.NReloc(i)
                        continue
                }
                osym := goobj2.Sym{}
@@ -848,7 +860,28 @@ func loadObjSyms(l *Loader, syms *sym.Symbols, r *oReader) {
 
                s := l.addNewSym(istart+Sym(i), syms, name, ver, r.unit, t)
                s.Attr.Set(sym.AttrReachable, l.Reachable.Has(istart+Sym(i)))
+               nr += r.NReloc(i)
        }
+       return nr
+}
+
+// funcInfoSym records the sym.Symbol for a function, along with a copy
+// of the corresponding goobj2.Sym and the index of its FuncInfo aux sym.
+// We use this to delay populating FuncInfo until we can batch-allocate
+// slices for their sub-objects.
+type funcInfoSym struct {
+       s    *sym.Symbol // sym.Symbol for a live function
+       osym goobj2.Sym  // object file symbol data for that function
+       isym int         // global symbol index of FuncInfo aux sym for func
+}
+
+// funcAllocInfo records totals/counts for all functions in an objfile;
+// used to help with bulk allocation of sym.Symbol sub-objects.
+type funcAllocInfo struct {
+       symPtr  uint32 // number of *sym.Symbol's needed in file slices
+       inlCall uint32 // number of sym.InlinedCall's needed in inltree slices
+       pcData  uint32 // number of sym.Pcdata's needed in pdata slices
+       fdOff   uint32 // number of int64's needed in all Funcdataoff slices
 }
 
 // LoadSymbol loads a single symbol by name.
@@ -884,6 +917,9 @@ func loadObjFull(l *Loader, r *oReader) {
                return l.Syms[i]
        }
 
+       funcs := []funcInfoSym{}
+       fdsyms := []*sym.Symbol{}
+       var funcAllocCounts funcAllocInfo
        pcdataBase := r.PcdataBase()
        rslice := []Reloc{}
        for i, n := 0, r.NSym()+r.NNonpkgdef(); i < n; i++ {
@@ -930,7 +966,9 @@ func loadObjFull(l *Loader, r *oReader) {
                // Relocs
                relocs := l.relocs(r, i)
                rslice = relocs.ReadAll(rslice)
-               s.R = make([]sym.Reloc, relocs.Count)
+               batch := l.relocBatch
+               s.R = batch[:relocs.Count:relocs.Count]
+               l.relocBatch = batch[relocs.Count:]
                for j := range s.R {
                        r := rslice[j]
                        rs := r.Sym
@@ -974,12 +1012,7 @@ func loadObjFull(l *Loader, r *oReader) {
                                        s.Gotype = typ
                                }
                        case goobj2.AuxFuncdata:
-                               pc := s.FuncInfo
-                               if pc == nil {
-                                       pc = &sym.FuncInfo{Funcdata: make([]*sym.Symbol, 0, 4)}
-                                       s.FuncInfo = pc
-                               }
-                               pc.Funcdata = append(pc.Funcdata, resolveSymRef(a.Sym))
+                               fdsyms = append(fdsyms, resolveSymRef(a.Sym))
                        case goobj2.AuxFuncInfo:
                                if a.Sym.PkgIdx != goobj2.PkgIdxSelf {
                                        panic("funcinfo symbol not defined in current package")
@@ -1014,10 +1047,44 @@ func loadObjFull(l *Loader, r *oReader) {
                        continue
                }
 
-               // FuncInfo
                if isym == -1 {
                        continue
                }
+
+               // Record function sym and associated info for additional
+               // processing in the loop below.
+               fwis := funcInfoSym{s: s, isym: isym, osym: osym}
+               funcs = append(funcs, fwis)
+
+               // Read the goobj2.FuncInfo for this text symbol so that we can
+               // collect allocation counts. We'll read it again in the loop
+               // below.
+               b := r.Data(isym)
+               info := goobj2.FuncInfo{}
+               info.Read(b)
+               funcAllocCounts.symPtr += uint32(len(info.File))
+               funcAllocCounts.pcData += uint32(len(info.Pcdata))
+               funcAllocCounts.inlCall += uint32(len(info.InlTree))
+               funcAllocCounts.fdOff += uint32(len(info.Funcdataoff))
+       }
+
+       // At this point we can do batch allocation of the sym.FuncInfo's,
+       // along with the slices of sub-objects they use.
+       fiBatch := make([]sym.FuncInfo, len(funcs))
+       inlCallBatch := make([]sym.InlinedCall, funcAllocCounts.inlCall)
+       symPtrBatch := make([]*sym.Symbol, funcAllocCounts.symPtr)
+       pcDataBatch := make([]sym.Pcdata, funcAllocCounts.pcData)
+       fdOffBatch := make([]int64, funcAllocCounts.fdOff)
+
+       // Populate FuncInfo contents for func symbols.
+       for fi := 0; fi < len(funcs); fi++ {
+               s := funcs[fi].s
+               isym := funcs[fi].isym
+               osym := funcs[fi].osym
+
+               s.FuncInfo = &fiBatch[0]
+               fiBatch = fiBatch[1:]
+
                b := r.Data(isym)
                info := goobj2.FuncInfo{}
                info.Read(b)
@@ -1035,18 +1102,34 @@ func loadObjFull(l *Loader, r *oReader) {
                        s.Attr |= sym.AttrTopFrame
                }
 
-               info.Pcdata = append(info.Pcdata, info.PcdataEnd) // for the ease of knowing where it ends
                pc := s.FuncInfo
-               if pc == nil {
-                       pc = &sym.FuncInfo{}
-                       s.FuncInfo = pc
+
+               if len(info.Funcdataoff) != 0 {
+                       nfd := len(info.Funcdataoff)
+                       pc.Funcdata = fdsyms[:nfd:nfd]
+                       fdsyms = fdsyms[nfd:]
                }
+
+               info.Pcdata = append(info.Pcdata, info.PcdataEnd) // for the ease of knowing where it ends
                pc.Args = int32(info.Args)
                pc.Locals = int32(info.Locals)
-               pc.Pcdata = make([]sym.Pcdata, len(info.Pcdata)-1) // -1 as we appended one above
-               pc.Funcdataoff = make([]int64, len(info.Funcdataoff))
-               pc.File = make([]*sym.Symbol, len(info.File))
-               pc.InlTree = make([]sym.InlinedCall, len(info.InlTree))
+
+               npc := len(info.Pcdata) - 1 // -1 as we appended one above
+               pc.Pcdata = pcDataBatch[:npc:npc]
+               pcDataBatch = pcDataBatch[npc:]
+
+               nfd := len(info.Funcdataoff)
+               pc.Funcdataoff = fdOffBatch[:nfd:nfd]
+               fdOffBatch = fdOffBatch[nfd:]
+
+               nsp := len(info.File)
+               pc.File = symPtrBatch[:nsp:nsp]
+               symPtrBatch = symPtrBatch[nsp:]
+
+               nic := len(info.InlTree)
+               pc.InlTree = inlCallBatch[:nic:nic]
+               inlCallBatch = inlCallBatch[nic:]
+
                pc.Pcsp.P = r.BytesAt(pcdataBase+info.Pcsp, int(info.Pcfile-info.Pcsp))
                pc.Pcfile.P = r.BytesAt(pcdataBase+info.Pcfile, int(info.Pcline-info.Pcfile))
                pc.Pcline.P = r.BytesAt(pcdataBase+info.Pcline, int(info.Pcinline-info.Pcline))
@@ -1071,6 +1154,7 @@ func loadObjFull(l *Loader, r *oReader) {
                        }
                }
 
+               dupok := osym.Dupok()
                if !dupok {
                        if s.Attr.OnList() {
                                log.Fatalf("symbol %s listed multiple times", s.Name)
@@ -1078,7 +1162,7 @@ func loadObjFull(l *Loader, r *oReader) {
                        s.Attr.Set(sym.AttrOnList, true)
                        lib.Textp = append(lib.Textp, s)
                } else {
-                       // there may ba a dup in another package
+                       // there may be a dup in another package
                        // put into a temp list and add to text later
                        lib.DupTextSyms = append(lib.DupTextSyms, s)
                }