Browse Source

codec: re-arrange Encoder and Decoder structs for better cache coherency

Also, cache the value of hasMoreElements() in (De|En)coder
as it never changes during the lifetime of the (En|De)coder.
Ugorji Nwoke 8 years ago
parent
commit
710e35eef1
2 changed files with 50 additions and 37 deletions
  1. 30 26
      codec/decode.go
  2. 20 11
      codec/encode.go

+ 30 - 26
codec/decode.go

@@ -127,8 +127,8 @@ func (x decDriverNoopContainerReader) CheckBreak() (v bool)    { return }
 // DecodeOptions captures configuration options during decode.
 type DecodeOptions struct {
 	// MapType specifies type to use during schema-less decoding of a map in the stream.
-	// If nil (unset), we default to map[string]interface{} for json and
-	// map[interface{}]interface{} for all other formats.
+	// If nil (unset), we default to map[string]interface{} iff json handle and MapStringAsKey=true,
+	// else map[interface{}]interface{}.
 	MapType reflect.Type
 
 	// SliceType specifies type to use during schema-less decoding of an array in the stream.
@@ -954,7 +954,7 @@ func (d *Decoder) kInterfaceNaked(f *codecFnInfo) (rvn reflect.Value) {
 		// if json, default to a map type with string keys
 		mtid := d.mtid
 		if mtid == 0 {
-			if d.js {
+			if d.jsms {
 				mtid = mapStrIntfTypId
 			} else {
 				mtid = mapIntfIntfTypId
@@ -1128,7 +1128,7 @@ func (d *Decoder) kInterface(f *codecFnInfo, rv reflect.Value) {
 func (d *Decoder) kStruct(f *codecFnInfo, rv reflect.Value) {
 	fti := f.ti
 	dd := d.d
-	elemsep := d.hh.hasElemSeparators()
+	elemsep := d.esep
 	sfn := structFieldNode{v: rv, update: true}
 	ctyp := dd.ContainerType()
 	if ctyp == valueTypeMap {
@@ -1428,7 +1428,7 @@ func (d *Decoder) kSlice(f *codecFnInfo, rv reflect.Value) {
 func (d *Decoder) kMap(f *codecFnInfo, rv reflect.Value) {
 	dd := d.d
 	containerLen := dd.ReadMapStart()
-	elemsep := d.hh.hasElemSeparators()
+	elemsep := d.esep
 	ti := f.ti
 	if rv.IsNil() {
 		rv.Set(makeMapReflect(ti.rt, containerLen))
@@ -1699,33 +1699,34 @@ type Decoder struct {
 	be    bool // is binary encoding
 	bytes bool // is bytes reader
 	js    bool // is json handle
+	jsms  bool // is json handle, and MapKeyAsString
+	esep  bool // has elem separators
 
 	// ---- cpu cache line boundary?
-
 	rb bytesDecReader
+	// ---- cpu cache line boundary?
 	ri ioDecReader
+	// ---- cpu cache line boundary?
 	bi bufioDecReader
-
-	// cr containerStateRecv
-
-	n   *decNaked
-	nsp *sync.Pool
-
 	// ---- cpu cache line boundary?
-
+	cf codecFner
+	// ---- cpu cache line boundary?
+	// cr containerStateRecv
 	is map[string]string // used for interning strings
-
 	// cache the mapTypeId and sliceTypeId for faster comparisons
 	mtid uintptr
 	stid uintptr
-
-	b [scratchByteArrayLen]byte
 	// _  uintptr // for alignment purposes, so next one starts from a cache line
 
+	// ---- writable fields during execution --- *try* to keep in sep cache line
+
+	// ---- cpu cache line boundary?
+	b   [scratchByteArrayLen]byte
+	n   *decNaked
+	nsp *sync.Pool
 	err error
 	// ---- cpu cache line boundary?
 
-	cf codecFner
 	// _ [64]byte // force alignment???
 }
 
@@ -1753,8 +1754,12 @@ func newDecoder(h Handle) *Decoder {
 	d := &Decoder{hh: h, h: h.getBasicHandle(), be: h.isBinary()}
 
 	// NOTE: do not initialize d.n here. It is lazily initialized in d.naked()
-
-	_, d.js = h.(*JsonHandle)
+	var jh *JsonHandle
+	jh, d.js = h.(*JsonHandle)
+	if d.js {
+		d.jsms = jh.MapKeyAsString
+	}
+	d.esep = d.hh.hasElemSeparators()
 	if d.h.InternString {
 		d.is = make(map[string]string, 32)
 	}
@@ -1895,14 +1900,13 @@ func (d *Decoder) MustDecode(v interface{}) {
 	} else {
 		d.decode(v)
 	}
-	if d.nsp != nil {
-		if d.n != nil {
-			d.nsp.Put(d.n)
-			d.n = nil
-		}
+	if d.n != nil {
+		// if d.n != nil { }
+		// if nsp != nil, then n != nil (they are always set together)
+		d.nsp.Put(d.n)
+		d.n = nil
 		d.nsp = nil
 	}
-	d.n = nil
 	// xprintf(">>>>>>>> >>>>>>>> num decFns: %v\n", d.cf.sn)
 }
 
@@ -1918,7 +1922,7 @@ func (d *Decoder) swallow() {
 	if dd.TryDecodeAsNil() {
 		return
 	}
-	elemsep := d.hh.hasElemSeparators()
+	elemsep := d.esep
 	switch dd.ContainerType() {
 	case valueTypeMap:
 		containerLen := dd.ReadMapStart()

+ 20 - 11
codec/encode.go

@@ -434,7 +434,7 @@ func (e *Encoder) kSlice(f *codecFnInfo, rv reflect.Value) {
 	if f.seq == seqTypeChan && ti.rt.ChanDir()&reflect.RecvDir == 0 {
 		e.errorf("send-only channel cannot be used for receiving byte(s)")
 	}
-	elemsep := e.hh.hasElemSeparators()
+	elemsep := e.esep
 	l := rv.Len()
 	rtelem := ti.rt.Elem()
 	rtelemIsByte := uint8TypId == rt2id(rtelem) // NOT rtelem.Kind() == reflect.Uint8
@@ -530,7 +530,7 @@ func (e *Encoder) kSlice(f *codecFnInfo, rv reflect.Value) {
 
 func (e *Encoder) kStructNoOmitempty(f *codecFnInfo, rv reflect.Value) {
 	fti := f.ti
-	elemsep := e.hh.hasElemSeparators()
+	elemsep := e.esep
 	tisfi := fti.sfip
 	toMap := !(fti.toArray || e.h.StructToArray)
 	if toMap {
@@ -583,7 +583,7 @@ func (e *Encoder) kStructNoOmitempty(f *codecFnInfo, rv reflect.Value) {
 
 func (e *Encoder) kStruct(f *codecFnInfo, rv reflect.Value) {
 	fti := f.ti
-	elemsep := e.hh.hasElemSeparators()
+	elemsep := e.esep
 	tisfi := fti.sfip
 	toMap := !(fti.toArray || e.h.StructToArray)
 	// if toMap, use the sorted array. If toArray, use unsorted array (to match sequence in struct)
@@ -713,7 +713,7 @@ func (e *Encoder) kMap(f *codecFnInfo, rv reflect.Value) {
 
 	l := rv.Len()
 	ee.WriteMapStart(l)
-	elemsep := e.hh.hasElemSeparators()
+	elemsep := e.esep
 	if l == 0 {
 		ee.WriteMapEnd()
 		return
@@ -785,7 +785,7 @@ func (e *Encoder) kMap(f *codecFnInfo, rv reflect.Value) {
 
 func (e *Encoder) kMapCanonical(rtkey reflect.Type, rv reflect.Value, mks []reflect.Value, valFn *codecFn, asSymbols bool) {
 	ee := e.e
-	elemsep := e.hh.hasElemSeparators()
+	elemsep := e.esep
 	// we previously did out-of-band if an extension was registered.
 	// This is not necessary, as the natural kind is sufficient for ordering.
 
@@ -986,21 +986,29 @@ type Encoder struct {
 	hh Handle
 	h  *BasicHandle
 
+	esep bool // whether it has elem separators
+
 	// ---- cpu cache line boundary?
+	// cr containerStateRecv
+	as encDriverAsis
+	ci set
 
+	// ---- cpu cache line boundary?
 	wi ioEncWriter
+	// ---- cpu cache line boundary?
 	wb bytesEncWriter
+
+	// ---- cpu cache line boundary?
 	bw bufio.Writer
 
-	// cr containerStateRecv
-	as encDriverAsis
 	// ---- cpu cache line boundary?
+	cf codecFner
 
-	ci  set
-	err error
+	// ---- writable fields during execution --- *try* to keep in sep cache line
 
-	b  [scratchByteArrayLen]byte
-	cf codecFner
+	// ---- cpu cache line boundary?
+	b   [scratchByteArrayLen]byte
+	err error
 }
 
 // NewEncoder returns an Encoder for encoding into an io.Writer.
@@ -1028,6 +1036,7 @@ func newEncoder(h Handle) *Encoder {
 	e := &Encoder{hh: h, h: h.getBasicHandle()}
 	e.e = h.newEncDriver(e)
 	e.as, _ = e.e.(encDriverAsis)
+	e.esep = e.hh.hasElemSeparators()
 	// e.cr, _ = e.e.(containerStateRecv)
 	return e
 }