github.com/kamalshkeir/kencoding@v0.0.2-0.20230409043843-44b609a0475a/proto/bytes.go (about)

     1  package proto
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"reflect"
     7  	"unsafe"
     8  )
     9  
    10  var bytesCodec = codec{
    11  	wire:   varlen,
    12  	size:   sizeOfBytes,
    13  	encode: encodeBytes,
    14  	decode: decodeBytes,
    15  }
    16  
    17  func sizeOfBytes(p unsafe.Pointer, flags flags) int {
    18  	if p != nil {
    19  		if v := *(*[]byte)(p); v != nil || flags.has(wantzero) {
    20  			return sizeOfVarlen(len(v))
    21  		}
    22  	}
    23  	return 0
    24  }
    25  
    26  func encodeBytes(b []byte, p unsafe.Pointer, flags flags) (int, error) {
    27  	if p != nil {
    28  		if v := *(*[]byte)(p); v != nil || flags.has(wantzero) {
    29  			n, err := encodeVarint(b, uint64(len(v)))
    30  			if err != nil {
    31  				return n, err
    32  			}
    33  			c := copy(b[n:], v)
    34  			n += c
    35  			if c < len(v) {
    36  				err = io.ErrShortBuffer
    37  			}
    38  			return n, err
    39  		}
    40  	}
    41  	return 0, nil
    42  }
    43  
    44  func decodeBytes(b []byte, p unsafe.Pointer, _ flags) (int, error) {
    45  	v, n, err := decodeVarlen(b)
    46  	pb := (*[]byte)(p)
    47  	if *pb == nil {
    48  		*pb = make([]byte, 0, len(v))
    49  	}
    50  	*pb = append((*pb)[:0], v...)
    51  	return n, err
    52  }
    53  
    54  func makeBytes(p unsafe.Pointer, n int) []byte {
    55  	return *(*[]byte)(unsafe.Pointer(&sliceHeader{
    56  		Data: p,
    57  		Len:  n,
    58  		Cap:  n,
    59  	}))
    60  }
    61  
    62  type sliceHeader struct {
    63  	Data unsafe.Pointer
    64  	Len  int
    65  	Cap  int
    66  }
    67  
    68  // isZeroBytes is an optimized version of this loop:
    69  //
    70  //	for i := range b {
    71  //		if b[i] != 0 {
    72  //			return false
    73  //		}
    74  //	}
    75  //	return true
    76  //
    77  // This implementation significantly reduces the CPU footprint of checking for
    78  // slices to be zero, especially when the length increases (these cases should
    79  // be rare tho).
    80  //
    81  // name            old time/op  new time/op  delta
    82  // IsZeroBytes0    1.78ns ± 1%  2.29ns ± 4%  +28.65%  (p=0.000 n=8+10)
    83  // IsZeroBytes4    3.17ns ± 3%  2.37ns ± 3%  -25.21%  (p=0.000 n=10+10)
    84  // IsZeroBytes7    3.97ns ± 4%  3.26ns ± 3%  -18.02%  (p=0.000 n=10+10)
    85  // IsZeroBytes64K  14.8µs ± 3%   1.9µs ± 3%  -87.34%  (p=0.000 n=10+10)
    86  func isZeroBytes(b []byte) bool {
    87  	if n := len(b) / 8; n != 0 {
    88  		if !isZeroUint64(*(*[]uint64)(unsafe.Pointer(&sliceHeader{
    89  			Data: unsafe.Pointer(&b[0]),
    90  			Len:  n,
    91  			Cap:  n,
    92  		}))) {
    93  			return false
    94  		}
    95  		b = b[n*8:]
    96  	}
    97  	switch len(b) {
    98  	case 7:
    99  		return bto32(b) == 0 && bto16(b[4:]) == 0 && b[6] == 0
   100  	case 6:
   101  		return bto32(b) == 0 && bto16(b[4:]) == 0
   102  	case 5:
   103  		return bto32(b) == 0 && b[4] == 0
   104  	case 4:
   105  		return bto32(b) == 0
   106  	case 3:
   107  		return bto16(b) == 0 && b[2] == 0
   108  	case 2:
   109  		return bto16(b) == 0
   110  	case 1:
   111  		return b[0] == 0
   112  	default:
   113  		return true
   114  	}
   115  }
   116  
   117  func bto32(b []byte) uint32 {
   118  	return *(*uint32)(unsafe.Pointer(&b[0]))
   119  }
   120  
   121  func bto16(b []byte) uint16 {
   122  	return *(*uint16)(unsafe.Pointer(&b[0]))
   123  }
   124  
   125  func isZeroUint64(b []uint64) bool {
   126  	for i := range b {
   127  		if b[i] != 0 {
   128  			return false
   129  		}
   130  	}
   131  	return true
   132  }
   133  
   134  func byteArrayCodecOf(t reflect.Type, seen map[reflect.Type]*codec) *codec {
   135  	n := t.Len()
   136  	c := &codec{
   137  		wire:   varlen,
   138  		size:   byteArraySizeFuncOf(n),
   139  		encode: byteArrayEncodeFuncOf(n),
   140  		decode: byteArrayDecodeFuncOf(n),
   141  	}
   142  	seen[t] = c
   143  	return c
   144  }
   145  
   146  func byteArraySizeFuncOf(n int) sizeFunc {
   147  	size := sizeOfVarlen(n)
   148  	return func(p unsafe.Pointer, flags flags) int {
   149  		if p != nil && (flags.has(wantzero) || !isZeroBytes(makeBytes(p, n))) {
   150  			return size
   151  		}
   152  		return 0
   153  	}
   154  }
   155  
   156  func byteArrayEncodeFuncOf(n int) encodeFunc {
   157  	return func(b []byte, p unsafe.Pointer, flags flags) (int, error) {
   158  		if p != nil {
   159  			if v := makeBytes(p, n); flags.has(wantzero) || !isZeroBytes(v) {
   160  				return encodeBytes(b, unsafe.Pointer(&v), noflags)
   161  			}
   162  		}
   163  		return 0, nil
   164  	}
   165  }
   166  
   167  func byteArrayDecodeFuncOf(n int) decodeFunc {
   168  	return func(b []byte, p unsafe.Pointer, _ flags) (int, error) {
   169  		v, r, err := decodeVarlen(b)
   170  		if err == nil {
   171  			if copy(makeBytes(p, n), v) != n {
   172  				err = fmt.Errorf("cannot decode byte sequence of size %d into byte array of size %d", len(v), n)
   173  			}
   174  		}
   175  		return r, err
   176  	}
   177  }