Commit 67f8a813 authored by Dmitry Vyukov's avatar Dmitry Vyukov

reflect: cache call frames

Call frame allocations can account for significant portion
of all allocations in a program, if call is executed
in an inner loop (e.g. to process every line in a log).
On the other hand, the allocation is easy to remove
using sync.Pool since the allocation is strictly scoped.

benchmark           old ns/op     new ns/op     delta
BenchmarkCall       634           338           -46.69%
BenchmarkCall-4     496           167           -66.33%

benchmark           old allocs     new allocs     delta
BenchmarkCall       1              0              -100.00%
BenchmarkCall-4     1              0              -100.00%

Update #7818

Change-Id: Icf60cce0a9be82e6171f0c0bd80dee2393db54a7
Reviewed-on: https://go-review.googlesource.com/1954Reviewed-by: default avatarKeith Randall <khr@golang.org>
parent bed884e8
...@@ -1506,6 +1506,17 @@ func TestCallWithStruct(t *testing.T) { ...@@ -1506,6 +1506,17 @@ func TestCallWithStruct(t *testing.T) {
} }
} }
func BenchmarkCall(b *testing.B) {
fv := ValueOf(func(a, b string) {})
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
args := []Value{ValueOf("a"), ValueOf("b")}
for pb.Next() {
fv.Call(args)
}
})
}
func TestMakeFunc(t *testing.T) { func TestMakeFunc(t *testing.T) {
f := dummy f := dummy
fv := MakeFunc(TypeOf(f), func(in []Value) []Value { return in }) fv := MakeFunc(TypeOf(f), func(in []Value) []Value { return in })
......
...@@ -26,9 +26,9 @@ func FuncLayout(t Type, rcvr Type) (frametype Type, argSize, retOffset uintptr, ...@@ -26,9 +26,9 @@ func FuncLayout(t Type, rcvr Type) (frametype Type, argSize, retOffset uintptr,
var ft *rtype var ft *rtype
var s *bitVector var s *bitVector
if rcvr != nil { if rcvr != nil {
ft, argSize, retOffset, s = funcLayout(t.(*rtype), rcvr.(*rtype)) ft, argSize, retOffset, s, _ = funcLayout(t.(*rtype), rcvr.(*rtype))
} else { } else {
ft, argSize, retOffset, s = funcLayout(t.(*rtype), nil) ft, argSize, retOffset, s, _ = funcLayout(t.(*rtype), nil)
} }
frametype = ft frametype = ft
for i := uint32(0); i < s.n; i += 2 { for i := uint32(0); i < s.n; i += 2 {
......
...@@ -56,7 +56,7 @@ func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { ...@@ -56,7 +56,7 @@ func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value {
code := **(**uintptr)(unsafe.Pointer(&dummy)) code := **(**uintptr)(unsafe.Pointer(&dummy))
// makeFuncImpl contains a stack map for use by the runtime // makeFuncImpl contains a stack map for use by the runtime
_, _, _, stack := funcLayout(t, nil) _, _, _, stack, _ := funcLayout(t, nil)
impl := &makeFuncImpl{code: code, stack: stack, typ: ftyp, fn: fn} impl := &makeFuncImpl{code: code, stack: stack, typ: ftyp, fn: fn}
...@@ -104,7 +104,7 @@ func makeMethodValue(op string, v Value) Value { ...@@ -104,7 +104,7 @@ func makeMethodValue(op string, v Value) Value {
code := **(**uintptr)(unsafe.Pointer(&dummy)) code := **(**uintptr)(unsafe.Pointer(&dummy))
// methodValue contains a stack map for use by the runtime // methodValue contains a stack map for use by the runtime
_, _, _, stack := funcLayout(funcType, nil) _, _, _, stack, _ := funcLayout(funcType, nil)
fv := &methodValue{ fv := &methodValue{
fn: code, fn: code,
......
...@@ -1809,6 +1809,7 @@ type layoutType struct { ...@@ -1809,6 +1809,7 @@ type layoutType struct {
argSize uintptr // size of arguments argSize uintptr // size of arguments
retOffset uintptr // offset of return values. retOffset uintptr // offset of return values.
stack *bitVector stack *bitVector
framePool *sync.Pool
} }
var layoutCache struct { var layoutCache struct {
...@@ -1822,7 +1823,7 @@ var layoutCache struct { ...@@ -1822,7 +1823,7 @@ var layoutCache struct {
// The returned type exists only for GC, so we only fill out GC relevant info. // The returned type exists only for GC, so we only fill out GC relevant info.
// Currently, that's just size and the GC program. We also fill in // Currently, that's just size and the GC program. We also fill in
// the name for possible debugging use. // the name for possible debugging use.
func funcLayout(t *rtype, rcvr *rtype) (frametype *rtype, argSize, retOffset uintptr, stack *bitVector) { func funcLayout(t *rtype, rcvr *rtype) (frametype *rtype, argSize, retOffset uintptr, stack *bitVector, framePool *sync.Pool) {
if t.Kind() != Func { if t.Kind() != Func {
panic("reflect: funcLayout of non-func type") panic("reflect: funcLayout of non-func type")
} }
...@@ -1833,13 +1834,13 @@ func funcLayout(t *rtype, rcvr *rtype) (frametype *rtype, argSize, retOffset uin ...@@ -1833,13 +1834,13 @@ func funcLayout(t *rtype, rcvr *rtype) (frametype *rtype, argSize, retOffset uin
layoutCache.RLock() layoutCache.RLock()
if x := layoutCache.m[k]; x.t != nil { if x := layoutCache.m[k]; x.t != nil {
layoutCache.RUnlock() layoutCache.RUnlock()
return x.t, x.argSize, x.retOffset, x.stack return x.t, x.argSize, x.retOffset, x.stack, x.framePool
} }
layoutCache.RUnlock() layoutCache.RUnlock()
layoutCache.Lock() layoutCache.Lock()
if x := layoutCache.m[k]; x.t != nil { if x := layoutCache.m[k]; x.t != nil {
layoutCache.Unlock() layoutCache.Unlock()
return x.t, x.argSize, x.retOffset, x.stack return x.t, x.argSize, x.retOffset, x.stack, x.framePool
} }
tt := (*funcType)(unsafe.Pointer(t)) tt := (*funcType)(unsafe.Pointer(t))
...@@ -1903,14 +1904,18 @@ func funcLayout(t *rtype, rcvr *rtype) (frametype *rtype, argSize, retOffset uin ...@@ -1903,14 +1904,18 @@ func funcLayout(t *rtype, rcvr *rtype) (frametype *rtype, argSize, retOffset uin
if layoutCache.m == nil { if layoutCache.m == nil {
layoutCache.m = make(map[layoutKey]layoutType) layoutCache.m = make(map[layoutKey]layoutType)
} }
framePool = &sync.Pool{New: func() interface{} {
return unsafe_New(x)
}}
layoutCache.m[k] = layoutType{ layoutCache.m[k] = layoutType{
t: x, t: x,
argSize: argSize, argSize: argSize,
retOffset: retOffset, retOffset: retOffset,
stack: stack, stack: stack,
framePool: framePool,
} }
layoutCache.Unlock() layoutCache.Unlock()
return x, argSize, retOffset, stack return x, argSize, retOffset, stack, framePool
} }
// ifaceIndir reports whether t is stored indirectly in an interface value. // ifaceIndir reports whether t is stored indirectly in an interface value.
......
...@@ -393,9 +393,18 @@ func (v Value) call(op string, in []Value) []Value { ...@@ -393,9 +393,18 @@ func (v Value) call(op string, in []Value) []Value {
} }
nout := t.NumOut() nout := t.NumOut()
// Compute frame type, allocate a chunk of memory for frame // Compute frame type.
frametype, _, retOffset, _ := funcLayout(t, rcvrtype) frametype, _, retOffset, _, framePool := funcLayout(t, rcvrtype)
args := unsafe_New(frametype)
// Allocate a chunk of memory for frame.
var args unsafe.Pointer
if nout == 0 {
args = framePool.Get().(unsafe.Pointer)
} else {
// Can't use pool if the function has return values.
// We will leak pointer to args in ret, so its lifetime is not scoped.
args = unsafe_New(frametype)
}
off := uintptr(0) off := uintptr(0)
// Copy inputs into args. // Copy inputs into args.
...@@ -427,16 +436,26 @@ func (v Value) call(op string, in []Value) []Value { ...@@ -427,16 +436,26 @@ func (v Value) call(op string, in []Value) []Value {
runtime.GC() runtime.GC()
} }
// Copy return values out of args. var ret []Value
ret := make([]Value, nout) if nout == 0 {
off = retOffset memclr(args, frametype.size)
for i := 0; i < nout; i++ { framePool.Put(args)
tv := t.Out(i) } else {
a := uintptr(tv.Align()) // Zero the now unused input area of args,
off = (off + a - 1) &^ (a - 1) // because the Values returned by this function contain pointers to the args object,
fl := flagIndir | flag(tv.Kind()) // and will thus keep the args object alive indefinitely.
ret[i] = Value{tv.common(), unsafe.Pointer(uintptr(args) + off), fl} memclr(args, retOffset)
off += tv.Size() // Copy return values out of args.
ret = make([]Value, nout)
off = retOffset
for i := 0; i < nout; i++ {
tv := t.Out(i)
a := uintptr(tv.Align())
off = (off + a - 1) &^ (a - 1)
fl := flagIndir | flag(tv.Kind())
ret[i] = Value{tv.common(), unsafe.Pointer(uintptr(args) + off), fl}
off += tv.Size()
}
} }
return ret return ret
...@@ -596,10 +615,10 @@ func align(x, n uintptr) uintptr { ...@@ -596,10 +615,10 @@ func align(x, n uintptr) uintptr {
func callMethod(ctxt *methodValue, frame unsafe.Pointer) { func callMethod(ctxt *methodValue, frame unsafe.Pointer) {
rcvr := ctxt.rcvr rcvr := ctxt.rcvr
rcvrtype, t, fn := methodReceiver("call", rcvr, ctxt.method) rcvrtype, t, fn := methodReceiver("call", rcvr, ctxt.method)
frametype, argSize, retOffset, _ := funcLayout(t, rcvrtype) frametype, argSize, retOffset, _, framePool := funcLayout(t, rcvrtype)
// Make a new frame that is one word bigger so we can store the receiver. // Make a new frame that is one word bigger so we can store the receiver.
args := unsafe_New(frametype) args := framePool.Get().(unsafe.Pointer)
// Copy in receiver and rest of args. // Copy in receiver and rest of args.
storeRcvr(rcvr, args) storeRcvr(rcvr, args)
...@@ -622,6 +641,9 @@ func callMethod(ctxt *methodValue, frame unsafe.Pointer) { ...@@ -622,6 +641,9 @@ func callMethod(ctxt *methodValue, frame unsafe.Pointer) {
unsafe.Pointer(uintptr(args)+retOffset), unsafe.Pointer(uintptr(args)+retOffset),
retOffset, retOffset,
frametype.size-retOffset) frametype.size-retOffset)
memclr(args, frametype.size)
framePool.Put(args)
} }
// funcName returns the name of f, for use in error messages. // funcName returns the name of f, for use in error messages.
...@@ -2448,6 +2470,9 @@ func typedmemmovepartial(t *rtype, dst, src unsafe.Pointer, off, size uintptr) ...@@ -2448,6 +2470,9 @@ func typedmemmovepartial(t *rtype, dst, src unsafe.Pointer, off, size uintptr)
//go:noescape //go:noescape
func typedslicecopy(elemType *rtype, dst, src sliceHeader) int func typedslicecopy(elemType *rtype, dst, src sliceHeader) int
//go:noescape
func memclr(ptr unsafe.Pointer, n uintptr)
// Dummy annotation marking that the value x escapes, // Dummy annotation marking that the value x escapes,
// for use in cases where the reflect code is so clever that // for use in cases where the reflect code is so clever that
// the compiler cannot follow. // the compiler cannot follow.
......
...@@ -61,6 +61,11 @@ func badsystemstack() { ...@@ -61,6 +61,11 @@ func badsystemstack() {
//go:noescape //go:noescape
func memclr(ptr unsafe.Pointer, n uintptr) func memclr(ptr unsafe.Pointer, n uintptr)
//go:linkname reflect_memclr reflect.memclr
func reflect_memclr(ptr unsafe.Pointer, n uintptr) {
memclr(ptr, n)
}
// memmove copies n bytes from "from" to "to". // memmove copies n bytes from "from" to "to".
// in memmove_*.s // in memmove_*.s
//go:noescape //go:noescape
......
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