]> Cypherpunks repositories - gostls13.git/commitdiff
reflect: cache call frames
authorDmitry Vyukov <dvyukov@google.com>
Mon, 22 Dec 2014 19:31:55 +0000 (22:31 +0300)
committerDmitry Vyukov <dvyukov@google.com>
Wed, 28 Jan 2015 08:40:26 +0000 (08:40 +0000)
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/1954
Reviewed-by: Keith Randall <khr@golang.org>
src/reflect/all_test.go
src/reflect/export_test.go
src/reflect/makefunc.go
src/reflect/type.go
src/reflect/value.go
src/runtime/stubs.go

index 278848bc007811dcbdbdf90daca4228230691acb..7d40f9a8b67f668f990820ad743b40c2ed03acc2 100644 (file)
@@ -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) {
        f := dummy
        fv := MakeFunc(TypeOf(f), func(in []Value) []Value { return in })
index 82a8a10930a816363603252500c7312ffa826656..9f06324bf86e6f2cc6230d1a41d6e6c73844e907 100644 (file)
@@ -26,9 +26,9 @@ func FuncLayout(t Type, rcvr Type) (frametype Type, argSize, retOffset uintptr,
        var ft *rtype
        var s *bitVector
        if rcvr != nil {
-               ft, argSize, retOffset, s = funcLayout(t.(*rtype), rcvr.(*rtype))
+               ft, argSize, retOffset, s, _ = funcLayout(t.(*rtype), rcvr.(*rtype))
        } else {
-               ft, argSize, retOffset, s = funcLayout(t.(*rtype), nil)
+               ft, argSize, retOffset, s, _ = funcLayout(t.(*rtype), nil)
        }
        frametype = ft
        for i := uint32(0); i < s.n; i += 2 {
index d89f7f6811daecb2b07ca9221e1fcc9b0a204d4a..447180524815a18cf0bb8de5e1bbdd39f163cf04 100644 (file)
@@ -56,7 +56,7 @@ func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value {
        code := **(**uintptr)(unsafe.Pointer(&dummy))
 
        // 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}
 
@@ -104,7 +104,7 @@ func makeMethodValue(op string, v Value) Value {
        code := **(**uintptr)(unsafe.Pointer(&dummy))
 
        // methodValue contains a stack map for use by the runtime
-       _, _, _, stack := funcLayout(funcType, nil)
+       _, _, _, stack, _ := funcLayout(funcType, nil)
 
        fv := &methodValue{
                fn:     code,
index 040b9c06ec482e626f103f4d28ebbf149a0647c7..ae7d165a68d2b2655b788b779261e130c7f14e14 100644 (file)
@@ -1809,6 +1809,7 @@ type layoutType struct {
        argSize   uintptr // size of arguments
        retOffset uintptr // offset of return values.
        stack     *bitVector
+       framePool *sync.Pool
 }
 
 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.
 // Currently, that's just size and the GC program.  We also fill in
 // 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 {
                panic("reflect: funcLayout of non-func type")
        }
@@ -1833,13 +1834,13 @@ func funcLayout(t *rtype, rcvr *rtype) (frametype *rtype, argSize, retOffset uin
        layoutCache.RLock()
        if x := layoutCache.m[k]; x.t != nil {
                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.Lock()
        if x := layoutCache.m[k]; x.t != nil {
                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))
@@ -1903,14 +1904,18 @@ func funcLayout(t *rtype, rcvr *rtype) (frametype *rtype, argSize, retOffset uin
        if layoutCache.m == nil {
                layoutCache.m = make(map[layoutKey]layoutType)
        }
+       framePool = &sync.Pool{New: func() interface{} {
+               return unsafe_New(x)
+       }}
        layoutCache.m[k] = layoutType{
                t:         x,
                argSize:   argSize,
                retOffset: retOffset,
                stack:     stack,
+               framePool: framePool,
        }
        layoutCache.Unlock()
-       return x, argSize, retOffset, stack
+       return x, argSize, retOffset, stack, framePool
 }
 
 // ifaceIndir reports whether t is stored indirectly in an interface value.
index 3255a697d5971ec663e64e92a6baf219afa45d11..4060206eacb21cc220c775210c38250cd74977bf 100644 (file)
@@ -393,9 +393,18 @@ func (v Value) call(op string, in []Value) []Value {
        }
        nout := t.NumOut()
 
-       // Compute frame type, allocate a chunk of memory for frame
-       frametype, _, retOffset, _ := funcLayout(t, rcvrtype)
-       args := unsafe_New(frametype)
+       // Compute frame type.
+       frametype, _, retOffset, _, framePool := funcLayout(t, rcvrtype)
+
+       // 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)
 
        // Copy inputs into args.
@@ -427,16 +436,26 @@ func (v Value) call(op string, in []Value) []Value {
                runtime.GC()
        }
 
-       // 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()
+       var ret []Value
+       if nout == 0 {
+               memclr(args, frametype.size)
+               framePool.Put(args)
+       } else {
+               // Zero the now unused input area of args,
+               // because the Values returned by this function contain pointers to the args object,
+               // and will thus keep the args object alive indefinitely.
+               memclr(args, retOffset)
+               // 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
@@ -596,10 +615,10 @@ func align(x, n uintptr) uintptr {
 func callMethod(ctxt *methodValue, frame unsafe.Pointer) {
        rcvr := ctxt.rcvr
        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.
-       args := unsafe_New(frametype)
+       args := framePool.Get().(unsafe.Pointer)
 
        // Copy in receiver and rest of args.
        storeRcvr(rcvr, args)
@@ -622,6 +641,9 @@ func callMethod(ctxt *methodValue, frame unsafe.Pointer) {
                unsafe.Pointer(uintptr(args)+retOffset),
                retOffset,
                frametype.size-retOffset)
+
+       memclr(args, frametype.size)
+       framePool.Put(args)
 }
 
 // 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)
 //go:noescape
 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,
 // for use in cases where the reflect code is so clever that
 // the compiler cannot follow.
index d198f02e600edec80e12335cfa9d1e7ae933d879..9aa83ef5874a10ea9db02604970c058d8217b490 100644 (file)
@@ -61,6 +61,11 @@ func badsystemstack() {
 //go:noescape
 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".
 // in memmove_*.s
 //go:noescape