Bladeren bron

codec: perf: make common-path of bytesEncWriter.grow inlineable

Most requests to grow for a bytesEncWriter will either move
the cursor or expand the length to the cap of the []byte.

Put this in a different method that can be inlined, and only
call out to the growCap method if an allocation is necessary.

Makes encode for a []byte about 8% faster in microbenchmarks.
Ugorji Nwoke 9 jaren geleden
bovenliggende
commit
8b94642d9a
2 gewijzigde bestanden met toevoegingen van 40 en 37 verwijderingen
  1. 40 33
      codec/encode.go
  2. 0 4
      codec/helper.go

+ 40 - 33
codec/encode.go

@@ -235,45 +235,57 @@ type bytesEncWriter struct {
 }
 
 func (z *bytesEncWriter) writeb(s []byte) {
-	if len(s) > 0 {
-		c := z.grow(len(s))
-		copy(z.b[c:], s)
+	if len(s) == 0 {
+		return
+	}
+	oc, a := z.growNoAlloc(len(s))
+	if a {
+		z.growAlloc(len(s), oc)
 	}
+	copy(z.b[oc:], s)
 }
 
 func (z *bytesEncWriter) writestr(s string) {
-	if len(s) > 0 {
-		c := z.grow(len(s))
-		copy(z.b[c:], s)
+	if len(s) == 0 {
+		return
+	}
+	oc, a := z.growNoAlloc(len(s))
+	if a {
+		z.growAlloc(len(s), oc)
 	}
+	copy(z.b[oc:], s)
 }
 
 func (z *bytesEncWriter) writen1(b1 byte) {
-	c := z.grow(1)
-	z.b[c] = b1
+	oc, a := z.growNoAlloc(1)
+	if a {
+		z.growAlloc(1, oc)
+	}
+	z.b[oc] = b1
 }
 
 func (z *bytesEncWriter) writen2(b1 byte, b2 byte) {
-	c := z.grow(2)
-	z.b[c+1] = b2
-	z.b[c] = b1
+	oc, a := z.growNoAlloc(2)
+	if a {
+		z.growAlloc(2, oc)
+	}
+	z.b[oc+1] = b2
+	z.b[oc] = b1
 }
 
 func (z *bytesEncWriter) atEndOfEncode() {
 	*(z.out) = z.b[:z.c]
 }
 
-func (z *bytesEncWriter) grow(n int) (oldcursor int) {
+// have a growNoalloc(n int), which can be inlined.
+// if allocation is needed, then call growAlloc(n int)
+
+func (z *bytesEncWriter) growNoAlloc(n int) (oldcursor int, allocNeeded bool) {
 	oldcursor = z.c
-	z.c = oldcursor + n
+	z.c = z.c + n
 	if z.c > len(z.b) {
 		if z.c > cap(z.b) {
-			// appendslice logic (if cap < 1024, *2, else *1.25): more expensive. many copy calls.
-			// bytes.Buffer model (2*cap + n): much better
-			// bs := make([]byte, 2*cap(z.b)+n)
-			bs := make([]byte, growCap(cap(z.b), 1, n))
-			copy(bs, z.b[:oldcursor])
-			z.b = bs
+			allocNeeded = true
 		} else {
 			z.b = z.b[:cap(z.b)]
 		}
@@ -281,6 +293,15 @@ func (z *bytesEncWriter) grow(n int) (oldcursor int) {
 	return
 }
 
+func (z *bytesEncWriter) growAlloc(n int, oldcursor int) {
+	// appendslice logic (if cap < 1024, *2, else *1.25): more expensive. many copy calls.
+	// bytes.Buffer model (2*cap + n): much better
+	// bs := make([]byte, 2*cap(z.b)+n)
+	bs := make([]byte, growCap(cap(z.b), 1, n))
+	copy(bs, z.b[:oldcursor])
+	z.b = bs
+}
+
 // ---------------------------------------------
 
 type encFnInfo struct {
@@ -1059,20 +1080,6 @@ func (e *Encoder) MustEncode(v interface{}) {
 	e.w.atEndOfEncode()
 }
 
-// comment out these (Must)Write methods. They were only put there to support cbor.
-// However, users already have access to the streams, and can write directly.
-//
-// // Write allows users write to the Encoder stream directly.
-// func (e *Encoder) Write(bs []byte) (err error) {
-// 	defer panicToErr(&err)
-// 	e.w.writeb(bs)
-// 	return
-// }
-// // MustWrite is like write, but panics if unable to Write.
-// func (e *Encoder) MustWrite(bs []byte) {
-// 	e.w.writeb(bs)
-// }
-
 func (e *Encoder) encode(iv interface{}) {
 	// if ics, ok := iv.(Selfer); ok {
 	// 	ics.CodecEncodeSelf(e)

+ 0 - 4
codec/helper.go

@@ -38,10 +38,6 @@ package codec
 // a length prefix, or if it used explicit breaks. If length-prefixed, we assume that
 // it has to be binary, and we do not even try to read separators.
 //
-// The only codec that may suffer (slightly) is cbor, and only when decoding indefinite-length.
-// It may suffer because we treat it like a text-based codec, and read separators.
-// However, this read is a no-op and the cost is insignificant.
-//
 // Philosophy
 // ------------
 // On decode, this codec will update containers appropriately: