Skip to content

Commit

Permalink
codec: perf: make common-path of bytesEncWriter.grow inlineable
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ugorji committed Nov 30, 2016
1 parent faddd61 commit 8b94642
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 37 deletions.
73 changes: 40 additions & 33 deletions codec/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,52 +235,73 @@ 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)]
}
}
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 {
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 0 additions & 4 deletions codec/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit 8b94642

Please sign in to comment.