Repository: fxamacker/cbor
Pull request #521 (2024-04-17 22:58:51)

Add a method for marshaling directly into a user-provided buffer.
<!--
Thank you for your interest in contributing to fxamacker/cbor!
-->

### Description

This is an implementation of the proposal in https://github.com/fxamacker/cbor/issues/520.

<!-- For code contributions, please complete all the items below this line. -->
<!-- For documentation-only contributions, please delete everything below this line. -->

#### PR Was Proposed and Welcomed in Currently Open Issue

- [ ] This PR was proposed and welcomed by maintainer(s) in issue #___
- [x] Closes or Updates Issue #520

#### Checklist (for code PR only, ignore for docs PR)

- [ ] Include unit tests that cover the new code
- [x] Pass all unit tests 
- [x] Pass all lint checks in CI (goimports, gosec, staticcheck, etc.)
- [x] Sign each commit with your real name and email.  
      Last line of each commit message should be in this format:  
      Signed-off-by: Firstname Lastname <firstname.lastname@example.com>
- [x] Certify the Developer's Certificate of Origin 1.1
      (see next section).

#### Certify the Developer's Certificate of Origin 1.1

- [x] By marking this item as completed, I certify 
      the Developer Certificate of Origin 1.1.

```
Developer Certificate of Origin
Version 1.1

Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA

Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.

Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.
```



benluddy: As part of this, I moved the scratch byte array from a pooled buffer field to the stack as-needed.

```
                                                            │ master.txt  │   user-provided-buffer-inline.txt    │
                                                            │   sec/op    │   sec/op     vs base                 │
Marshal/Go_bool_to_CBOR_bool                                  45.33n ± 0%   45.41n ± 0%  +0.19% (p=0.033 n=10)
Marshal/Go_uint64_to_CBOR_positive_int                        63.97n ± 0%   64.06n ± 0%       ~ (p=0.209 n=10)
Marshal/Go_int64_to_CBOR_negative_int                         50.56n ± 1%   50.61n ± 1%       ~ (p=0.195 n=10)
Marshal/Go_float64_to_CBOR_float                              60.98n ± 0%   61.42n ± 0%  +0.72% (p=0.001 n=10)
Marshal/Go_[]uint8_to_CBOR_bytes                              77.63n ± 0%   74.29n ± 0%  -4.30% (p=0.000 n=10)
Marshal/Go_string_to_CBOR_text                                77.50n ± 1%   76.89n ± 0%  -0.79% (p=0.000 n=10)
Marshal/Go_[]int_to_CBOR_array                                286.9n ± 0%   288.9n ± 0%  +0.71% (p=0.000 n=10)
Marshal/Go_map[string]string_to_CBOR_map                      914.8n ± 1%   876.3n ± 1%  -4.20% (p=0.000 n=10)
Marshal/Go_map[string]interface{}_to_CBOR_map                 2.233µ ± 0%   2.260µ ± 0%  +1.21% (p=0.000 n=10)
Marshal/Go_struct_to_CBOR_map                                 1.391µ ± 1%   1.395µ ± 1%       ~ (p=0.839 n=10)
Marshal/Go_map[int]interface{}_to_CBOR_map                    2.155µ ± 0%   2.188µ ± 0%  +1.51% (p=0.000 n=10)
Marshal/Go_struct_keyasint_to_CBOR_map                        1.382µ ± 1%   1.371µ ± 1%  -0.80% (p=0.000 n=10)
Marshal/Go_[]interface{}_to_CBOR_map                          1.617µ ± 0%   1.671µ ± 0%  +3.37% (p=0.000 n=10)
Marshal/Go_struct_toarray_to_CBOR_array                       1.332µ ± 1%   1.329µ ± 1%       ~ (p=0.401 n=10)
```

I also tried updating the implementation of `Marshal` to delegated to `MarshalToBuffer`, which worked fine, but the benchmarks were marginally worse. I think this may just be the overhead of an extra function call being super visible in benchmark cases that are already very fast, or noise:

```
Marshal/Go_bool_to_CBOR_bool                                  45.33n ± 0%   46.59n ± 0%  +2.78% (p=0.000 n=10)
Marshal/Go_uint64_to_CBOR_positive_int                        63.97n ± 0%   64.98n ± 0%  +1.56% (p=0.000 n=10)
Marshal/Go_int64_to_CBOR_negative_int                         50.56n ± 1%   53.09n ± 1%  +4.98% (p=0.000 n=10)
Marshal/Go_float64_to_CBOR_float                              60.98n ± 0%   63.23n ± 0%  +3.69% (p=0.000 n=10)
Marshal/Go_[]uint8_to_CBOR_bytes                              77.63n ± 0%   74.98n ± 0%  -3.41% (p=0.000 n=10)
Marshal/Go_string_to_CBOR_text                                77.50n ± 1%   78.20n ± 0%  +0.89% (p=0.000 n=10)
Marshal/Go_[]int_to_CBOR_array                                286.9n ± 0%   286.9n ± 0%       ~ (p=0.364 n=10)
Marshal/Go_map[string]string_to_CBOR_map                      914.8n ± 1%   889.5n ± 1%  -2.77% (p=0.000 n=10)
Marshal/Go_map[string]interface{}_to_CBOR_map                 2.233µ ± 0%   2.204µ ± 1%  -1.30% (p=0.000 n=10)
Marshal/Go_struct_to_CBOR_map                                 1.391µ ± 1%   1.400µ ± 1%       ~ (p=0.108 n=10)
Marshal/Go_map[int]interface{}_to_CBOR_map                    2.155µ ± 0%   2.151µ ± 1%       ~ (p=0.342 n=10)
Marshal/Go_struct_keyasint_to_CBOR_map                        1.382µ ± 1%   1.336µ ± 0%  -3.33% (p=0.000 n=10)
Marshal/Go_[]interface{}_to_CBOR_map                          1.617µ ± 0%   1.601µ ± 0%  -1.02% (p=0.000 n=10)
Marshal/Go_struct_toarray_to_CBOR_array                       1.332µ ± 1%   1.343µ ± 1%  +0.79% (p=0.022 n=10)
```

I would still rather see `Marshal` call `MarshalToBuffer` so that they both benefit from all of the existing test coverage, unless the benchmark diff is a concern.

Patch:
diff --git a/cache.go b/cache.go
index 87bb63a..8b4d463 100644
--- a/cache.go
+++ b/cache.go
@@ -234,7 +234,7 @@ func getEncodingStructType(t reflect.Type) (*encodingStructType, error) {
 	var hasKeyAsInt bool
 	var hasKeyAsStr bool
 	var omitEmptyIdx []int
-	e := getEncoderBuffer()
+	e := getEncodeBuffer()
 	for i := 0; i < len(flds); i++ {
 		// Get field's encodeFunc
 		flds[i].ef, flds[i].ief = getEncodeFunc(flds[i].typ)
@@ -286,7 +286,7 @@ func getEncodingStructType(t reflect.Type) (*encodingStructType, error) {
 			omitEmptyIdx = append(omitEmptyIdx, i)
 		}
 	}
-	putEncoderBuffer(e)
+	putEncodeBuffer(e)
 
 	if err != nil {
 		structType := &encodingStructType{err: err}
diff --git a/encode.go b/encode.go
index 6fd7e82..9859a8f 100644
--- a/encode.go
+++ b/encode.go
@@ -840,49 +840,48 @@ func (em *encMode) encTagBytes(t reflect.Type) []byte {
 //
 // See the documentation for Marshal for details.
 func (em *encMode) Marshal(v interface{}) ([]byte, error) {
-	e := getEncoderBuffer()
+	e := getEncodeBuffer()
 
 	if err := encode(e, em, reflect.ValueOf(v)); err != nil {
-		putEncoderBuffer(e)
+		putEncodeBuffer(e)
 		return nil, err
 	}
 
 	buf := make([]byte, e.Len())
 	copy(buf, e.Bytes())
 
-	putEncoderBuffer(e)
+	putEncodeBuffer(e)
 	return buf, nil
 }
 
+func (em *encMode) MarshalToBuffer(v interface{}, buf *bytes.Buffer) error {
+	return encode(buf, em, reflect.ValueOf(v))
+}
+
 // NewEncoder returns a new encoder that writes to w using em EncMode.
 func (em *encMode) NewEncoder(w io.Writer) *Encoder {
 	return &Encoder{w: w, em: em}
 }
 
-type encoderBuffer struct {
-	bytes.Buffer
-	scratch [16]byte
-}
-
-// encoderBufferPool caches unused encoderBuffer objects for later reuse.
-var encoderBufferPool = sync.Pool{
+// encodeBufferPool caches unused bytes.Buffer objects for later reuse.
+var encodeBufferPool = sync.Pool{
 	New: func() interface{} {
-		e := new(encoderBuffer)
+		e := new(bytes.Buffer)
 		e.Grow(32) // TODO: make this configurable
 		return e
 	},
 }
 
-func getEncoderBuffer() *encoderBuffer {
-	return encoderBufferPool.Get().(*encoderBuffer)
+func getEncodeBuffer() *bytes.Buffer {
+	return encodeBufferPool.Get().(*bytes.Buffer)
 }
 
-func putEncoderBuffer(e *encoderBuffer) {
+func putEncodeBuffer(e *bytes.Buffer) {
 	e.Reset()
-	encoderBufferPool.Put(e)
+	encodeBufferPool.Put(e)
 }
 
-type encodeFunc func(e *encoderBuffer, em *encMode, v reflect.Value) error
+type encodeFunc func(e *bytes.Buffer, em *encMode, v reflect.Value) error
 type isEmptyFunc func(em *encMode, v reflect.Value) (empty bool, err error)
 
 var (
@@ -894,7 +893,7 @@ var (
 	cborNegativeInfinity = []byte{0xf9, 0xfc, 0x00}
 )
 
-func encode(e *encoderBuffer, em *encMode, v reflect.Value) error {
+func encode(e *bytes.Buffer, em *encMode, v reflect.Value) error {
 	if !v.IsValid() {
 		// v is zero value
 		e.Write(cborNil)
@@ -909,7 +908,7 @@ func encode(e *encoderBuffer, em *encMode, v reflect.Value) error {
 	return f(e, em, v)
 }
 
-func encodeBool(e *encoderBuffer, em *encMode, v reflect.Value) error {
+func encodeBool(e *bytes.Buffer, em *encMode, v reflect.Value) error {
 	if b := em.encTagBytes(v.Type()); b != nil {
 		e.Write(b)
 	}
@@ -921,7 +920,7 @@ func encodeBool(e *encoderBuffer, em *encMode, v reflect.Value) error {
 	return nil
 }
 
-func encodeInt(e *encoderBuffer, em *encMode, v reflect.Value) error {
+func encodeInt(e *bytes.Buffer, em *encMode, v reflect.Value) error {
 	if b := em.encTagBytes(v.Type()); b != nil {
 		e.Write(b)
 	}
@@ -935,7 +934,7 @@ func encodeInt(e *encoderBuffer, em *encMode, v reflect.Value) error {
 	return nil
 }
 
-func encodeUint(e *encoderBuffer, em *encMode, v reflect.Value) error {
+func encodeUint(e *bytes.Buffer, em *encMode, v reflect.Value) error {
 	if b := em.encTagBytes(v.Type()); b != nil {
 		e.Write(b)
 	}
@@ -943,7 +942,7 @@ func encodeUint(e *encoderBuffer, em *encMode, v reflect.Value) error {
 	return nil
 }
 
-func encodeFloat(e *encoderBuffer, em *encMode, v reflect.Value) error {
+func encodeFloat(e *bytes.Buffer, em *encMode, v reflect.Value) error {
 	if b := em.encTagBytes(v.Type()); b != nil {
 		e.Write(b)
 	}
@@ -958,9 +957,10 @@ func encodeFloat(e *encoderBuffer, em *encMode, v reflect.Value) error {
 	if v.Kind() == reflect.Float64 && (fopt == ShortestFloatNone || cannotFitFloat32(f64)) {
 		// Encode float64
 		// Don't use encodeFloat64() because it cannot be inlined.
-		e.scratch[0] = byte(cborTypePrimitives) | byte(27)
-		binary.BigEndian.PutUint64(e.scratch[1:], math.Float64bits(f64))
-		e.Write(e.scratch[:9])
+		var scratch [9]byte
+		scratch[0] = byte(cborTypePrimitives) | byte(27)
+		binary.BigEndian.PutUint64(scratch[1:], math.Float64bits(f64))
+		e.Write(scratch[:9])
 		return nil
 	}
 
@@ -981,22 +981,24 @@ func encodeFloat(e *encoderBuffer, em *encMode, v reflect.Value) error {
 		if p == float16.PrecisionExact {
 			// Encode float16
 			// Don't use encodeFloat16() because it cannot be inlined.
-			e.scratch[0] = byte(cborTypePrimitives) | byte(25)
-			binary.BigEndian.PutUint16(e.scratch[1:], uint16(f16))
-			e.Write(e.scratch[:3])
+			var scratch [3]byte
+			scratch[0] = byte(cborTypePrimitives) | byte(25)
+			binary.BigEndian.PutUint16(scratch[1:], uint16(f16))
+			e.Write(scratch[:3])
 			return nil
 		}
 	}
 
 	// Encode float32
 	// Don't use encodeFloat32() because it cannot be inlined.
-	e.scratch[0] = byte(cborTypePrimitives) | byte(26)
-	binary.BigEndian.PutUint32(e.scratch[1:], math.Float32bits(f32))
-	e.Write(e.scratch[:5])
+	var scratch [5]byte
+	scratch[0] = byte(cborTypePrimitives) | byte(26)
+	binary.BigEndian.PutUint32(scratch[1:], math.Float32bits(f32))
+	e.Write(scratch[:5])
 	return nil
 }
 
-func encodeInf(e *encoderBuffer, em *encMode, v reflect.Value) error {
+func encodeInf(e *bytes.Buffer, em *encMode, v reflect.Value) error {
 	f64 := v.Float()
 	switch em.infConvert {
 	case InfConvertReject:
@@ -1015,7 +1017,7 @@ func encodeInf(e *encoderBuffer, em *encMode, v reflect.Value) error {
 	return encodeFloat32(e, float32(f64))
 }
 
-func encodeNaN(e *encoderBuffer, em *encMode, v reflect.Value) error {
+func encodeNaN(e *bytes.Buffer, em *encMode, v reflect.Value) error {
 	switch em.nanConvert {
 	case NaNConvert7e00:
 		e.Write(cborNaN)
@@ -1076,28 +1078,31 @@ func encodeNaN(e *encoderBuffer, em *encMode, v reflect.Value) error {
 	}
 }
 
-func encodeFloat16(e *encoderBuffer, f16 float16.Float16) error {
-	e.scratch[0] = byte(cborTypePrimitives) | byte(25)
-	binary.BigEndian.PutUint16(e.scratch[1:], uint16(f16))
-	e.Write(e.scratch[:3])
+func encodeFloat16(e *bytes.Buffer, f16 float16.Float16) error {
+	var scratch [3]byte
+	scratch[0] = byte(cborTypePrimitives) | byte(25)
+	binary.BigEndian.PutUint16(scratch[1:], uint16(f16))
+	e.Write(scratch[:3])
 	return nil
 }
 
-func encodeFloat32(e *encoderBuffer, f32 float32) error {
-	e.scratch[0] = byte(cborTypePrimitives) | byte(26)
-	binary.BigEndian.PutUint32(e.scratch[1:], math.Float32bits(f32))
-	e.Write(e.scratch[:5])
+func encodeFloat32(e *bytes.Buffer, f32 float32) error {
+	var scratch [5]byte
+	scratch[0] = byte(cborTypePrimitives) | byte(26)
+	binary.BigEndian.PutUint32(scratch[1:], math.Float32bits(f32))
+	e.Write(scratch[:5])
 	return nil
 }
 
-func encodeFloat64(e *encoderBuffer, f64 float64) error {
-	e.scratch[0] = byte(cborTypePrimitives) | byte(27)
-	binary.BigEndian.PutUint64(e.scratch[1:], math.Float64bits(f64))
-	e.Write(e.scratch[:9])
+func encodeFloat64(e *bytes.Buffer, f64 float64) error {
+	var scratch [9]byte
+	scratch[0] = byte(cborTypePrimitives) | byte(27)
+	binary.BigEndian.PutUint64(scratch[1:], math.Float64bits(f64))
+	e.Write(scratch[:9])
 	return nil
 }
 
-func encodeByteString(e *encoderBuffer, em *encMode, v reflect.Value) error {
+func encodeByteString(e *bytes.Buffer, em *encMode, v reflect.Value) error {
 	vk := v.Kind()
 	if vk == reflect.Slice && v.IsNil() && em.nilContainers == NilContainerAsNull {
 		e.Write(cborNil)
@@ -1124,7 +1129,7 @@ func encodeByteString(e *encoderBuffer, em *encMode, v reflect.Value) error {
 	return nil
 }
 
-func encodeString(e *encoderBuffer, em *encMode, v reflect.Value) error {
+func encodeString(e *bytes.Buffer, em *encMode, v reflect.Value) error {
 	if b := em.encTagBytes(v.Type()); b != nil {
 		e.Write(b)
 	}
@@ -1138,7 +1143,7 @@ type arrayEncodeFunc struct {
 	f encodeFunc
 }
 
-func (ae arrayEncodeFunc) encode(e *encoderBuffer, em *encMode, v reflect.Value) error {
+func (ae arrayEncodeFunc) encode(e *bytes.Buffer, em *encMode, v reflect.Value) error {
 	if em.byteArray == ByteArrayToByteSlice && v.Type().Elem().Kind() == reflect.Uint8 {
 		return encodeByteString(e, em, v)
 	}
@@ -1165,13 +1170,13 @@ func (ae arrayEncodeFunc) encode(e *encoderBuffer, em *encMode, v reflect.Value)
 // encodeKeyValueFunc encodes key/value pairs in map (v).
 // If kvs is provided (having the same length as v), length of encoded key and value are stored in kvs.
 // kvs is used for canonical encoding of map.
-type encodeKeyValueFunc func(e *encoderBuffer, em *encMode, v reflect.Value, kvs []keyValue) error
+type encodeKeyValueFunc func(e *bytes.Buffer, em *encMode, v reflect.Value, kvs []keyValue) error
 
 type mapEncodeFunc struct {
 	e encodeKeyValueFunc
 }
 
-func (me mapEncodeFunc) encode(e *encoderBuffer, em *encMode, v reflect.Value) error {
+func (me mapEncodeFunc) encode(e *bytes.Buffer, em *encMode, v reflect.Value) error {
 	if v.IsNil() && em.nilContainers == NilContainerAsNull {
 		e.Write(cborNil)
 		return nil
@@ -1260,9 +1265,9 @@ func putKeyValues(x *[]keyValue) {
 	keyValuePool.Put(x)
 }
 
-func (me mapEncodeFunc) encodeCanonical(e *encoderBuffer, em *encMode, v reflect.Value) error {
-	kve := getEncoderBuffer() // accumulated cbor encoded key-values
-	defer putEncoderBuffer(kve)
+func (me mapEncodeFunc) encodeCanonical(e *bytes.Buffer, em *encMode, v reflect.Value) error {
+	kve := getEncodeBuffer() // accumulated cbor encoded key-values
+	defer putEncodeBuffer(kve)
 
 	kvsp := getKeyValues(v.Len()) // for sorting keys
 	defer putKeyValues(kvsp)
@@ -1295,7 +1300,7 @@ func (me mapEncodeFunc) encodeCanonical(e *encoderBuffer, em *encMode, v reflect
 	return nil
 }
 
-func encodeStructToArray(e *encoderBuffer, em *encMode, v reflect.Value) (err error) {
+func encodeStructToArray(e *bytes.Buffer, em *encMode, v reflect.Value) (err error) {
 	structType, err := getEncodingStructType(v.Type())
 	if err != nil {
 		return err
@@ -1333,7 +1338,7 @@ func encodeStructToArray(e *encoderBuffer, em *encMode, v reflect.Value) (err er
 	return nil
 }
 
-func encodeStruct(e *encoderBuffer, em *encMode, v reflect.Value) (err error) {
+func encodeStruct(e *bytes.Buffer, em *encMode, v reflect.Value) (err error) {
 	structType, err := getEncodingStructType(v.Type())
 	if err != nil {
 		return err
@@ -1403,7 +1408,7 @@ func encodeStruct(e *encoderBuffer, em *encMode, v reflect.Value) (err error) {
 	// Overwrite the bytes that were reserved for the head before encoding the map entries.
 	var actualHeadLen int
 	{
-		headbuf := encoderBuffer{Buffer: *bytes.NewBuffer(e.Bytes()[kvbegin-encodedHeadLen : kvbegin-encodedHeadLen : kvbegin])}
+		headbuf := *bytes.NewBuffer(e.Bytes()[kvbegin-encodedHeadLen : kvbegin-encodedHeadLen : kvbegin])
 		actualHeadLen = encodeHead(&headbuf, byte(cborTypeMap), uint64(kvcount))
 	}
 
@@ -1427,7 +1432,7 @@ func encodeStruct(e *encoderBuffer, em *encMode, v reflect.Value) (err error) {
 	return nil
 }
 
-func encodeIntf(e *encoderBuffer, em *encMode, v reflect.Value) error {
+func encodeIntf(e *bytes.Buffer, em *encMode, v reflect.Value) error {
 	if v.IsNil() {
 		e.Write(cborNil)
 		return nil
@@ -1435,7 +1440,7 @@ func encodeIntf(e *encoderBuffer, em *encMode, v reflect.Value) error {
 	return encode(e, em, v.Elem())
 }
 
-func encodeTime(e *encoderBuffer, em *encMode, v reflect.Value) error {
+func encodeTime(e *bytes.Buffer, em *encMode, v reflect.Value) error {
 	t := v.Interface().(time.Time)
 	if t.IsZero() {
 		e.Write(cborNil) // Even if tag is required, encode as CBOR null.
@@ -1473,7 +1478,7 @@ func encodeTime(e *encoderBuffer, em *encMode, v reflect.Value) error {
 	}
 }
 
-func encodeBigInt(e *encoderBuffer, em *encMode, v reflect.Value) error {
+func encodeBigInt(e *bytes.Buffer, em *encMode, v reflect.Value) error {
 	vbi := v.Interface().(big.Int)
 	sign := vbi.Sign()
 	bi := new(big.Int).SetBytes(vbi.Bytes()) // bi is absolute value of v
@@ -1508,7 +1513,7 @@ func encodeBigInt(e *encoderBuffer, em *encMode, v reflect.Value) error {
 	return nil
 }
 
-func encodeBinaryMarshalerType(e *encoderBuffer, em *encMode, v reflect.Value) error {
+func encodeBinaryMarshalerType(e *bytes.Buffer, em *encMode, v reflect.Value) error {
 	vt := v.Type()
 	m, ok := v.Interface().(encoding.BinaryMarshaler)
 	if !ok {
@@ -1528,7 +1533,7 @@ func encodeBinaryMarshalerType(e *encoderBuffer, em *encMode, v reflect.Value) e
 	return nil
 }
 
-func encodeMarshalerType(e *encoderBuffer, em *encMode, v reflect.Value) error {
+func encodeMarshalerType(e *bytes.Buffer, em *encMode, v reflect.Value) error {
 	if em.tagsMd == TagsForbidden && v.Type() == typeRawTag {
 		return errors.New("cbor: cannot encode cbor.RawTag when TagsMd is TagsForbidden")
 	}
@@ -1554,7 +1559,7 @@ func encodeMarshalerType(e *encoderBuffer, em *encMode, v reflect.Value) error {
 	return nil
 }
 
-func encodeTag(e *encoderBuffer, em *encMode, v reflect.Value) error {
+func encodeTag(e *bytes.Buffer, em *encMode, v reflect.Value) error {
 	if em.tagsMd == TagsForbidden {
 		return errors.New("cbor: cannot encode cbor.Tag when TagsMd is TagsForbidden")
 	}
@@ -1575,32 +1580,34 @@ func encodeTag(e *encoderBuffer, em *encMode, v reflect.Value) error {
 }
 
 // encodeHead writes CBOR head of specified type t and returns number of bytes written.
-func encodeHead(e *encoderBuffer, t byte, n uint64) int {
+func encodeHead(e *bytes.Buffer, t byte, n uint64) int {
 	if n <= 23 {
 		e.WriteByte(t | byte(n))
 		return 1
 	}
 	if n <= math.MaxUint8 {
-		e.scratch[0] = t | byte(24)
-		e.scratch[1] = byte(n)
-		e.Write(e.scratch[:2])
+		scratch := [2]byte{t | byte(24), byte(n)}
+		e.Write(scratch[:2])
 		return 2
 	}
 	if n <= math.MaxUint16 {
-		e.scratch[0] = t | byte(25)
-		binary.BigEndian.PutUint16(e.scratch[1:], uint16(n))
-		e.Write(e.scratch[:3])
+		var scratch [3]byte
+		scratch[0] = t | byte(25)
+		binary.BigEndian.PutUint16(scratch[1:], uint16(n))
+		e.Write(scratch[:3])
 		return 3
 	}
 	if n <= math.MaxUint32 {
-		e.scratch[0] = t | byte(26)
-		binary.BigEndian.PutUint32(e.scratch[1:], uint32(n))
-		e.Write(e.scratch[:5])
+		var scratch [5]byte
+		scratch[0] = t | byte(26)
+		binary.BigEndian.PutUint32(scratch[1:], uint32(n))
+		e.Write(scratch[:5])
 		return 5
 	}
-	e.scratch[0] = t | byte(27)
-	binary.BigEndian.PutUint64(e.scratch[1:], n)
-	e.Write(e.scratch[:9])
+	var scratch [9]byte
+	scratch[0] = t | byte(27)
+	binary.BigEndian.PutUint64(scratch[1:], n)
+	e.Write(scratch[:9])
 	return 9
 }
 
@@ -1689,7 +1696,7 @@ func getEncodeIndirectValueFunc(t reflect.Type) encodeFunc {
 	if f == nil {
 		return nil
 	}
-	return func(e *encoderBuffer, em *encMode, v reflect.Value) error {
+	return func(e *bytes.Buffer, em *encMode, v reflect.Value) error {
 		for v.Kind() == reflect.Ptr && !v.IsNil() {
 			v = v.Elem()
 		}
diff --git a/encode_map.go b/encode_map.go
index 9850dc0..42ec26a 100644
--- a/encode_map.go
+++ b/encode_map.go
@@ -6,6 +6,7 @@
 package cbor
 
 import (
+	"bytes"
 	"reflect"
 	"sync"
 )
@@ -15,7 +16,7 @@ type mapKeyValueEncodeFunc struct {
 	kpool, vpool sync.Pool
 }
 
-func (me *mapKeyValueEncodeFunc) encodeKeyValues(e *encoderBuffer, em *encMode, v reflect.Value, kvs []keyValue) error {
+func (me *mapKeyValueEncodeFunc) encodeKeyValues(e *bytes.Buffer, em *encMode, v reflect.Value, kvs []keyValue) error {
 	trackKeyValueLength := len(kvs) == v.Len()
 	iterk := me.kpool.Get().(*reflect.Value)
 	defer func() {
diff --git a/encode_map_go117.go b/encode_map_go117.go
index f942147..6cbb8b0 100644
--- a/encode_map_go117.go
+++ b/encode_map_go117.go
@@ -6,6 +6,7 @@
 package cbor
 
 import (
+	"bytes"
 	"reflect"
 )
 
@@ -13,7 +14,7 @@ type mapKeyValueEncodeFunc struct {
 	kf, ef encodeFunc
 }
 
-func (me *mapKeyValueEncodeFunc) encodeKeyValues(e *encoderBuffer, em *encMode, v reflect.Value, kvs []keyValue) error {
+func (me *mapKeyValueEncodeFunc) encodeKeyValues(e *bytes.Buffer, em *encMode, v reflect.Value, kvs []keyValue) error {
 	trackKeyValueLength := len(kvs) == v.Len()
 
 	iter := v.MapRange()
diff --git a/stream.go b/stream.go
index 7ce0c43..4091d88 100644
--- a/stream.go
+++ b/stream.go
@@ -187,14 +187,14 @@ func (enc *Encoder) Encode(v interface{}) error {
 		}
 	}
 
-	buf := getEncoderBuffer()
+	buf := getEncodeBuffer()
 
 	err := encode(buf, enc.em, reflect.ValueOf(v))
 	if err == nil {
 		_, err = enc.w.Write(buf.Bytes())
 	}
 
-	putEncoderBuffer(buf)
+	putEncodeBuffer(buf)
 	return err
 }
 
diff --git a/tag.go b/tag.go
index aefb4d3..4fade06 100644
--- a/tag.go
+++ b/tag.go
@@ -56,7 +56,7 @@ func (t RawTag) MarshalCBOR() ([]byte, error) {
 		return b, nil
 	}
 
-	e := getEncoderBuffer()
+	e := getEncodeBuffer()
 
 	encodeHead(e, byte(cborTypeTag), t.Number)
 
@@ -69,7 +69,7 @@ func (t RawTag) MarshalCBOR() ([]byte, error) {
 	n := copy(buf, e.Bytes())
 	copy(buf[n:], content)
 
-	putEncoderBuffer(e)
+	putEncodeBuffer(e)
 	return buf, nil
 }
 
@@ -269,13 +269,13 @@ func newTagItem(opts TagOptions, contentType reflect.Type, num uint64, nestedNum
 	te.num = append(te.num, nestedNum...)
 
 	// Cache encoded tag numbers
-	e := getEncoderBuffer()
+	e := getEncodeBuffer()
 	for _, n := range te.num {
 		encodeHead(e, byte(cborTypeTag), n)
 	}
 	te.cborTagNum = make([]byte, e.Len())
 	copy(te.cborTagNum, e.Bytes())
-	putEncoderBuffer(e)
+	putEncodeBuffer(e)
 
 	return &te, nil
 }


Test patch:
diff --git a/bytestring.go b/bytestring.go
index 52a28ed..823bff1 100644
--- a/bytestring.go
+++ b/bytestring.go
@@ -22,8 +22,8 @@ func (bs ByteString) Bytes() []byte {
 
 // MarshalCBOR encodes ByteString as CBOR byte string (major type 2).
 func (bs ByteString) MarshalCBOR() ([]byte, error) {
-	e := getEncoderBuffer()
-	defer putEncoderBuffer(e)
+	e := getEncodeBuffer()
+	defer putEncodeBuffer(e)
 
 	// Encode length
 	encodeHead(e, byte(cborTypeByteString), uint64(len(bs)))