package proto import ( "fmt" "io" "reflect" "unsafe" ) var bytesCodec = codec{ wire: varlen, size: sizeOfBytes, encode: encodeBytes, decode: decodeBytes, } func sizeOfBytes(p unsafe.Pointer, flags flags) int { if p != nil { if v := *(*[]byte)(p); v != nil || flags.has(wantzero) { return sizeOfVarlen(len(v)) } } return 0 } func encodeBytes(b []byte, p unsafe.Pointer, flags flags) (int, error) { if p != nil { if v := *(*[]byte)(p); v != nil || flags.has(wantzero) { n, err := encodeVarint(b, uint64(len(v))) if err != nil { return n, err } c := copy(b[n:], v) n += c if c < len(v) { err = io.ErrShortBuffer } return n, err } } return 0, nil } func decodeBytes(b []byte, p unsafe.Pointer, _ flags) (int, error) { v, n, err := decodeVarlen(b) pb := (*[]byte)(p) if *pb == nil { *pb = make([]byte, 0, len(v)) } *pb = append((*pb)[:0], v...) return n, err } func makeBytes(p unsafe.Pointer, n int) []byte { return *(*[]byte)(unsafe.Pointer(&sliceHeader{ Data: p, Len: n, Cap: n, })) } type sliceHeader struct { Data unsafe.Pointer Len int Cap int } // isZeroBytes is an optimized version of this loop: // // for i := range b { // if b[i] != 0 { // return false // } // } // return true // // This implementation significantly reduces the CPU footprint of checking for // slices to be zero, especially when the length increases (these cases should // be rare tho). // // name old time/op new time/op delta // IsZeroBytes0 1.78ns ± 1% 2.29ns ± 4% +28.65% (p=0.000 n=8+10) // IsZeroBytes4 3.17ns ± 3% 2.37ns ± 3% -25.21% (p=0.000 n=10+10) // IsZeroBytes7 3.97ns ± 4% 3.26ns ± 3% -18.02% (p=0.000 n=10+10) // IsZeroBytes64K 14.8µs ± 3% 1.9µs ± 3% -87.34% (p=0.000 n=10+10) func isZeroBytes(b []byte) bool { if n := len(b) / 8; n != 0 { if !isZeroUint64(*(*[]uint64)(unsafe.Pointer(&sliceHeader{ Data: unsafe.Pointer(&b[0]), Len: n, Cap: n, }))) { return false } b = b[n*8:] } switch len(b) { case 7: return bto32(b) == 0 && bto16(b[4:]) == 0 && b[6] == 0 case 6: return bto32(b) == 0 && bto16(b[4:]) == 0 case 5: return bto32(b) == 0 && b[4] == 0 case 4: return bto32(b) == 0 case 3: return bto16(b) == 0 && b[2] == 0 case 2: return bto16(b) == 0 case 1: return b[0] == 0 default: return true } } func bto32(b []byte) uint32 { return *(*uint32)(unsafe.Pointer(&b[0])) } func bto16(b []byte) uint16 { return *(*uint16)(unsafe.Pointer(&b[0])) } func isZeroUint64(b []uint64) bool { for i := range b { if b[i] != 0 { return false } } return true } func byteArrayCodecOf(t reflect.Type, seen map[reflect.Type]*codec) *codec { n := t.Len() c := &codec{ wire: varlen, size: byteArraySizeFuncOf(n), encode: byteArrayEncodeFuncOf(n), decode: byteArrayDecodeFuncOf(n), } seen[t] = c return c } func byteArraySizeFuncOf(n int) sizeFunc { size := sizeOfVarlen(n) return func(p unsafe.Pointer, flags flags) int { if p != nil && (flags.has(wantzero) || !isZeroBytes(makeBytes(p, n))) { return size } return 0 } } func byteArrayEncodeFuncOf(n int) encodeFunc { return func(b []byte, p unsafe.Pointer, flags flags) (int, error) { if p != nil { if v := makeBytes(p, n); flags.has(wantzero) || !isZeroBytes(v) { return encodeBytes(b, unsafe.Pointer(&v), noflags) } } return 0, nil } } func byteArrayDecodeFuncOf(n int) decodeFunc { return func(b []byte, p unsafe.Pointer, _ flags) (int, error) { v, r, err := decodeVarlen(b) if err == nil { if copy(makeBytes(p, n), v) != n { err = fmt.Errorf("cannot decode byte sequence of size %d into byte array of size %d", len(v), n) } } return r, err } }