Explorar el Código

codec: improve MapBySlice performance, and integrate tests for it.

Previously, fastpath didn't handle mapbyslice, and so we only handled
mapbyslice via reflection, even if the underlying type
is one of the supported fastpath slice types.

Now, fastpath handles mapbyslice using a dedicated method, so it can be handled performantly.

Tests for MapBySlice are also integrated.

Other Misc Changes:
- prebuild.sh: document that -x is also accepted as a parameter.
- doc: update planned support for non-concurrent callback (ie using a callback instead of a channel)

Update #124
Ugorji Nwoke hace 10 años
padre
commit
d517a49d59
Se han modificado 8 ficheros con 576 adiciones y 44 borrados
  1. 1 1
      codec/0doc.go
  2. 1 1
      codec/cbor.go
  3. 49 17
      codec/codec_test.go
  4. 2 2
      codec/encode.go
  5. 480 15
      codec/fast-path.generated.go
  6. 30 1
      codec/fast-path.go.tmpl
  7. 1 1
      codec/prebuild.sh
  8. 12 6
      codec/test.py

+ 1 - 1
codec/0doc.go

@@ -186,7 +186,7 @@ package codec
 //             Name string
 //             Ys []Y
 //             Ys chan <- Y
-//             Ys func(interface{}) -> call this interface for each entry in there.
+//             Ys func(Y) -> call this function for each entry
 //        }
 //    - Consider adding a isZeroer interface { isZero() bool }
 //      It is used within isEmpty, for omitEmpty support.

+ 1 - 1
codec/cbor.go

@@ -508,7 +508,7 @@ func (d *cborDecDriver) DecodeNaked() {
 			n.v = valueTypeExt
 			n.u = d.decUint()
 			n.l = nil
-			d.bdRead = false
+			// d.bdRead = false
 			// d.d.decode(&re.Value) // handled by decode itself.
 			// decodeFurther = true
 		default:

+ 49 - 17
codec/codec_test.go

@@ -48,6 +48,11 @@ func init() {
 	testPreInitFns = append(testPreInitFns, testInit)
 }
 
+// make this a mapbyslice
+type testMbsT []interface{}
+
+func (_ testMbsT) MapBySlice() {}
+
 type testVerifyArg int
 
 const (
@@ -60,6 +65,12 @@ const (
 
 const testSkipRPCTests = false
 
+var (
+	testTableNumPrimitives int
+	testTableIdxTime       int
+	testTableNumMaps       int
+)
+
 var (
 	testVerbose        bool
 	testInitDebug      bool
@@ -261,6 +272,12 @@ func testVerifyVal(v interface{}, arg testVerifyArg) (v2 interface{}) {
 			m2[j] = testVerifyVal(vj, arg)
 		}
 		v2 = m2
+	case testMbsT:
+		m2 := make([]interface{}, len(iv))
+		for j, vj := range iv {
+			m2[j] = testVerifyVal(vj, arg)
+		}
+		v2 = testMbsT(m2)
 	case map[string]bool:
 		switch arg {
 		case testVerifyMapTypeSame:
@@ -362,6 +379,7 @@ func testInit() {
 	testCborH.SetInterfaceExt(timeTyp, 1, &testUnixNanoTimeExt{})
 	// testJsonH.SetInterfaceExt(timeTyp, 1, &testUnixNanoTimeExt{})
 
+	// primitives MUST be an even number, so it can be used as a mapBySlice also.
 	primitives := []interface{}{
 		int8(-8),
 		int16(-1616),
@@ -375,19 +393,23 @@ func testInit() {
 		float32(-3232.0),
 		float64(-6464646464.0),
 		float32(3232.0),
+		float64(6464.0),
 		float64(6464646464.0),
 		false,
 		true,
+		"null",
 		nil,
 		"someday",
-		"",
-		"bytestring",
 		timeToCompare1,
+		"",
 		timeToCompare2,
+		"bytestring",
 		timeToCompare3,
+		"none",
 		timeToCompare4,
 	}
-	mapsAndStrucs := []interface{}{
+
+	maps := []interface{}{
 		map[string]bool{
 			"true":  true,
 			"false": false,
@@ -421,22 +443,27 @@ func testInit() {
 			uint8(138): false,
 			"false":    uint8(200),
 		},
-		newTestStruc(0, false, !testSkipIntf, false),
 	}
 
+	testTableNumPrimitives = len(primitives)
+	testTableIdxTime = testTableNumPrimitives - 8
+	testTableNumMaps = len(maps)
+
 	table = []interface{}{}
-	table = append(table, primitives...)    //0-19 are primitives
-	table = append(table, primitives)       //20 is a list of primitives
-	table = append(table, mapsAndStrucs...) //21-24 are maps. 25 is a *struct
+	table = append(table, primitives...)
+	table = append(table, primitives)
+	table = append(table, testMbsT(primitives))
+	table = append(table, maps...)
+	table = append(table, newTestStruc(0, false, !testSkipIntf, false))
 
 	tableVerify = make([]interface{}, len(table))
 	tableTestNilVerify = make([]interface{}, len(table))
 	tablePythonVerify = make([]interface{}, len(table))
 
-	lp := len(primitives)
+	lp := testTableNumPrimitives + 4
 	av := tableVerify
 	for i, v := range table {
-		if i == lp+3 {
+		if i == lp {
 			av[i] = skipVerifyVal
 			continue
 		}
@@ -444,6 +471,8 @@ func testInit() {
 		switch v.(type) {
 		case []interface{}:
 			av[i] = testVerifyVal(v, testVerifyMapTypeSame)
+		case testMbsT:
+			av[i] = testVerifyVal(v, testVerifyMapTypeSame)
 		case map[string]interface{}:
 			av[i] = testVerifyVal(v, testVerifyMapTypeSame)
 		case map[interface{}]interface{}:
@@ -455,7 +484,7 @@ func testInit() {
 
 	av = tableTestNilVerify
 	for i, v := range table {
-		if i > lp+3 {
+		if i > lp {
 			av[i] = skipVerifyVal
 			continue
 		}
@@ -464,14 +493,15 @@ func testInit() {
 
 	av = tablePythonVerify
 	for i, v := range table {
-		if i > lp+3 {
+		if i == testTableNumPrimitives+1 || i > lp { // testTableNumPrimitives+1 is the mapBySlice
 			av[i] = skipVerifyVal
 			continue
 		}
 		av[i] = testVerifyVal(v, testVerifyForPython)
 	}
 
-	tablePythonVerify = tablePythonVerify[:24]
+	// only do the python verify up to the maps, skipping the last 2 maps.
+	tablePythonVerify = tablePythonVerify[:testTableNumPrimitives+2+testTableNumMaps-2]
 }
 
 func testUnmarshal(v interface{}, data []byte, h Handle) (err error) {
@@ -566,7 +596,8 @@ func testCodecTableOne(t *testing.T, h Handle) {
 	// func TestMsgpackAllExperimental(t *testing.T) {
 	// dopts := testDecOpts(nil, nil, false, true, true),
 
-	idxTime, numPrim, numMap := 19, 23, 4
+	numPrim, numMap, idxTime, idxMap := testTableNumPrimitives, testTableNumMaps, testTableIdxTime, testTableNumPrimitives+2
+
 	//println("#################")
 	switch v := h.(type) {
 	case *MsgpackHandle:
@@ -579,7 +610,7 @@ func testCodecTableOne(t *testing.T, h Handle) {
 		//skip []interface{} containing time.Time, as it encodes as a number, but cannot decode back to time.Time.
 		//As there is no real support for extension tags in json, this must be skipped.
 		doTestCodecTableOne(t, false, h, table[:numPrim], tableVerify[:numPrim])
-		doTestCodecTableOne(t, false, h, table[numPrim+1:], tableVerify[numPrim+1:])
+		doTestCodecTableOne(t, false, h, table[idxMap:], tableVerify[idxMap:])
 	default:
 		doTestCodecTableOne(t, false, h, table, tableVerify)
 	}
@@ -596,14 +627,15 @@ func testCodecTableOne(t *testing.T, h Handle) {
 
 	//skip time.Time, []interface{} containing time.Time, last map, and newStruc
 	doTestCodecTableOne(t, true, h, table[:idxTime], tableTestNilVerify[:idxTime])
-	doTestCodecTableOne(t, true, h, table[numPrim+1:numPrim+numMap], tableTestNilVerify[numPrim+1:numPrim+numMap])
+	doTestCodecTableOne(t, true, h, table[idxMap:idxMap+numMap-1], tableTestNilVerify[idxMap:idxMap+numMap-1])
 
 	v.MapType = oldMapType
 
 	// func TestMsgpackNilIntf(t *testing.T) {
 
-	//do newTestStruc and last element of map
-	doTestCodecTableOne(t, true, h, table[numPrim+numMap:], tableTestNilVerify[numPrim+numMap:])
+	//do last map and newStruc
+	idx2 := idxMap + numMap - 1
+	doTestCodecTableOne(t, true, h, table[idx2:], tableTestNilVerify[idx2:])
 	//TODO? What is this one?
 	//doTestCodecTableOne(t, true, h, table[17:18], tableTestNilVerify[17:18])
 }

+ 2 - 2
codec/encode.go

@@ -1188,7 +1188,8 @@ func (e *Encoder) doEncodeValue(rv reflect.Value, fn *encFn, sptr uintptr,
 	if fn == nil {
 		rt := rv.Type()
 		rtid := reflect.ValueOf(rt).Pointer()
-		fn = e.getEncFn(rtid, rt, true, true)
+		// fn = e.getEncFn(rtid, rt, true, true)
+		fn = e.getEncFn(rtid, rt, checkFastpath, checkCodecSelfer)
 	}
 	fn.f(&fn.i, rv)
 	if sptr != 0 {
@@ -1269,7 +1270,6 @@ func (e *Encoder) getEncFn(rtid uintptr, rt reflect.Type, checkFastpath, checkCo
 				if idx := fastpathAV.index(rtid); idx != -1 {
 					fn.f = fastpathAV[idx].encfn
 				}
-			} else if ti.mbs { // map by slice. Do not use underlying type AS-IS.
 			} else {
 				ok = false
 				// use mapping for underlying type if there

+ 480 - 15
codec/fast-path.generated.go

@@ -3124,7 +3124,11 @@ func fastpathEncodeTypeSwitchMap(iv interface{}, e *Encoder) bool {
 // -- -- fast path functions
 
 func (f *encFnInfo) fastpathEncSliceIntfR(rv reflect.Value) {
-	fastpathTV.EncSliceIntfV(rv.Interface().([]interface{}), fastpathCheckNilFalse, f.e)
+	if f.ti.mbs {
+		fastpathTV.EncAsMapSliceIntfV(rv.Interface().([]interface{}), fastpathCheckNilFalse, f.e)
+	} else {
+		fastpathTV.EncSliceIntfV(rv.Interface().([]interface{}), fastpathCheckNilFalse, f.e)
+	}
 }
 func (_ fastpathT) EncSliceIntfV(v []interface{}, checkNil bool, e *Encoder) {
 	ee := e.e
@@ -3145,8 +3149,39 @@ func (_ fastpathT) EncSliceIntfV(v []interface{}, checkNil bool, e *Encoder) {
 	}
 }
 
+func (_ fastpathT) EncAsMapSliceIntfV(v []interface{}, checkNil bool, e *Encoder) {
+	ee := e.e
+	cr := e.cr
+	if checkNil && v == nil {
+		ee.EncodeNil()
+		return
+	}
+	if len(v)%2 == 1 {
+		e.errorf("mapBySlice requires even slice length, but got %v", len(v))
+		return
+	}
+	ee.EncodeMapStart(len(v) / 2)
+	for j, v2 := range v {
+		if cr != nil {
+			if j%2 == 0 {
+				cr.sendContainerState(containerMapKey)
+			} else {
+				cr.sendContainerState(containerMapValue)
+			}
+		}
+		e.encode(v2)
+	}
+	if cr != nil {
+		cr.sendContainerState(containerMapEnd)
+	}
+}
+
 func (f *encFnInfo) fastpathEncSliceStringR(rv reflect.Value) {
-	fastpathTV.EncSliceStringV(rv.Interface().([]string), fastpathCheckNilFalse, f.e)
+	if f.ti.mbs {
+		fastpathTV.EncAsMapSliceStringV(rv.Interface().([]string), fastpathCheckNilFalse, f.e)
+	} else {
+		fastpathTV.EncSliceStringV(rv.Interface().([]string), fastpathCheckNilFalse, f.e)
+	}
 }
 func (_ fastpathT) EncSliceStringV(v []string, checkNil bool, e *Encoder) {
 	ee := e.e
@@ -3167,8 +3202,39 @@ func (_ fastpathT) EncSliceStringV(v []string, checkNil bool, e *Encoder) {
 	}
 }
 
+func (_ fastpathT) EncAsMapSliceStringV(v []string, checkNil bool, e *Encoder) {
+	ee := e.e
+	cr := e.cr
+	if checkNil && v == nil {
+		ee.EncodeNil()
+		return
+	}
+	if len(v)%2 == 1 {
+		e.errorf("mapBySlice requires even slice length, but got %v", len(v))
+		return
+	}
+	ee.EncodeMapStart(len(v) / 2)
+	for j, v2 := range v {
+		if cr != nil {
+			if j%2 == 0 {
+				cr.sendContainerState(containerMapKey)
+			} else {
+				cr.sendContainerState(containerMapValue)
+			}
+		}
+		ee.EncodeString(c_UTF8, v2)
+	}
+	if cr != nil {
+		cr.sendContainerState(containerMapEnd)
+	}
+}
+
 func (f *encFnInfo) fastpathEncSliceFloat32R(rv reflect.Value) {
-	fastpathTV.EncSliceFloat32V(rv.Interface().([]float32), fastpathCheckNilFalse, f.e)
+	if f.ti.mbs {
+		fastpathTV.EncAsMapSliceFloat32V(rv.Interface().([]float32), fastpathCheckNilFalse, f.e)
+	} else {
+		fastpathTV.EncSliceFloat32V(rv.Interface().([]float32), fastpathCheckNilFalse, f.e)
+	}
 }
 func (_ fastpathT) EncSliceFloat32V(v []float32, checkNil bool, e *Encoder) {
 	ee := e.e
@@ -3189,8 +3255,39 @@ func (_ fastpathT) EncSliceFloat32V(v []float32, checkNil bool, e *Encoder) {
 	}
 }
 
+func (_ fastpathT) EncAsMapSliceFloat32V(v []float32, checkNil bool, e *Encoder) {
+	ee := e.e
+	cr := e.cr
+	if checkNil && v == nil {
+		ee.EncodeNil()
+		return
+	}
+	if len(v)%2 == 1 {
+		e.errorf("mapBySlice requires even slice length, but got %v", len(v))
+		return
+	}
+	ee.EncodeMapStart(len(v) / 2)
+	for j, v2 := range v {
+		if cr != nil {
+			if j%2 == 0 {
+				cr.sendContainerState(containerMapKey)
+			} else {
+				cr.sendContainerState(containerMapValue)
+			}
+		}
+		ee.EncodeFloat32(v2)
+	}
+	if cr != nil {
+		cr.sendContainerState(containerMapEnd)
+	}
+}
+
 func (f *encFnInfo) fastpathEncSliceFloat64R(rv reflect.Value) {
-	fastpathTV.EncSliceFloat64V(rv.Interface().([]float64), fastpathCheckNilFalse, f.e)
+	if f.ti.mbs {
+		fastpathTV.EncAsMapSliceFloat64V(rv.Interface().([]float64), fastpathCheckNilFalse, f.e)
+	} else {
+		fastpathTV.EncSliceFloat64V(rv.Interface().([]float64), fastpathCheckNilFalse, f.e)
+	}
 }
 func (_ fastpathT) EncSliceFloat64V(v []float64, checkNil bool, e *Encoder) {
 	ee := e.e
@@ -3211,8 +3308,39 @@ func (_ fastpathT) EncSliceFloat64V(v []float64, checkNil bool, e *Encoder) {
 	}
 }
 
+func (_ fastpathT) EncAsMapSliceFloat64V(v []float64, checkNil bool, e *Encoder) {
+	ee := e.e
+	cr := e.cr
+	if checkNil && v == nil {
+		ee.EncodeNil()
+		return
+	}
+	if len(v)%2 == 1 {
+		e.errorf("mapBySlice requires even slice length, but got %v", len(v))
+		return
+	}
+	ee.EncodeMapStart(len(v) / 2)
+	for j, v2 := range v {
+		if cr != nil {
+			if j%2 == 0 {
+				cr.sendContainerState(containerMapKey)
+			} else {
+				cr.sendContainerState(containerMapValue)
+			}
+		}
+		ee.EncodeFloat64(v2)
+	}
+	if cr != nil {
+		cr.sendContainerState(containerMapEnd)
+	}
+}
+
 func (f *encFnInfo) fastpathEncSliceUintR(rv reflect.Value) {
-	fastpathTV.EncSliceUintV(rv.Interface().([]uint), fastpathCheckNilFalse, f.e)
+	if f.ti.mbs {
+		fastpathTV.EncAsMapSliceUintV(rv.Interface().([]uint), fastpathCheckNilFalse, f.e)
+	} else {
+		fastpathTV.EncSliceUintV(rv.Interface().([]uint), fastpathCheckNilFalse, f.e)
+	}
 }
 func (_ fastpathT) EncSliceUintV(v []uint, checkNil bool, e *Encoder) {
 	ee := e.e
@@ -3233,8 +3361,39 @@ func (_ fastpathT) EncSliceUintV(v []uint, checkNil bool, e *Encoder) {
 	}
 }
 
+func (_ fastpathT) EncAsMapSliceUintV(v []uint, checkNil bool, e *Encoder) {
+	ee := e.e
+	cr := e.cr
+	if checkNil && v == nil {
+		ee.EncodeNil()
+		return
+	}
+	if len(v)%2 == 1 {
+		e.errorf("mapBySlice requires even slice length, but got %v", len(v))
+		return
+	}
+	ee.EncodeMapStart(len(v) / 2)
+	for j, v2 := range v {
+		if cr != nil {
+			if j%2 == 0 {
+				cr.sendContainerState(containerMapKey)
+			} else {
+				cr.sendContainerState(containerMapValue)
+			}
+		}
+		ee.EncodeUint(uint64(v2))
+	}
+	if cr != nil {
+		cr.sendContainerState(containerMapEnd)
+	}
+}
+
 func (f *encFnInfo) fastpathEncSliceUint16R(rv reflect.Value) {
-	fastpathTV.EncSliceUint16V(rv.Interface().([]uint16), fastpathCheckNilFalse, f.e)
+	if f.ti.mbs {
+		fastpathTV.EncAsMapSliceUint16V(rv.Interface().([]uint16), fastpathCheckNilFalse, f.e)
+	} else {
+		fastpathTV.EncSliceUint16V(rv.Interface().([]uint16), fastpathCheckNilFalse, f.e)
+	}
 }
 func (_ fastpathT) EncSliceUint16V(v []uint16, checkNil bool, e *Encoder) {
 	ee := e.e
@@ -3255,8 +3414,39 @@ func (_ fastpathT) EncSliceUint16V(v []uint16, checkNil bool, e *Encoder) {
 	}
 }
 
+func (_ fastpathT) EncAsMapSliceUint16V(v []uint16, checkNil bool, e *Encoder) {
+	ee := e.e
+	cr := e.cr
+	if checkNil && v == nil {
+		ee.EncodeNil()
+		return
+	}
+	if len(v)%2 == 1 {
+		e.errorf("mapBySlice requires even slice length, but got %v", len(v))
+		return
+	}
+	ee.EncodeMapStart(len(v) / 2)
+	for j, v2 := range v {
+		if cr != nil {
+			if j%2 == 0 {
+				cr.sendContainerState(containerMapKey)
+			} else {
+				cr.sendContainerState(containerMapValue)
+			}
+		}
+		ee.EncodeUint(uint64(v2))
+	}
+	if cr != nil {
+		cr.sendContainerState(containerMapEnd)
+	}
+}
+
 func (f *encFnInfo) fastpathEncSliceUint32R(rv reflect.Value) {
-	fastpathTV.EncSliceUint32V(rv.Interface().([]uint32), fastpathCheckNilFalse, f.e)
+	if f.ti.mbs {
+		fastpathTV.EncAsMapSliceUint32V(rv.Interface().([]uint32), fastpathCheckNilFalse, f.e)
+	} else {
+		fastpathTV.EncSliceUint32V(rv.Interface().([]uint32), fastpathCheckNilFalse, f.e)
+	}
 }
 func (_ fastpathT) EncSliceUint32V(v []uint32, checkNil bool, e *Encoder) {
 	ee := e.e
@@ -3277,8 +3467,39 @@ func (_ fastpathT) EncSliceUint32V(v []uint32, checkNil bool, e *Encoder) {
 	}
 }
 
+func (_ fastpathT) EncAsMapSliceUint32V(v []uint32, checkNil bool, e *Encoder) {
+	ee := e.e
+	cr := e.cr
+	if checkNil && v == nil {
+		ee.EncodeNil()
+		return
+	}
+	if len(v)%2 == 1 {
+		e.errorf("mapBySlice requires even slice length, but got %v", len(v))
+		return
+	}
+	ee.EncodeMapStart(len(v) / 2)
+	for j, v2 := range v {
+		if cr != nil {
+			if j%2 == 0 {
+				cr.sendContainerState(containerMapKey)
+			} else {
+				cr.sendContainerState(containerMapValue)
+			}
+		}
+		ee.EncodeUint(uint64(v2))
+	}
+	if cr != nil {
+		cr.sendContainerState(containerMapEnd)
+	}
+}
+
 func (f *encFnInfo) fastpathEncSliceUint64R(rv reflect.Value) {
-	fastpathTV.EncSliceUint64V(rv.Interface().([]uint64), fastpathCheckNilFalse, f.e)
+	if f.ti.mbs {
+		fastpathTV.EncAsMapSliceUint64V(rv.Interface().([]uint64), fastpathCheckNilFalse, f.e)
+	} else {
+		fastpathTV.EncSliceUint64V(rv.Interface().([]uint64), fastpathCheckNilFalse, f.e)
+	}
 }
 func (_ fastpathT) EncSliceUint64V(v []uint64, checkNil bool, e *Encoder) {
 	ee := e.e
@@ -3299,8 +3520,39 @@ func (_ fastpathT) EncSliceUint64V(v []uint64, checkNil bool, e *Encoder) {
 	}
 }
 
+func (_ fastpathT) EncAsMapSliceUint64V(v []uint64, checkNil bool, e *Encoder) {
+	ee := e.e
+	cr := e.cr
+	if checkNil && v == nil {
+		ee.EncodeNil()
+		return
+	}
+	if len(v)%2 == 1 {
+		e.errorf("mapBySlice requires even slice length, but got %v", len(v))
+		return
+	}
+	ee.EncodeMapStart(len(v) / 2)
+	for j, v2 := range v {
+		if cr != nil {
+			if j%2 == 0 {
+				cr.sendContainerState(containerMapKey)
+			} else {
+				cr.sendContainerState(containerMapValue)
+			}
+		}
+		ee.EncodeUint(uint64(v2))
+	}
+	if cr != nil {
+		cr.sendContainerState(containerMapEnd)
+	}
+}
+
 func (f *encFnInfo) fastpathEncSliceUintptrR(rv reflect.Value) {
-	fastpathTV.EncSliceUintptrV(rv.Interface().([]uintptr), fastpathCheckNilFalse, f.e)
+	if f.ti.mbs {
+		fastpathTV.EncAsMapSliceUintptrV(rv.Interface().([]uintptr), fastpathCheckNilFalse, f.e)
+	} else {
+		fastpathTV.EncSliceUintptrV(rv.Interface().([]uintptr), fastpathCheckNilFalse, f.e)
+	}
 }
 func (_ fastpathT) EncSliceUintptrV(v []uintptr, checkNil bool, e *Encoder) {
 	ee := e.e
@@ -3321,8 +3573,39 @@ func (_ fastpathT) EncSliceUintptrV(v []uintptr, checkNil bool, e *Encoder) {
 	}
 }
 
+func (_ fastpathT) EncAsMapSliceUintptrV(v []uintptr, checkNil bool, e *Encoder) {
+	ee := e.e
+	cr := e.cr
+	if checkNil && v == nil {
+		ee.EncodeNil()
+		return
+	}
+	if len(v)%2 == 1 {
+		e.errorf("mapBySlice requires even slice length, but got %v", len(v))
+		return
+	}
+	ee.EncodeMapStart(len(v) / 2)
+	for j, v2 := range v {
+		if cr != nil {
+			if j%2 == 0 {
+				cr.sendContainerState(containerMapKey)
+			} else {
+				cr.sendContainerState(containerMapValue)
+			}
+		}
+		e.encode(v2)
+	}
+	if cr != nil {
+		cr.sendContainerState(containerMapEnd)
+	}
+}
+
 func (f *encFnInfo) fastpathEncSliceIntR(rv reflect.Value) {
-	fastpathTV.EncSliceIntV(rv.Interface().([]int), fastpathCheckNilFalse, f.e)
+	if f.ti.mbs {
+		fastpathTV.EncAsMapSliceIntV(rv.Interface().([]int), fastpathCheckNilFalse, f.e)
+	} else {
+		fastpathTV.EncSliceIntV(rv.Interface().([]int), fastpathCheckNilFalse, f.e)
+	}
 }
 func (_ fastpathT) EncSliceIntV(v []int, checkNil bool, e *Encoder) {
 	ee := e.e
@@ -3343,8 +3626,39 @@ func (_ fastpathT) EncSliceIntV(v []int, checkNil bool, e *Encoder) {
 	}
 }
 
+func (_ fastpathT) EncAsMapSliceIntV(v []int, checkNil bool, e *Encoder) {
+	ee := e.e
+	cr := e.cr
+	if checkNil && v == nil {
+		ee.EncodeNil()
+		return
+	}
+	if len(v)%2 == 1 {
+		e.errorf("mapBySlice requires even slice length, but got %v", len(v))
+		return
+	}
+	ee.EncodeMapStart(len(v) / 2)
+	for j, v2 := range v {
+		if cr != nil {
+			if j%2 == 0 {
+				cr.sendContainerState(containerMapKey)
+			} else {
+				cr.sendContainerState(containerMapValue)
+			}
+		}
+		ee.EncodeInt(int64(v2))
+	}
+	if cr != nil {
+		cr.sendContainerState(containerMapEnd)
+	}
+}
+
 func (f *encFnInfo) fastpathEncSliceInt8R(rv reflect.Value) {
-	fastpathTV.EncSliceInt8V(rv.Interface().([]int8), fastpathCheckNilFalse, f.e)
+	if f.ti.mbs {
+		fastpathTV.EncAsMapSliceInt8V(rv.Interface().([]int8), fastpathCheckNilFalse, f.e)
+	} else {
+		fastpathTV.EncSliceInt8V(rv.Interface().([]int8), fastpathCheckNilFalse, f.e)
+	}
 }
 func (_ fastpathT) EncSliceInt8V(v []int8, checkNil bool, e *Encoder) {
 	ee := e.e
@@ -3365,8 +3679,39 @@ func (_ fastpathT) EncSliceInt8V(v []int8, checkNil bool, e *Encoder) {
 	}
 }
 
+func (_ fastpathT) EncAsMapSliceInt8V(v []int8, checkNil bool, e *Encoder) {
+	ee := e.e
+	cr := e.cr
+	if checkNil && v == nil {
+		ee.EncodeNil()
+		return
+	}
+	if len(v)%2 == 1 {
+		e.errorf("mapBySlice requires even slice length, but got %v", len(v))
+		return
+	}
+	ee.EncodeMapStart(len(v) / 2)
+	for j, v2 := range v {
+		if cr != nil {
+			if j%2 == 0 {
+				cr.sendContainerState(containerMapKey)
+			} else {
+				cr.sendContainerState(containerMapValue)
+			}
+		}
+		ee.EncodeInt(int64(v2))
+	}
+	if cr != nil {
+		cr.sendContainerState(containerMapEnd)
+	}
+}
+
 func (f *encFnInfo) fastpathEncSliceInt16R(rv reflect.Value) {
-	fastpathTV.EncSliceInt16V(rv.Interface().([]int16), fastpathCheckNilFalse, f.e)
+	if f.ti.mbs {
+		fastpathTV.EncAsMapSliceInt16V(rv.Interface().([]int16), fastpathCheckNilFalse, f.e)
+	} else {
+		fastpathTV.EncSliceInt16V(rv.Interface().([]int16), fastpathCheckNilFalse, f.e)
+	}
 }
 func (_ fastpathT) EncSliceInt16V(v []int16, checkNil bool, e *Encoder) {
 	ee := e.e
@@ -3387,8 +3732,39 @@ func (_ fastpathT) EncSliceInt16V(v []int16, checkNil bool, e *Encoder) {
 	}
 }
 
+func (_ fastpathT) EncAsMapSliceInt16V(v []int16, checkNil bool, e *Encoder) {
+	ee := e.e
+	cr := e.cr
+	if checkNil && v == nil {
+		ee.EncodeNil()
+		return
+	}
+	if len(v)%2 == 1 {
+		e.errorf("mapBySlice requires even slice length, but got %v", len(v))
+		return
+	}
+	ee.EncodeMapStart(len(v) / 2)
+	for j, v2 := range v {
+		if cr != nil {
+			if j%2 == 0 {
+				cr.sendContainerState(containerMapKey)
+			} else {
+				cr.sendContainerState(containerMapValue)
+			}
+		}
+		ee.EncodeInt(int64(v2))
+	}
+	if cr != nil {
+		cr.sendContainerState(containerMapEnd)
+	}
+}
+
 func (f *encFnInfo) fastpathEncSliceInt32R(rv reflect.Value) {
-	fastpathTV.EncSliceInt32V(rv.Interface().([]int32), fastpathCheckNilFalse, f.e)
+	if f.ti.mbs {
+		fastpathTV.EncAsMapSliceInt32V(rv.Interface().([]int32), fastpathCheckNilFalse, f.e)
+	} else {
+		fastpathTV.EncSliceInt32V(rv.Interface().([]int32), fastpathCheckNilFalse, f.e)
+	}
 }
 func (_ fastpathT) EncSliceInt32V(v []int32, checkNil bool, e *Encoder) {
 	ee := e.e
@@ -3409,8 +3785,39 @@ func (_ fastpathT) EncSliceInt32V(v []int32, checkNil bool, e *Encoder) {
 	}
 }
 
+func (_ fastpathT) EncAsMapSliceInt32V(v []int32, checkNil bool, e *Encoder) {
+	ee := e.e
+	cr := e.cr
+	if checkNil && v == nil {
+		ee.EncodeNil()
+		return
+	}
+	if len(v)%2 == 1 {
+		e.errorf("mapBySlice requires even slice length, but got %v", len(v))
+		return
+	}
+	ee.EncodeMapStart(len(v) / 2)
+	for j, v2 := range v {
+		if cr != nil {
+			if j%2 == 0 {
+				cr.sendContainerState(containerMapKey)
+			} else {
+				cr.sendContainerState(containerMapValue)
+			}
+		}
+		ee.EncodeInt(int64(v2))
+	}
+	if cr != nil {
+		cr.sendContainerState(containerMapEnd)
+	}
+}
+
 func (f *encFnInfo) fastpathEncSliceInt64R(rv reflect.Value) {
-	fastpathTV.EncSliceInt64V(rv.Interface().([]int64), fastpathCheckNilFalse, f.e)
+	if f.ti.mbs {
+		fastpathTV.EncAsMapSliceInt64V(rv.Interface().([]int64), fastpathCheckNilFalse, f.e)
+	} else {
+		fastpathTV.EncSliceInt64V(rv.Interface().([]int64), fastpathCheckNilFalse, f.e)
+	}
 }
 func (_ fastpathT) EncSliceInt64V(v []int64, checkNil bool, e *Encoder) {
 	ee := e.e
@@ -3431,8 +3838,39 @@ func (_ fastpathT) EncSliceInt64V(v []int64, checkNil bool, e *Encoder) {
 	}
 }
 
+func (_ fastpathT) EncAsMapSliceInt64V(v []int64, checkNil bool, e *Encoder) {
+	ee := e.e
+	cr := e.cr
+	if checkNil && v == nil {
+		ee.EncodeNil()
+		return
+	}
+	if len(v)%2 == 1 {
+		e.errorf("mapBySlice requires even slice length, but got %v", len(v))
+		return
+	}
+	ee.EncodeMapStart(len(v) / 2)
+	for j, v2 := range v {
+		if cr != nil {
+			if j%2 == 0 {
+				cr.sendContainerState(containerMapKey)
+			} else {
+				cr.sendContainerState(containerMapValue)
+			}
+		}
+		ee.EncodeInt(int64(v2))
+	}
+	if cr != nil {
+		cr.sendContainerState(containerMapEnd)
+	}
+}
+
 func (f *encFnInfo) fastpathEncSliceBoolR(rv reflect.Value) {
-	fastpathTV.EncSliceBoolV(rv.Interface().([]bool), fastpathCheckNilFalse, f.e)
+	if f.ti.mbs {
+		fastpathTV.EncAsMapSliceBoolV(rv.Interface().([]bool), fastpathCheckNilFalse, f.e)
+	} else {
+		fastpathTV.EncSliceBoolV(rv.Interface().([]bool), fastpathCheckNilFalse, f.e)
+	}
 }
 func (_ fastpathT) EncSliceBoolV(v []bool, checkNil bool, e *Encoder) {
 	ee := e.e
@@ -3453,6 +3891,33 @@ func (_ fastpathT) EncSliceBoolV(v []bool, checkNil bool, e *Encoder) {
 	}
 }
 
+func (_ fastpathT) EncAsMapSliceBoolV(v []bool, checkNil bool, e *Encoder) {
+	ee := e.e
+	cr := e.cr
+	if checkNil && v == nil {
+		ee.EncodeNil()
+		return
+	}
+	if len(v)%2 == 1 {
+		e.errorf("mapBySlice requires even slice length, but got %v", len(v))
+		return
+	}
+	ee.EncodeMapStart(len(v) / 2)
+	for j, v2 := range v {
+		if cr != nil {
+			if j%2 == 0 {
+				cr.sendContainerState(containerMapKey)
+			} else {
+				cr.sendContainerState(containerMapValue)
+			}
+		}
+		ee.EncodeBool(v2)
+	}
+	if cr != nil {
+		cr.sendContainerState(containerMapEnd)
+	}
+}
+
 func (f *encFnInfo) fastpathEncMapIntfIntfR(rv reflect.Value) {
 	fastpathTV.EncMapIntfIntfV(rv.Interface().(map[interface{}]interface{}), fastpathCheckNilFalse, f.e)
 }

+ 30 - 1
codec/fast-path.go.tmpl

@@ -165,7 +165,11 @@ func fastpathEncodeTypeSwitchMap(iv interface{}, e *Encoder) bool {
 {{range .Values}}{{if not .Primitive}}{{if not .MapKey }} 
 
 func (f *encFnInfo) {{ .MethodNamePfx "fastpathEnc" false }}R(rv reflect.Value) {
-	fastpathTV.{{ .MethodNamePfx "Enc" false }}V(rv.Interface().([]{{ .Elem }}), fastpathCheckNilFalse, f.e)
+	if f.ti.mbs {
+		fastpathTV.{{ .MethodNamePfx "EncAsMap" false }}V(rv.Interface().([]{{ .Elem }}), fastpathCheckNilFalse, f.e)
+	} else {
+		fastpathTV.{{ .MethodNamePfx "Enc" false }}V(rv.Interface().([]{{ .Elem }}), fastpathCheckNilFalse, f.e)
+	}
 }
 func (_ fastpathT) {{ .MethodNamePfx "Enc" false }}V(v []{{ .Elem }}, checkNil bool, e *Encoder) {
 	ee := e.e
@@ -182,6 +186,31 @@ func (_ fastpathT) {{ .MethodNamePfx "Enc" false }}V(v []{{ .Elem }}, checkNil b
 	if cr != nil { cr.sendContainerState(containerArrayEnd) }{{/* ee.EncodeEnd() */}}
 }
 
+func (_ fastpathT) {{ .MethodNamePfx "EncAsMap" false }}V(v []{{ .Elem }}, checkNil bool, e *Encoder) {
+	ee := e.e
+	cr := e.cr
+	if checkNil && v == nil {
+		ee.EncodeNil()
+		return
+	}
+	if len(v)%2 == 1 {
+		e.errorf("mapBySlice requires even slice length, but got %v", len(v))
+		return
+	}
+	ee.EncodeMapStart(len(v) / 2)
+	for j, v2 := range v {
+		if cr != nil {
+			if j%2 == 0 {
+				cr.sendContainerState(containerMapKey)
+			} else {
+				cr.sendContainerState(containerMapValue)
+			}
+		}
+		{{ encmd .Elem "v2"}}
+	}
+	if cr != nil { cr.sendContainerState(containerMapEnd) }
+}
+
 {{end}}{{end}}{{end}}
 
 {{range .Values}}{{if not .Primitive}}{{if .MapKey }}

+ 1 - 1
codec/prebuild.sh

@@ -171,7 +171,7 @@ do
         'xf') zforce=1;;
         'xb') zbak=1;;
         'xx') zexternal=1;;
-        *) echo "prebuild.sh accepts [-fb] only"; return 1;;
+        *) echo "prebuild.sh accepts [-fbx] only"; return 1;;
     esac
 done
 shift $((OPTIND-1))

+ 12 - 6
codec/test.py

@@ -9,6 +9,8 @@
 #   sudo apt-get install python-pip
 #   pip install --user msgpack-python msgpack-rpc-python cbor
 
+# Ensure all "string" keys are utf strings (else encoded as bytes)
+
 import cbor, msgpack, msgpackrpc, sys, os, threading
 
 def get_test_data_list():
@@ -26,35 +28,39 @@ def get_test_data_list():
          -3232.0,
          -6464646464.0,
          3232.0,
+         6464.0,
          6464646464.0,
          False,
          True,
+         u"null",
          None,
          u"someday",
-         u"",
-         u"bytestring",
          1328176922000002000,
+         u"",
          -2206187877999998000,
+         u"bytestring",
          270,
+         u"none",
         -2013855847999995777,
          #-6795364578871345152,
          ]
     l1 = [
         { "true": True,
           "false": False },
-        { "true": "True",
+        { "true": u"True",
           "false": False,
           "uint16(1616)": 1616 },
         { "list": [1616, 32323232, True, -3232.0, {"TRUE":True, "FALSE":False}, [True, False] ],
           "int32":32323232, "bool": True, 
-          "LONG STRING": "123456789012345678901234567890123456789012345678901234567890",
-          "SHORT STRING": "1234567890" },	
-        { True: "true", 8: False, "false": 0 }
+          "LONG STRING": u"123456789012345678901234567890123456789012345678901234567890",
+          "SHORT STRING": u"1234567890" },
+        { True: "true", 138: False, "false": 200 }
         ]
     
     l = []
     l.extend(l0)
     l.append(l0)
+    l.append(1)
     l.extend(l1)
     return l