Browse Source

codec: optimize reflect mode

- optimize convert maps/slices from one value to another (rvconvert)
- optimize allocation of new value (rvzeroaddr)
- pool map iterators to reduce allocation cost
Ugorji Nwoke 6 years ago
parent
commit
ae051ebe2b

+ 11 - 3
codec/codec_test.go

@@ -3223,7 +3223,10 @@ func TestMapRangeIndex(t *testing.T) {
 	}
 
 	mt := reflect.TypeOf(m1)
-	it := mapRange(reflect.ValueOf(m1), mapAddressableRV(mt.Key()), mapAddressableRV(mt.Elem()), true)
+	it := mapRange(reflect.ValueOf(m1),
+		mapAddressableRV(mt.Key(), mt.Key().Kind()),
+		mapAddressableRV(mt.Elem(), mt.Elem().Kind()),
+		true)
 	for it.Next() {
 		k := it.Key().Interface().(string)
 		v := it.Value().Interface().(*T)
@@ -3235,6 +3238,7 @@ func TestMapRangeIndex(t *testing.T) {
 			t.FailNow()
 		}
 	}
+	it.Done()
 	testDeepEqualErr(len(m1c), 0, t, "all-keys-not-consumed")
 
 	// ------
@@ -3249,7 +3253,10 @@ func TestMapRangeIndex(t *testing.T) {
 	}
 
 	mt = reflect.TypeOf(m2)
-	it = mapRange(reflect.ValueOf(m2), mapAddressableRV(mt.Key()), mapAddressableRV(mt.Elem()), true)
+	it = mapRange(reflect.ValueOf(m2),
+		mapAddressableRV(mt.Key(), mt.Key().Kind()),
+		mapAddressableRV(mt.Elem(), mt.Elem().Kind()),
+		true)
 	for it.Next() {
 		k := it.Key().Interface().(*T)
 		v := it.Value().Interface().(T)
@@ -3261,6 +3268,7 @@ func TestMapRangeIndex(t *testing.T) {
 			t.FailNow()
 		}
 	}
+	it.Done()
 	testDeepEqualErr(len(m2c), 0, t, "all-keys-not-consumed")
 
 	// ---- test mapGet
@@ -3268,7 +3276,7 @@ func TestMapRangeIndex(t *testing.T) {
 	fnTestMapIndex := func(mi ...interface{}) {
 		for _, m0 := range mi {
 			m := reflect.ValueOf(m0)
-			rvv := mapAddressableRV(m.Type().Elem())
+			rvv := mapAddressableRV(m.Type().Elem(), m.Type().Elem().Kind())
 			for _, k := range m.MapKeys() {
 				testDeepEqualErr(m.MapIndex(k).Interface(), mapGet(m, k, rvv).Interface(), t, "map-index-eq")
 			}

+ 24 - 18
codec/decode.go

@@ -385,7 +385,7 @@ func (d *Decoder) kInterfaceNaked(f *codecFnInfo) (rvn reflect.Value) {
 				d.decode(rv2i(rvn))
 				rvn = rvn.Elem()
 			} else {
-				rvn = reflect.New(d.h.MapType).Elem()
+				rvn = rvzeroaddrk(d.h.MapType, reflect.Map)
 				d.decodeValue(rvn, nil)
 			}
 		}
@@ -395,7 +395,7 @@ func (d *Decoder) kInterfaceNaked(f *codecFnInfo) (rvn reflect.Value) {
 			d.decode(&v2)
 			rvn = reflect.ValueOf(&v2).Elem()
 			if reflectArrayOfSupported && d.stid == 0 && d.h.PreferArrayOverSlice {
-				rvn2 := reflect.New(reflectArrayOf(rvn.Len(), intfTyp)).Elem()
+				rvn2 := rvzeroaddrk(reflectArrayOf(rvn.Len(), intfTyp), reflect.Array)
 				reflect.Copy(rvn2, rvn)
 				rvn = rvn2
 			}
@@ -405,7 +405,7 @@ func (d *Decoder) kInterfaceNaked(f *codecFnInfo) (rvn reflect.Value) {
 				d.decode(rv2i(rvn))
 				rvn = rvn.Elem()
 			} else {
-				rvn = reflect.New(d.h.SliceType).Elem()
+				rvn = rvzeroaddrk(d.h.SliceType, reflect.Slice)
 				d.decodeValue(rvn, nil)
 			}
 		}
@@ -420,11 +420,11 @@ func (d *Decoder) kInterfaceNaked(f *codecFnInfo) (rvn reflect.Value) {
 				d.decode(&re.Value)
 				rvn = reflect.ValueOf(&re).Elem()
 			} else {
-				rvn = reflect.New(bfn.rt)
 				if bfn.ext == SelfExt {
-					rvn = rvn.Elem()
-					d.decodeValue(rvn, d.h.fnNoExt(rvn.Type()))
+					rvn = rvzeroaddrk(bfn.rt, bfn.rt.Kind())
+					d.decodeValue(rvn, d.h.fnNoExt(bfn.rt))
 				} else {
+					rvn = reflect.New(bfn.rt)
 					d.interfaceExtConvertAndDecode(rv2i(rvn), bfn.ext)
 					rvn = rvn.Elem()
 				}
@@ -518,7 +518,7 @@ func (d *Decoder) kInterface(f *codecFnInfo, rv reflect.Value) {
 		return
 	}
 
-	rvn2 := reflect.New(rvn.Type()).Elem()
+	rvn2 := rvzeroaddrk(rvn.Type(), rvn.Kind())
 	rvn2.Set(rvn)
 	d.decodeValue(rvn2, nil)
 	rv.Set(rvn2)
@@ -931,7 +931,7 @@ func (d *Decoder) kSliceForChan(f *codecFnInfo, rv reflect.Value) {
 		// 	continue
 		// }
 		if rtelem0Mut || !rv9.IsValid() { // || (rtElem0Kind == reflect.Ptr && rvisnil(rv9)) {
-			rv9 = reflect.New(rtelem0).Elem()
+			rv9 = rvzeroaddrk(rtelem0, rtElem0Kind)
 		}
 		if fn == nil {
 			fn = d.h.fn(rtelem)
@@ -972,7 +972,10 @@ func (d *Decoder) kMap(f *codecFnInfo, rv reflect.Value) {
 	ktype, vtype := ti.key, ti.elem
 	ktypeId := rt2id(ktype)
 	vtypeKind := vtype.Kind()
-	// ktypeKind := ktype.Kind()
+	ktypeKind := ktype.Kind()
+
+	var vtypeElem reflect.Type
+
 	var keyFn, valFn *codecFn
 	var ktypeLo, vtypeLo reflect.Type
 
@@ -1011,15 +1014,15 @@ func (d *Decoder) kMap(f *codecFnInfo, rv reflect.Value) {
 			// rvvz = reflect.Zero(vtype)
 			// rvkz = reflect.Zero(ktype)
 			if !rvkMut {
-				rvkn = reflect.New(ktype).Elem() //, ktypeKind)
+				rvkn = rvzeroaddrk(ktype, ktypeKind)
 			}
 			if !rvvMut {
-				rvvn = reflect.New(vtype).Elem() //, vtypeKind)
+				rvvn = rvzeroaddrk(vtype, vtypeKind)
 			}
 		}
 
 		if rvkMut {
-			rvk = reflect.New(ktype).Elem() //, ktypeKind)
+			rvk = rvzeroaddrk(ktype, ktypeKind)
 		} else {
 			rvk = rvkn
 		}
@@ -1066,7 +1069,7 @@ func (d *Decoder) kMap(f *codecFnInfo, rv reflect.Value) {
 		doMapSet = true // set to false if u do a get, and its a non-nil pointer
 		if doMapGet {
 			if !rvvaSet {
-				rvva = mapAddressableRV(vtype)
+				rvva = mapAddressableRV(vtype, vtypeKind)
 				rvvaSet = true
 			}
 			rvv = mapGet(rv, rvk, rvva) // reflect.Value{})
@@ -1074,19 +1077,22 @@ func (d *Decoder) kMap(f *codecFnInfo, rv reflect.Value) {
 				if rvv.IsValid() && !rvisnil(rvv) {
 					doMapSet = false
 				} else {
-					rvv = reflect.New(vtype.Elem())
+					if vtypeElem == nil {
+						vtypeElem = vtype.Elem()
+					}
+					rvv = reflect.New(vtypeElem) // TODO: use rvzeroaddr?
 				}
 			} else if rvv.IsValid() && vtypeKind == reflect.Interface && !rvisnil(rvv) {
-				rvvn = reflect.New(vtype).Elem()
+				rvvn = rvzeroaddrk(vtype, vtypeKind)
 				rvvn.Set(rvv)
 				rvv = rvvn
 			} else if rvvMut {
-				rvv = reflect.New(vtype).Elem()
+				rvv = rvzeroaddrk(vtype, vtypeKind)
 			} else {
 				rvv = rvvn
 			}
 		} else if rvvMut {
-			rvv = reflect.New(vtype).Elem() //, vtypeKind)
+			rvv = rvzeroaddrk(vtype, vtypeKind)
 		} else {
 			rvv = rvvn
 		}
@@ -1986,7 +1992,7 @@ func (d *Decoder) interfaceExtConvertAndDecode(v interface{}, ext Ext) {
 			rv2 = reflect.New(rv.Type().Elem())
 			rv2.Set(rv)
 		} else {
-			rv2 = reflect.New(rv.Type()).Elem()
+			rv2 = rvzeroaddrk(rv.Type(), rv.Kind())
 			rv2.Set(rv)
 		}
 		rv = rv2

+ 17 - 11
codec/encode.go

@@ -553,6 +553,7 @@ func (e *Encoder) kMap(f *codecFnInfo, rv reflect.Value) {
 		e.mapEnd()
 		return
 	}
+
 	// var asSymbols bool
 	// determine the underlying key and val encFn's for the map.
 	// This eliminates some work which is done for each loop iteration i.e.
@@ -562,17 +563,25 @@ func (e *Encoder) kMap(f *codecFnInfo, rv reflect.Value) {
 	// encoding type, because preEncodeValue may break it down to
 	// a concrete type and kInterface will bomb.
 	var keyFn, valFn *codecFn
-	rtval := f.ti.elem
+
 	// rtkeyid := rt2id(f.ti.key)
-	for rtval.Kind() == reflect.Ptr {
+	ktypeKind := f.ti.key.Kind()
+	vtypeKind := f.ti.elem.Kind()
+
+	rtval := f.ti.elem
+	rtvalkind := vtypeKind
+	for rtvalkind == reflect.Ptr {
 		rtval = rtval.Elem()
+		rtvalkind = rtval.Kind()
 	}
-	if rtval.Kind() != reflect.Interface {
+	if rtvalkind != reflect.Interface {
 		valFn = e.h.fn(rtval)
 	}
 
+	var rvv = mapAddressableRV(f.ti.elem, vtypeKind)
+
 	if e.h.Canonical {
-		e.kMapCanonical(f.ti.key, f.ti.elem, rv, valFn)
+		e.kMapCanonical(f.ti.key, f.ti.elem, rv, rvv, valFn)
 		e.mapEnd()
 		return
 	}
@@ -589,11 +598,9 @@ func (e *Encoder) kMap(f *codecFnInfo, rv reflect.Value) {
 		}
 	}
 
-	var rvk = mapAddressableRV(f.ti.key)  //, f.ti.key.Kind())
-	var rvv = mapAddressableRV(f.ti.elem) //, f.ti.elem.Kind())
+	var rvk = mapAddressableRV(f.ti.key, ktypeKind)
 
 	it := mapRange(rv, rvk, rvv, true)
-
 	for it.Next() {
 		e.mapElemKey()
 		if keyTypeIsString {
@@ -603,22 +610,21 @@ func (e *Encoder) kMap(f *codecFnInfo, rv reflect.Value) {
 				e.e.EncodeStringEnc(cUTF8, it.Key().String())
 			}
 		} else {
-			e.encodeValue(it.Key(), keyFn) //
+			e.encodeValue(it.Key(), keyFn)
 		}
 		e.mapElemValue()
 		iv := it.Value()
 		e.encodeValue(iv, valFn)
 	}
+	it.Done()
 
 	e.mapEnd()
 }
 
-func (e *Encoder) kMapCanonical(rtkey, rtval reflect.Type, rv reflect.Value, valFn *codecFn) {
+func (e *Encoder) kMapCanonical(rtkey, rtval reflect.Type, rv, rvv reflect.Value, valFn *codecFn) {
 	// we previously did out-of-band if an extension was registered.
 	// This is not necessary, as the natural kind is sufficient for ordering.
 
-	rvv := mapAddressableRV(rtval)
-
 	mks := rv.MapKeys()
 	switch rtkey.Kind() {
 	case reflect.Bool:

+ 2 - 0
codec/goversion_maprange_gte_go112.go

@@ -29,6 +29,8 @@ func (t *mapIter) Value() (r reflect.Value) {
 	return
 }
 
+func (t *mapIter) Done() {}
+
 func mapRange(m, k, v reflect.Value, values bool) *mapIter {
 	return &mapIter{
 		m:      m,

+ 2 - 0
codec/goversion_maprange_lt_go112.go

@@ -31,6 +31,8 @@ func (t *mapIter) Value() (r reflect.Value) {
 	return
 }
 
+func (t *mapIter) Done() {}
+
 func mapRange(m, k, v reflect.Value, values bool) *mapIter {
 	return &mapIter{
 		m:      m,

+ 30 - 13
codec/helper.go

@@ -155,17 +155,24 @@ const (
 	xdebug = true
 )
 
-var oneByteArr [1]byte
-var zeroByteSlice = oneByteArr[:0:0]
+var (
+	oneByteArr    [1]byte
+	zeroByteSlice = oneByteArr[:0:0]
 
-var codecgen bool
+	codecgen bool
 
-var pool pooler
-var panicv panicHdl
+	pool   pooler
+	panicv panicHdl
 
-var refBitset bitset32
-var isnilBitset bitset32
-var scalarBitset bitset32
+	refBitset    bitset32
+	isnilBitset  bitset32
+	scalarBitset bitset32
+)
+
+var (
+	errMapTypeNotMapKind     = errors.New("MapType MUST be of Map Kind")
+	errSliceTypeNotSliceKind = errors.New("SliceType MUST be of Slice Kind")
+)
 
 func init() {
 	pool.init()
@@ -695,6 +702,13 @@ func (x *BasicHandle) init(hh Handle) {
 		// _, x.js = hh.(*JsonHandle)
 		// x.n = hh.Name()[0]
 		atomic.StoreUint32(&x.inited, uint32(f))
+		// ensure MapType and SliceType are of correct type
+		if x.MapType != nil && x.MapType.Kind() != reflect.Map {
+			panic(errMapTypeNotMapKind)
+		}
+		if x.SliceType != nil && x.SliceType.Kind() != reflect.Slice {
+			panic(errSliceTypeNotSliceKind)
+		}
 	}
 	x.mu.Unlock()
 }
@@ -854,16 +868,18 @@ func (x *BasicHandle) fnLoad(rt reflect.Type, rtid uintptr, checkExt bool) (fn *
 					xfnf := fastpathAV[idx].encfn
 					xrt := fastpathAV[idx].rt
 					fn.fe = func(e *Encoder, xf *codecFnInfo, xrv reflect.Value) {
-						xfnf(e, xf, xrv.Convert(xrt))
+						xfnf(e, xf, rvconvert(xrv, xrt))
 					}
 					fi.addrD = true
 					fi.addrF = false // meaning it can be an address(ptr) or a value
 					xfnf2 := fastpathAV[idx].decfn
+					xptr2rt := reflect.PtrTo(xrt)
 					fn.fd = func(d *Decoder, xf *codecFnInfo, xrv reflect.Value) {
+						// xdebug2f("fd: convert from %v to %v", xrv.Type(), xrt)
 						if xrv.Kind() == reflect.Ptr {
-							xfnf2(d, xf, xrv.Convert(reflect.PtrTo(xrt)))
+							xfnf2(d, xf, rvconvert(xrv, xptr2rt))
 						} else {
-							xfnf2(d, xf, xrv.Convert(xrt))
+							xfnf2(d, xf, rvconvert(xrv, xrt))
 						}
 					}
 				}
@@ -1307,10 +1323,11 @@ func (o intf2impls) intf2impl(rtid uintptr) (rv reflect.Value) {
 			if v.impl == nil {
 				return
 			}
-			if v.impl.Kind() == reflect.Ptr {
+			vkind := v.impl.Kind()
+			if vkind == reflect.Ptr {
 				return reflect.New(v.impl.Elem())
 			}
-			return reflect.New(v.impl).Elem()
+			return rvzeroaddrk(v.impl, vkind)
 		}
 	}
 	return

+ 13 - 1
codec/helper_not_unsafe.go

@@ -56,6 +56,18 @@ func rvssetlen(rv reflect.Value, length int) {
 	rv.SetLen(length)
 }
 
+// func rvzeroaddr(t reflect.Type) reflect.Value {
+// 	return reflect.New(t).Elem()
+// }
+
+func rvzeroaddrk(t reflect.Type, k reflect.Kind) reflect.Value {
+	return reflect.New(t).Elem()
+}
+
+func rvconvert(v reflect.Value, t reflect.Type) (rv reflect.Value) {
+	return v.Convert(t)
+}
+
 // func rvisnilref(rv reflect.Value) bool {
 // 	return rv.IsNil()
 // }
@@ -349,6 +361,6 @@ func mapDelete(m, k reflect.Value) {
 // return an addressable reflect value that can be used in mapRange and mapGet operations.
 //
 // all calls to mapGet or mapRange will call here to get an addressable reflect.Value.
-func mapAddressableRV(t reflect.Type) (r reflect.Value) {
+func mapAddressableRV(t reflect.Type, k reflect.Kind) (r reflect.Value) {
 	return // reflect.New(t).Elem()
 }

+ 67 - 11
codec/helper_unsafe.go

@@ -9,6 +9,7 @@ package codec
 
 import (
 	"reflect"
+	"sync"
 	"sync/atomic"
 	"time"
 	"unsafe"
@@ -34,6 +35,7 @@ const safeMode = false
 // keep in sync with GO_ROOT/src/reflect/value.go
 const (
 	unsafeFlagIndir    = 1 << 7
+	unsafeFlagAddr     = 1 << 8
 	unsafeFlagKindMask = (1 << 5) - 1 // 5 bits for 27 kinds (up to 31)
 	// unsafeTypeKindDirectIface = 1 << 5
 )
@@ -92,7 +94,7 @@ func bytesView(v string) []byte {
 // }
 
 func isNil(v interface{}) (rv reflect.Value, isnil bool) {
-	var ui *unsafeIntf = (*unsafeIntf)(unsafe.Pointer(&v))
+	var ui = (*unsafeIntf)(unsafe.Pointer(&v))
 	if ui.word == nil {
 		isnil = true
 		return
@@ -139,6 +141,33 @@ func rvssetlen(rv reflect.Value, length int) {
 	(*unsafeString)(urv.ptr).Len = length
 }
 
+// func rvzeroaddr(t reflect.Type) (rv reflect.Value) {
+// 	// return reflect.New(t).Elem()
+// 	var ui = (*unsafeIntf)(unsafe.Pointer(&t))
+// 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
+// 	urv.typ = ui.word
+// 	urv.flag = uintptr(t.Kind()) | unsafeFlagIndir | unsafeFlagAddr
+// 	urv.ptr = unsafe_New(ui.word)
+// 	return
+// }
+
+func rvzeroaddrk(t reflect.Type, k reflect.Kind) (rv reflect.Value) {
+	// return reflect.New(t).Elem()
+	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
+	urv.flag = uintptr(k) | unsafeFlagIndir | unsafeFlagAddr
+	urv.typ = ((*unsafeIntf)(unsafe.Pointer(&t))).word
+	urv.ptr = unsafe_New(urv.typ)
+	return
+}
+
+func rvconvert(v reflect.Value, t reflect.Type) (rv reflect.Value) {
+	uv := (*unsafeReflectValue)(unsafe.Pointer(&v))
+	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
+	*urv = *uv
+	urv.typ = ((*unsafeIntf)(unsafe.Pointer(&t))).word
+	return
+}
+
 // func rvisnilref(rv reflect.Value) bool {
 // 	return (*unsafeReflectValue)(unsafe.Pointer(&rv)).ptr == nil
 // }
@@ -583,8 +612,18 @@ type unsafeMapIter struct {
 	// _ [2]uint64 // padding (cache-aligned)
 }
 
+// pprof show that 13% of cbor encode time taken in
+// allocation of unsafeMapIter.
+// Options are to try to alloc on stack, or pool it.
+// Easiest to pool it.
+const unsafeMapIterUsePool = true
+
+var unsafeMapIterPool = sync.Pool{
+	New: func() interface{} { return new(unsafeMapIter) },
+}
+
 func (t *unsafeMapIter) Next() (r bool) {
-	if t.done {
+	if t == nil || t.done {
 		return
 	}
 	if t.it == nil {
@@ -614,6 +653,13 @@ func (t *unsafeMapIter) Value() (r reflect.Value) {
 	return
 }
 
+func (t *unsafeMapIter) Done() {
+	if unsafeMapIterUsePool && t != nil {
+		*t = unsafeMapIter{}
+		unsafeMapIterPool.Put(t)
+	}
+}
+
 func unsafeSet(p, ptyp, p2 unsafe.Pointer, isref bool) {
 	if isref {
 		*(*unsafe.Pointer)(p) = *(*unsafe.Pointer)(p2) // p2
@@ -629,14 +675,19 @@ func unsafeMapKVPtr(urv *unsafeReflectValue) unsafe.Pointer {
 	return urv.ptr
 }
 
-func mapRange(m, k, v reflect.Value, mapvalues bool) *unsafeMapIter {
-	if m.IsNil() {
-		return &unsafeMapIter{done: true}
+func mapRange(m, k, v reflect.Value, mapvalues bool) (t *unsafeMapIter) {
+	if rvisnil(m) {
+		// return &unsafeMapIter{done: true}
+		return
 	}
-	t := &unsafeMapIter{
-		k: k, v: v,
-		mapvalues: mapvalues,
+	if unsafeMapIterUsePool {
+		t = unsafeMapIterPool.Get().(*unsafeMapIter)
+	} else {
+		t = new(unsafeMapIter)
 	}
+	t.k = k
+	t.v = v
+	t.mapvalues = mapvalues
 
 	var urv *unsafeReflectValue
 
@@ -656,7 +707,7 @@ func mapRange(m, k, v reflect.Value, mapvalues bool) *unsafeMapIter {
 		t.visref = refBitset.isset(byte(v.Kind()))
 	}
 
-	return t
+	return
 }
 
 func mapGet(m, k, v reflect.Value) (vv reflect.Value) {
@@ -696,8 +747,9 @@ func mapDelete(m, k reflect.Value) {
 // return an addressable reflect value that can be used in mapRange and mapGet operations.
 //
 // all calls to mapGet or mapRange will call here to get an addressable reflect.Value.
-func mapAddressableRV(t reflect.Type) (r reflect.Value) {
-	return reflect.New(t).Elem()
+func mapAddressableRV(t reflect.Type, k reflect.Kind) (r reflect.Value) {
+	// return reflect.New(t).Elem()
+	return rvzeroaddrk(t, k)
 }
 
 //go:linkname mapiterinit reflect.mapiterinit
@@ -723,3 +775,7 @@ func mapdelete(typ unsafe.Pointer, m unsafe.Pointer, key unsafe.Pointer)
 //go:linkname typedmemmove reflect.typedmemmove
 //go:noescape
 func typedmemmove(typ unsafe.Pointer, dst, src unsafe.Pointer)
+
+//go:linkname unsafe_New reflect.unsafe_New
+//go:noescape
+func unsafe_New(typ unsafe.Pointer) unsafe.Pointer