Commit dbe32284 authored by Cherry Zhang's avatar Cherry Zhang

cmd/link: mmap object data

This resurrects CL 121198, except that this time we map read-only.

In case that we need to apply relocations to the symbol's
content that is backed by read-only memory, we do our own copy-
on-write. This can happen if we failed to mmap the output file,
or we build for Wasm.

Memory profile for building k8s.io/kubernetes/cmd/kube-apiserver
on Linux/AMD64:

Old (before this sequence of CLs):
inuse_space 1598.75MB total
669.87MB 41.90% 41.90%   669.87MB 41.90%  cmd/link/internal/objfile.(*objReader).readSlices

New:
inuse_space 1280.45MB total
441.18MB 34.46% 34.46%   441.18MB 34.46%  cmd/link/internal/objfile.(*objReader).readSlices

Change-Id: I6b4d29d6eee9828089ea3120eb38c212db21330b
Reviewed-on: https://go-review.googlesource.com/c/go/+/170741
Run-TryBot: Cherry Zhang <cherryyz@google.com>
Reviewed-by: default avatarAustin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent f957a7e3
...@@ -7,6 +7,7 @@ package bio ...@@ -7,6 +7,7 @@ package bio
import ( import (
"bufio" "bufio"
"io"
"log" "log"
"os" "os"
) )
...@@ -105,3 +106,26 @@ func (r *Reader) File() *os.File { ...@@ -105,3 +106,26 @@ func (r *Reader) File() *os.File {
func (w *Writer) File() *os.File { func (w *Writer) File() *os.File {
return w.f return w.f
} }
// Slice reads the next length bytes of r into a slice.
//
// This slice may be backed by mmap'ed memory. Currently, this memory
// will never be unmapped. The second result reports whether the
// backing memory is read-only.
func (r *Reader) Slice(length uint64) ([]byte, bool, error) {
if length == 0 {
return []byte{}, false, nil
}
data, ok := r.sliceOS(length)
if ok {
return data, true, nil
}
data = make([]byte, length)
_, err := io.ReadFull(r, data)
if err != nil {
return nil, false, err
}
return data, false, nil
}
// Copyright 2019 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.
// +build darwin dragonfly freebsd linux netbsd openbsd
package bio
import (
"runtime"
"sync/atomic"
"syscall"
)
// mmapLimit is the maximum number of mmaped regions to create before
// falling back to reading into a heap-allocated slice. This exists
// because some operating systems place a limit on the number of
// distinct mapped regions per process. As of this writing:
//
// Darwin unlimited
// DragonFly 1000000 (vm.max_proc_mmap)
// FreeBSD unlimited
// Linux 65530 (vm.max_map_count) // TODO: query /proc/sys/vm/max_map_count?
// NetBSD unlimited
// OpenBSD unlimited
var mmapLimit int32 = 1<<31 - 1
func init() {
// Linux is the only practically concerning OS.
if runtime.GOOS == "linux" {
mmapLimit = 30000
}
}
func (r *Reader) sliceOS(length uint64) ([]byte, bool) {
// For small slices, don't bother with the overhead of a
// mapping, especially since we have no way to unmap it.
const threshold = 16 << 10
if length < threshold {
return nil, false
}
// Have we reached the mmap limit?
if atomic.AddInt32(&mmapLimit, -1) < 0 {
atomic.AddInt32(&mmapLimit, 1)
return nil, false
}
// Page-align the offset.
off := r.Offset()
align := syscall.Getpagesize()
aoff := off &^ int64(align-1)
data, err := syscall.Mmap(int(r.f.Fd()), aoff, int(length+uint64(off-aoff)), syscall.PROT_READ, syscall.MAP_SHARED|syscall.MAP_FILE)
if err != nil {
return nil, false
}
data = data[off-aoff:]
r.Seek(int64(length), 1)
return data, true
}
// Copyright 2019 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.
// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd
package bio
func (r *Reader) sliceOS(length uint64) ([]byte, bool) {
return nil, false
}
...@@ -127,6 +127,15 @@ func trampoline(ctxt *Link, s *sym.Symbol) { ...@@ -127,6 +127,15 @@ func trampoline(ctxt *Link, s *sym.Symbol) {
// This is a performance-critical function for the linker; be careful // This is a performance-critical function for the linker; be careful
// to avoid introducing unnecessary allocations in the main loop. // to avoid introducing unnecessary allocations in the main loop.
func relocsym(ctxt *Link, s *sym.Symbol) { func relocsym(ctxt *Link, s *sym.Symbol) {
if len(s.R) == 0 {
return
}
if s.Attr.ReadOnly() {
// The symbol's content is backed by read-only memory.
// Copy it to writable memory to apply relocations.
s.P = append([]byte(nil), s.P...)
s.Attr.Set(sym.AttrReadOnly, false)
}
for ri := int32(0); ri < int32(len(s.R)); ri++ { for ri := int32(0); ri < int32(len(s.R)); ri++ {
r := &s.R[ri] r := &s.R[ri]
if r.Done { if r.Done {
...@@ -2384,17 +2393,21 @@ func compressSyms(ctxt *Link, syms []*sym.Symbol) []byte { ...@@ -2384,17 +2393,21 @@ func compressSyms(ctxt *Link, syms []*sym.Symbol) []byte {
if err != nil { if err != nil {
log.Fatalf("NewWriterLevel failed: %s", err) log.Fatalf("NewWriterLevel failed: %s", err)
} }
for _, sym := range syms { for _, s := range syms {
// sym.P may be read-only. Apply relocations in a // s.P may be read-only. Apply relocations in a
// temporary buffer, and immediately write it out. // temporary buffer, and immediately write it out.
oldP := sym.P oldP := s.P
ctxt.relocbuf = append(ctxt.relocbuf[:0], sym.P...) wasReadOnly := s.Attr.ReadOnly()
sym.P = ctxt.relocbuf if len(s.R) != 0 && wasReadOnly {
relocsym(ctxt, sym) ctxt.relocbuf = append(ctxt.relocbuf[:0], s.P...)
if _, err := z.Write(sym.P); err != nil { s.P = ctxt.relocbuf
s.Attr.Set(sym.AttrReadOnly, false)
}
relocsym(ctxt, s)
if _, err := z.Write(s.P); err != nil {
log.Fatalf("compression failed: %s", err) log.Fatalf("compression failed: %s", err)
} }
for i := sym.Size - int64(len(sym.P)); i > 0; { for i := s.Size - int64(len(s.P)); i > 0; {
b := zeros[:] b := zeros[:]
if i < int64(len(b)) { if i < int64(len(b)) {
b = b[:i] b = b[:i]
...@@ -2405,13 +2418,15 @@ func compressSyms(ctxt *Link, syms []*sym.Symbol) []byte { ...@@ -2405,13 +2418,15 @@ func compressSyms(ctxt *Link, syms []*sym.Symbol) []byte {
} }
i -= int64(n) i -= int64(n)
} }
// Restore sym.P, for 1. not holding temp buffer live // Restore s.P if a temporary buffer was used. If compression
// unnecessarily, 2. if compression is not beneficial, // is not beneficial, we'll go back to use the uncompressed
// we'll go back to use the uncompressed contents, in // contents, in which case we still need s.P.
// which case we still need sym.P. if len(s.R) != 0 && wasReadOnly {
sym.P = oldP s.P = oldP
for i := range sym.R { s.Attr.Set(sym.AttrReadOnly, wasReadOnly)
sym.R[i].Done = false for i := range s.R {
s.R[i].Done = false
}
} }
} }
if err := z.Close(); err != nil { if err := z.Close(); err != nil {
......
...@@ -158,6 +158,7 @@ func (out *OutBuf) WriteSym(s *sym.Symbol) { ...@@ -158,6 +158,7 @@ func (out *OutBuf) WriteSym(s *sym.Symbol) {
start := out.off start := out.off
out.Write(s.P) out.Write(s.P)
s.P = out.buf[start:out.off] s.P = out.buf[start:out.off]
s.Attr.Set(sym.AttrReadOnly, false)
} else { } else {
out.Write(s.P) out.Write(s.P)
} }
......
...@@ -34,7 +34,7 @@ var emptyPkg = []byte(`"".`) ...@@ -34,7 +34,7 @@ var emptyPkg = []byte(`"".`)
// objReader reads Go object files. // objReader reads Go object files.
type objReader struct { type objReader struct {
rd *bufio.Reader rd *bio.Reader
arch *sys.Arch arch *sys.Arch
syms *sym.Symbols syms *sym.Symbols
lib *sym.Library lib *sym.Library
...@@ -43,6 +43,7 @@ type objReader struct { ...@@ -43,6 +43,7 @@ type objReader struct {
localSymVersion int localSymVersion int
flags int flags int
strictDupMsgs int strictDupMsgs int
dataSize int
// rdBuf is used by readString and readSymName as scratch for reading strings. // rdBuf is used by readString and readSymName as scratch for reading strings.
rdBuf []byte rdBuf []byte
...@@ -56,6 +57,8 @@ type objReader struct { ...@@ -56,6 +57,8 @@ type objReader struct {
funcdata []*sym.Symbol funcdata []*sym.Symbol
funcdataoff []int64 funcdataoff []int64
file []*sym.Symbol file []*sym.Symbol
dataReadOnly bool // whether data is backed by read-only memory
} }
// Flags to enable optional behavior during object loading/reading. // Flags to enable optional behavior during object loading/reading.
...@@ -76,7 +79,7 @@ const ( ...@@ -76,7 +79,7 @@ const (
func Load(arch *sys.Arch, syms *sym.Symbols, f *bio.Reader, lib *sym.Library, length int64, pn string, flags int) int { func Load(arch *sys.Arch, syms *sym.Symbols, f *bio.Reader, lib *sym.Library, length int64, pn string, flags int) int {
start := f.Offset() start := f.Offset()
r := &objReader{ r := &objReader{
rd: f.Reader, rd: f,
lib: lib, lib: lib,
arch: arch, arch: arch,
syms: syms, syms: syms,
...@@ -133,7 +136,10 @@ func (r *objReader) loadObjFile() { ...@@ -133,7 +136,10 @@ func (r *objReader) loadObjFile() {
r.readSlices() r.readSlices()
// Data section // Data section
r.readFull(r.data) r.data, r.dataReadOnly, err = r.rd.Slice(uint64(r.dataSize))
if err != nil {
log.Fatalf("%s: error reading %s", r.pn, err)
}
// Defined symbols // Defined symbols
for { for {
...@@ -156,9 +162,8 @@ func (r *objReader) loadObjFile() { ...@@ -156,9 +162,8 @@ func (r *objReader) loadObjFile() {
} }
func (r *objReader) readSlices() { func (r *objReader) readSlices() {
r.dataSize = r.readInt()
n := r.readInt() n := r.readInt()
r.data = make([]byte, n)
n = r.readInt()
r.reloc = make([]sym.Reloc, n) r.reloc = make([]sym.Reloc, n)
n = r.readInt() n = r.readInt()
r.pcdata = make([]sym.Pcdata, n) r.pcdata = make([]sym.Pcdata, n)
...@@ -249,6 +254,7 @@ overwrite: ...@@ -249,6 +254,7 @@ overwrite:
dup.Gotype = typ dup.Gotype = typ
} }
s.P = data s.P = data
s.Attr.Set(sym.AttrReadOnly, r.dataReadOnly)
if nreloc > 0 { if nreloc > 0 {
s.R = r.reloc[:nreloc:nreloc] s.R = r.reloc[:nreloc:nreloc]
if !isdup { if !isdup {
......
...@@ -78,7 +78,10 @@ const ( ...@@ -78,7 +78,10 @@ const (
// AttrTopFrame means that the function is an entry point and unwinders // AttrTopFrame means that the function is an entry point and unwinders
// should stop when they hit this function. // should stop when they hit this function.
AttrTopFrame AttrTopFrame
// 18 attributes defined so far. // AttrReadOnly indicates whether the symbol's content (Symbol.P) is backed by
// read-only memory.
AttrReadOnly
// 19 attributes defined so far.
) )
func (a Attribute) DuplicateOK() bool { return a&AttrDuplicateOK != 0 } func (a Attribute) DuplicateOK() bool { return a&AttrDuplicateOK != 0 }
...@@ -99,6 +102,7 @@ func (a Attribute) VisibilityHidden() bool { return a&AttrVisibilityHidden != 0 ...@@ -99,6 +102,7 @@ func (a Attribute) VisibilityHidden() bool { return a&AttrVisibilityHidden != 0
func (a Attribute) SubSymbol() bool { return a&AttrSubSymbol != 0 } func (a Attribute) SubSymbol() bool { return a&AttrSubSymbol != 0 }
func (a Attribute) Container() bool { return a&AttrContainer != 0 } func (a Attribute) Container() bool { return a&AttrContainer != 0 }
func (a Attribute) TopFrame() bool { return a&AttrTopFrame != 0 } func (a Attribute) TopFrame() bool { return a&AttrTopFrame != 0 }
func (a Attribute) ReadOnly() bool { return a&AttrReadOnly != 0 }
func (a Attribute) CgoExport() bool { func (a Attribute) CgoExport() bool {
return a.CgoExportDynamic() || a.CgoExportStatic() return a.CgoExportDynamic() || a.CgoExportStatic()
......
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