Browse Source

codec: Improve Perf: Use slice, not map, to cache (En/De)code specific functions.

Each time the recursive (en/de)codeValue is called, we previously checked a map
to get the specific function to use for the value.

Since each En/Decoder only touches a small subset of types, it is more
performant to use a slice to keep track of them and just linear search
the slice (without locks) to find the specific function to call.

This improved performance by over 10% in benchmarks.
Ugorji Nwoke 12 years ago
parent
commit
31af0a3704
4 changed files with 74 additions and 27 deletions
  1. 14 14
      codec/0doc.go
  2. 26 6
      codec/decode.go
  3. 26 7
      codec/encode.go
  4. 8 0
      codec/helper.go

+ 14 - 14
codec/0doc.go

@@ -135,7 +135,7 @@ Representative Benchmark Results
 A sample run of benchmark using "go test -bi -bench=.":
 
    ..............................................
-   BENCHMARK INIT: 2013-09-30 14:18:26.997930788 -0400 EDT
+   BENCHMARK INIT: 2013-10-04 14:36:50.381959842 -0400 EDT
    To run full benchmark comparing encodings (MsgPack, Binc, JSON, GOB, etc), use: "go test -bench=."
    Benchmark: 
       	Struct recursive Depth:             1
@@ -149,19 +149,19 @@ A sample run of benchmark using "go test -bi -bench=.":
       	      json: len: 2538 bytes
    ..............................................
    PASS
-   Benchmark__Msgpack__Encode	   50000	     69408 ns/op	   15852 B/op	      84 allocs/op
-   Benchmark__Msgpack__Decode	   10000	    119152 ns/op	   15542 B/op	     424 allocs/op
-   Benchmark__Binc_____Encode	   20000	     80940 ns/op	   18033 B/op	      88 allocs/op
-   Benchmark__Binc_____Decode	   10000	    123617 ns/op	   16363 B/op	     305 allocs/op
-   Benchmark__Gob______Encode	   10000	    152634 ns/op	   21342 B/op	     238 allocs/op
-   Benchmark__Gob______Decode	    5000	    424450 ns/op	   83625 B/op	    1842 allocs/op
-   Benchmark__Json_____Encode	   20000	     83246 ns/op	   13866 B/op	     102 allocs/op
-   Benchmark__Json_____Decode	   10000	    263762 ns/op	   14166 B/op	     493 allocs/op
-   Benchmark__Bson_____Encode	   10000	    129876 ns/op	   27722 B/op	     514 allocs/op
-   Benchmark__Bson_____Decode	   10000	    164583 ns/op	   16478 B/op	     789 allocs/op
-   Benchmark__VMsgpack_Encode	   50000	     71333 ns/op	   12356 B/op	     343 allocs/op
-   Benchmark__VMsgpack_Decode	   10000	    161800 ns/op	   20302 B/op	     571 allocs/op
-   ok  	ugorji.net/codec	27.165s
+   Benchmark__Msgpack__Encode	   50000	     62774 ns/op	   16336 B/op	      93 allocs/op
+   Benchmark__Msgpack__Decode	   10000	    113152 ns/op	   16195 B/op	     434 allocs/op
+   Benchmark__Binc_____Encode	   50000	     73546 ns/op	   18515 B/op	      98 allocs/op
+   Benchmark__Binc_____Decode	   10000	    112489 ns/op	   16906 B/op	     315 allocs/op
+   Benchmark__Gob______Encode	   10000	    139114 ns/op	   21143 B/op	     237 allocs/op
+   Benchmark__Gob______Decode	    5000	    412988 ns/op	   82900 B/op	    1840 allocs/op
+   Benchmark__Json_____Encode	   20000	     80286 ns/op	   13866 B/op	     102 allocs/op
+   Benchmark__Json_____Decode	   10000	    249694 ns/op	   14153 B/op	     493 allocs/op
+   Benchmark__Bson_____Encode	   10000	    123965 ns/op	   27739 B/op	     514 allocs/op
+   Benchmark__Bson_____Decode	   10000	    157703 ns/op	   16441 B/op	     789 allocs/op
+   Benchmark__VMsgpack_Encode	   50000	     67791 ns/op	   12358 B/op	     343 allocs/op
+   Benchmark__VMsgpack_Decode	   10000	    151476 ns/op	   20264 B/op	     571 allocs/op
+   ok  	ugorji.net/codec	27.609s
 
 
 To run full benchmark suite (including against vmsgpack and bson), 

+ 26 - 6
codec/decode.go

@@ -98,6 +98,8 @@ type Decoder struct {
 	d decDriver
 	h decodeHandleI
 	f map[uintptr]decFn
+	x []uintptr
+	s []decFn
 }
 
 func (f *decFnInfo) builtin(rv reflect.Value) {
@@ -548,11 +550,21 @@ func (d *Decoder) decodeValue(rv reflect.Value) {
 	// retrieve or register a focus'ed function for this type
 	// to eliminate need to do the retrieval multiple times
 	
-	if d.f == nil {
-		// debugf("---->Creating new dec f map for type: %v\n", rt)
-		d.f = make(map[uintptr]decFn, 16)
+	// if d.f == nil && d.s == nil {
+	// 	// debugf("---->Creating new dec f map for type: %v\n", rt)
+	// }
+	var fn decFn 
+	var ok bool
+	if useMapForCodecCache {
+		fn, ok = d.f[rtid]
+	} else {
+		for i, v := range d.x {
+			if v == rtid {
+				fn, ok = d.s[i], true
+				break
+			}
+		}
 	}
-	fn, ok := d.f[rtid]
 	if !ok {
 		// debugf("\tCreating new dec fn for type: %v\n", rt)
 		fi := decFnInfo { sis:getTypeInfo(rtid, rt), d:d, dd:d.d, rt:rt, rtid:rtid }
@@ -621,8 +633,16 @@ func (d *Decoder) decodeValue(rv reflect.Value) {
 			default:
 				fn = decFn { &fi, (*decFnInfo).kErr }
 			}
-		}		
-		d.f[rtid] = fn
+		}
+		if useMapForCodecCache {
+			if d.f == nil {
+				d.f = make(map[uintptr]decFn, 16)
+			}
+			d.f[rtid] = fn
+		} else {
+			d.s = append(d.s, fn )
+			d.x = append(d.x, rtid)
+		}
 	}
 	
 	fn.f(fn.i, rv)

+ 26 - 7
codec/encode.go

@@ -83,6 +83,8 @@ type Encoder struct {
 	e encDriver
 	h encodeHandleI
 	f map[uintptr]encFn
+	x []uintptr
+	s []encFn
 }
 
 type ioEncWriterWriter interface {
@@ -526,13 +528,22 @@ func (e *Encoder) encode(iv interface{}) {
 func (e *Encoder) encodeValue(rv reflect.Value) {
 	rt := rv.Type()
 	rtid := reflect.ValueOf(rt).Pointer()
-	
-	if e.f == nil {
-		// debugf("---->Creating new enc f map for type: %v\n", rt)
-		e.f = make(map[uintptr]encFn, 16)
+		
+	// if e.f == nil && e.s == nil {
+	// 	debugf("---->Creating new enc f map for type: %v\n", rt)
+	// }
+	var fn encFn 
+	var ok bool
+	if useMapForCodecCache {
+		fn, ok = e.f[rtid]
+	} else {
+		for i, v := range e.x {
+			if v == rtid {
+				fn, ok = e.s[i], true
+				break
+			}
+		}
 	}
-
-	fn, ok := e.f[rtid]
 	if !ok {
 		// debugf("\tCreating new enc fn for type: %v\n", rt)
 		fi := encFnInfo { sis:getTypeInfo(rtid, rt), e:e, ee:e.e, rt:rt, rtid:rtid }
@@ -577,7 +588,15 @@ func (e *Encoder) encodeValue(rv reflect.Value) {
 				fn = encFn{ &fi, (*encFnInfo).kErr }
 			}
 		}
-		e.f[rtid] = fn
+		if useMapForCodecCache {
+			if e.f == nil {
+				e.f = make(map[uintptr]encFn, 16)
+			}
+			e.f[rtid] = fn
+		} else {
+			e.s = append(e.s, fn )
+			e.x = append(e.x, rtid)
+		}
 	}
 	
 	fn.f(fn.i, rv)

+ 8 - 0
codec/helper.go

@@ -41,6 +41,14 @@ const (
 	// 
 	// TODO: Look into this again later.
 	supportBinaryMarshal  = true
+
+	// Each Encoder or Decoder uses a cache of functions based on conditionals,
+	// so that the conditionals are not run every time.
+	// 
+	// Either a map or a slice is used to keep track of the functions.
+	// The map is more natural, but has a higher cost than a slice/array.
+	// This flag (useMapForCodecCache) controls which is used.
+	useMapForCodecCache = false
 )
 
 type charEncoding uint8