diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..20d5f35 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: gomod + directory: "/" + schedule: + interval: daily + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..3914f20 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,17 @@ +name: build + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: '0 0 * * 0' # run "At 00:00 on Sunday" + +# See https://github.com/cristalhq/.github/tree/main/.github/workflows +jobs: + build: + uses: cristalhq/.github/.github/workflows/build.yml@v0.5.0 + + vuln: + uses: cristalhq/.github/.github/workflows/vuln.yml@v0.5.0 diff --git a/bigend/bigend.go b/bigend/bigend.go new file mode 100644 index 0000000..bd24613 --- /dev/null +++ b/bigend/bigend.go @@ -0,0 +1,646 @@ +package bigend + +import ( + "errors" + "io" + "math" + "reflect" + "sync" +) + +func Uint16(b []byte) uint16 { + _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808 + return uint16(b[1]) | uint16(b[0])<<8 +} + +func PutUint16(b []byte, v uint16) { + _ = b[1] // early bounds check to guarantee safety of writes below + b[0] = byte(v >> 8) + b[1] = byte(v) +} + +func AppendUint16(b []byte, v uint16) []byte { + return append(b, + byte(v>>8), + byte(v), + ) +} + +func Uint32(b []byte) uint32 { + _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808 + return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24 +} + +func PutUint32(b []byte, v uint32) { + _ = b[3] // early bounds check to guarantee safety of writes below + b[0] = byte(v >> 24) + b[1] = byte(v >> 16) + b[2] = byte(v >> 8) + b[3] = byte(v) +} + +func AppendUint32(b []byte, v uint32) []byte { + return append(b, + byte(v>>24), + byte(v>>16), + byte(v>>8), + byte(v), + ) +} + +func Uint64(b []byte) uint64 { + _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 + return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | + uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56 +} + +func PutUint64(b []byte, v uint64) { + _ = b[7] // early bounds check to guarantee safety of writes below + b[0] = byte(v >> 56) + b[1] = byte(v >> 48) + b[2] = byte(v >> 40) + b[3] = byte(v >> 32) + b[4] = byte(v >> 24) + b[5] = byte(v >> 16) + b[6] = byte(v >> 8) + b[7] = byte(v) +} + +func AppendUint64(b []byte, v uint64) []byte { + return append(b, + byte(v>>56), + byte(v>>48), + byte(v>>40), + byte(v>>32), + byte(v>>24), + byte(v>>16), + byte(v>>8), + byte(v), + ) +} + +func Read(r io.Reader, data any) error { + // Fast path for basic types and slices. + if n := intDataSize(data); n != 0 { + bs := make([]byte, n) + if _, err := io.ReadFull(r, bs); err != nil { + return err + } + switch data := data.(type) { + case *bool: + *data = bs[0] != 0 + case *int8: + *data = int8(bs[0]) + case *uint8: + *data = bs[0] + case *int16: + *data = int16(Uint16(bs)) + case *uint16: + *data = Uint16(bs) + case *int32: + *data = int32(Uint32(bs)) + case *uint32: + *data = Uint32(bs) + case *int64: + *data = int64(Uint64(bs)) + case *uint64: + *data = Uint64(bs) + case *float32: + *data = math.Float32frombits(Uint32(bs)) + case *float64: + *data = math.Float64frombits(Uint64(bs)) + case []bool: + for i, x := range bs { // Easier to loop over the input for 8-bit values. + data[i] = x != 0 + } + case []int8: + for i, x := range bs { + data[i] = int8(x) + } + case []uint8: + copy(data, bs) + case []int16: + for i := range data { + data[i] = int16(Uint16(bs[2*i:])) + } + case []uint16: + for i := range data { + data[i] = Uint16(bs[2*i:]) + } + case []int32: + for i := range data { + data[i] = int32(Uint32(bs[4*i:])) + } + case []uint32: + for i := range data { + data[i] = Uint32(bs[4*i:]) + } + case []int64: + for i := range data { + data[i] = int64(Uint64(bs[8*i:])) + } + case []uint64: + for i := range data { + data[i] = Uint64(bs[8*i:]) + } + case []float32: + for i := range data { + data[i] = math.Float32frombits(Uint32(bs[4*i:])) + } + case []float64: + for i := range data { + data[i] = math.Float64frombits(Uint64(bs[8*i:])) + } + default: + n = 0 // fast path doesn't apply + } + if n != 0 { + return nil + } + } + + // Fallback to reflect-based decoding. + v := reflect.ValueOf(data) + size := -1 + switch v.Kind() { + case reflect.Pointer: + v = v.Elem() + size = SizeOf(v) + case reflect.Slice: + size = SizeOf(v) + } + if size < 0 { + return errors.New("binary.Read: invalid type " + reflect.TypeOf(data).String()) + } + d := &decoder{buf: make([]byte, size)} + if _, err := io.ReadFull(r, d.buf); err != nil { + return err + } + d.value(v) + return nil +} + +func Write(w io.Writer, data any) error { + // Fast path for basic types and slices. + if n := intDataSize(data); n != 0 { + bs := make([]byte, n) + switch v := data.(type) { + case *bool: + if *v { + bs[0] = 1 + } else { + bs[0] = 0 + } + case bool: + if v { + bs[0] = 1 + } else { + bs[0] = 0 + } + case []bool: + for i, x := range v { + if x { + bs[i] = 1 + } else { + bs[i] = 0 + } + } + case *int8: + bs[0] = byte(*v) + case int8: + bs[0] = byte(v) + case []int8: + for i, x := range v { + bs[i] = byte(x) + } + case *uint8: + bs[0] = *v + case uint8: + bs[0] = v + case []uint8: + bs = v + case *int16: + PutUint16(bs, uint16(*v)) + case int16: + PutUint16(bs, uint16(v)) + case []int16: + for i, x := range v { + PutUint16(bs[2*i:], uint16(x)) + } + case *uint16: + PutUint16(bs, *v) + case uint16: + PutUint16(bs, v) + case []uint16: + for i, x := range v { + PutUint16(bs[2*i:], x) + } + case *int32: + PutUint32(bs, uint32(*v)) + case int32: + PutUint32(bs, uint32(v)) + case []int32: + for i, x := range v { + PutUint32(bs[4*i:], uint32(x)) + } + case *uint32: + PutUint32(bs, *v) + case uint32: + PutUint32(bs, v) + case []uint32: + for i, x := range v { + PutUint32(bs[4*i:], x) + } + case *int64: + PutUint64(bs, uint64(*v)) + case int64: + PutUint64(bs, uint64(v)) + case []int64: + for i, x := range v { + PutUint64(bs[8*i:], uint64(x)) + } + case *uint64: + PutUint64(bs, *v) + case uint64: + PutUint64(bs, v) + case []uint64: + for i, x := range v { + PutUint64(bs[8*i:], x) + } + case *float32: + PutUint32(bs, math.Float32bits(*v)) + case float32: + PutUint32(bs, math.Float32bits(v)) + case []float32: + for i, x := range v { + PutUint32(bs[4*i:], math.Float32bits(x)) + } + case *float64: + PutUint64(bs, math.Float64bits(*v)) + case float64: + PutUint64(bs, math.Float64bits(v)) + case []float64: + for i, x := range v { + PutUint64(bs[8*i:], math.Float64bits(x)) + } + } + _, err := w.Write(bs) + return err + } + + // Fallback to reflect-based encoding. + v := reflect.Indirect(reflect.ValueOf(data)) + size := SizeOf(v) + if size < 0 { + return errors.New("binary.Write: some values are not fixed-sized in type " + reflect.TypeOf(data).String()) + } + buf := make([]byte, size) + e := &encoder{buf: buf} + e.value(v) + _, err := w.Write(buf) + return err +} + +func Size(v any) int { + return SizeOf(reflect.Indirect(reflect.ValueOf(v))) +} + +var structSize sync.Map // map[reflect.Type]int + +func SizeOf(v reflect.Value) int { + switch v.Kind() { + case reflect.Slice: + if s := sizeof(v.Type().Elem()); s >= 0 { + return s * v.Len() + } + + case reflect.Struct: + t := v.Type() + if size, ok := structSize.Load(t); ok { + return size.(int) + } + size := sizeof(t) + structSize.Store(t, size) + return size + + default: + if v.IsValid() { + return sizeof(v.Type()) + } + } + + return -1 +} + +// sizeof returns the size >= 0 of variables for the given type or -1 if the type is not acceptable. +func sizeof(t reflect.Type) int { + switch t.Kind() { + case reflect.Array: + if s := sizeof(t.Elem()); s >= 0 { + return s * t.Len() + } + + case reflect.Struct: + sum := 0 + for i, n := 0, t.NumField(); i < n; i++ { + s := sizeof(t.Field(i).Type) + if s < 0 { + return -1 + } + sum += s + } + return sum + + case reflect.Bool, + reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: + return int(t.Size()) + } + + return -1 +} + +type coder struct { + buf []byte + offset int +} + +type ( + decoder coder + encoder coder +) + +func (d *decoder) bool() bool { + x := d.buf[d.offset] + d.offset++ + return x != 0 +} + +func (e *encoder) bool(x bool) { + if x { + e.buf[e.offset] = 1 + } else { + e.buf[e.offset] = 0 + } + e.offset++ +} + +func (d *decoder) uint8() uint8 { + x := d.buf[d.offset] + d.offset++ + return x +} + +func (e *encoder) uint8(x uint8) { + e.buf[e.offset] = x + e.offset++ +} + +func (d *decoder) uint16() uint16 { + x := Uint16(d.buf[d.offset : d.offset+2]) + d.offset += 2 + return x +} + +func (e *encoder) uint16(x uint16) { + PutUint16(e.buf[e.offset:e.offset+2], x) + e.offset += 2 +} + +func (d *decoder) uint32() uint32 { + x := Uint32(d.buf[d.offset : d.offset+4]) + d.offset += 4 + return x +} + +func (e *encoder) uint32(x uint32) { + PutUint32(e.buf[e.offset:e.offset+4], x) + e.offset += 4 +} + +func (d *decoder) uint64() uint64 { + x := Uint64(d.buf[d.offset : d.offset+8]) + d.offset += 8 + return x +} + +func (e *encoder) uint64(x uint64) { + PutUint64(e.buf[e.offset:e.offset+8], x) + e.offset += 8 +} + +func (d *decoder) int8() int8 { return int8(d.uint8()) } + +func (e *encoder) int8(x int8) { e.uint8(uint8(x)) } + +func (d *decoder) int16() int16 { return int16(d.uint16()) } + +func (e *encoder) int16(x int16) { e.uint16(uint16(x)) } + +func (d *decoder) int32() int32 { return int32(d.uint32()) } + +func (e *encoder) int32(x int32) { e.uint32(uint32(x)) } + +func (d *decoder) int64() int64 { return int64(d.uint64()) } + +func (e *encoder) int64(x int64) { e.uint64(uint64(x)) } + +func (d *decoder) value(v reflect.Value) { + switch v.Kind() { + case reflect.Array: + l := v.Len() + for i := 0; i < l; i++ { + d.value(v.Index(i)) + } + + case reflect.Struct: + t := v.Type() + l := v.NumField() + for i := 0; i < l; i++ { + // Note: Calling v.CanSet() below is an optimization. + // It would be sufficient to check the field name, + // but creating the StructField info for each field is + // costly (run "go test -bench=ReadStruct" and compare + // results when making changes to this code). + if v := v.Field(i); v.CanSet() || t.Field(i).Name != "_" { + d.value(v) + } else { + d.skip(v) + } + } + + case reflect.Slice: + l := v.Len() + for i := 0; i < l; i++ { + d.value(v.Index(i)) + } + + case reflect.Bool: + v.SetBool(d.bool()) + + case reflect.Int8: + v.SetInt(int64(d.int8())) + case reflect.Int16: + v.SetInt(int64(d.int16())) + case reflect.Int32: + v.SetInt(int64(d.int32())) + case reflect.Int64: + v.SetInt(d.int64()) + + case reflect.Uint8: + v.SetUint(uint64(d.uint8())) + case reflect.Uint16: + v.SetUint(uint64(d.uint16())) + case reflect.Uint32: + v.SetUint(uint64(d.uint32())) + case reflect.Uint64: + v.SetUint(d.uint64()) + + case reflect.Float32: + v.SetFloat(float64(math.Float32frombits(d.uint32()))) + case reflect.Float64: + v.SetFloat(math.Float64frombits(d.uint64())) + + case reflect.Complex64: + v.SetComplex(complex( + float64(math.Float32frombits(d.uint32())), + float64(math.Float32frombits(d.uint32())), + )) + case reflect.Complex128: + v.SetComplex(complex( + math.Float64frombits(d.uint64()), + math.Float64frombits(d.uint64()), + )) + } +} + +func (e *encoder) value(v reflect.Value) { + switch v.Kind() { + case reflect.Array: + l := v.Len() + for i := 0; i < l; i++ { + e.value(v.Index(i)) + } + + case reflect.Struct: + t := v.Type() + l := v.NumField() + for i := 0; i < l; i++ { + // see comment for corresponding code in decoder.value() + if v := v.Field(i); v.CanSet() || t.Field(i).Name != "_" { + e.value(v) + } else { + e.skip(v) + } + } + + case reflect.Slice: + l := v.Len() + for i := 0; i < l; i++ { + e.value(v.Index(i)) + } + + case reflect.Bool: + e.bool(v.Bool()) + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch v.Type().Kind() { + case reflect.Int8: + e.int8(int8(v.Int())) + case reflect.Int16: + e.int16(int16(v.Int())) + case reflect.Int32: + e.int32(int32(v.Int())) + case reflect.Int64: + e.int64(v.Int()) + } + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch v.Type().Kind() { + case reflect.Uint8: + e.uint8(uint8(v.Uint())) + case reflect.Uint16: + e.uint16(uint16(v.Uint())) + case reflect.Uint32: + e.uint32(uint32(v.Uint())) + case reflect.Uint64: + e.uint64(v.Uint()) + } + + case reflect.Float32, reflect.Float64: + switch v.Type().Kind() { + case reflect.Float32: + e.uint32(math.Float32bits(float32(v.Float()))) + case reflect.Float64: + e.uint64(math.Float64bits(v.Float())) + } + + case reflect.Complex64, reflect.Complex128: + switch v.Type().Kind() { + case reflect.Complex64: + x := v.Complex() + e.uint32(math.Float32bits(float32(real(x)))) + e.uint32(math.Float32bits(float32(imag(x)))) + case reflect.Complex128: + x := v.Complex() + e.uint64(math.Float64bits(real(x))) + e.uint64(math.Float64bits(imag(x))) + } + } +} + +func (d *decoder) skip(v reflect.Value) { + d.offset += SizeOf(v) +} + +func (e *encoder) skip(v reflect.Value) { + n := SizeOf(v) + zero := e.buf[e.offset : e.offset+n] + for i := range zero { + zero[i] = 0 + } + e.offset += n +} + +// intDataSize returns the size of the data required to represent the data when encoded. +// It returns zero if the type cannot be implemented by the fast path in Read or Write. +func intDataSize(data any) int { + switch data := data.(type) { + case bool, int8, uint8, *bool, *int8, *uint8: + return 1 + case []bool: + return len(data) + case []int8: + return len(data) + case []uint8: + return len(data) + case int16, uint16, *int16, *uint16: + return 2 + case []int16: + return 2 * len(data) + case []uint16: + return 2 * len(data) + case int32, uint32, *int32, *uint32: + return 4 + case []int32: + return 4 * len(data) + case []uint32: + return 4 * len(data) + case int64, uint64, *int64, *uint64: + return 8 + case []int64: + return 8 * len(data) + case []uint64: + return 8 * len(data) + case float32, *float32: + return 4 + case float64, *float64: + return 8 + case []float32: + return 4 * len(data) + case []float64: + return 8 * len(data) + } + return 0 +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..de8f69c --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/go-perf/encoding + +go 1.18 diff --git a/internal/bench/bench_test.go b/internal/bench/bench_test.go new file mode 100644 index 0000000..aee6216 --- /dev/null +++ b/internal/bench/bench_test.go @@ -0,0 +1,839 @@ +package bench + +import ( + "bytes" + "encoding/binary" + "io" + "reflect" + "testing" + + "github.com/go-perf/encoding/bigend" + "github.com/go-perf/encoding/litend" +) + +func BenchmarkReadSlice1000Int32s(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + bsr := &byteSliceReader{} + slice := make([]int32, 1000) + buf := make([]byte, len(slice)*4) + b.SetBytes(int64(len(buf))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + bsr.remain = buf + binary.Read(bsr, binary.BigEndian, slice) + } + }) + b.Run("litend", func(b *testing.B) { + bsr := &byteSliceReader{} + slice := make([]int32, 1000) + buf := make([]byte, len(slice)*4) + b.SetBytes(int64(len(buf))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + bsr.remain = buf + litend.Read(bsr, slice) + } + }) + b.Run("bigend", func(b *testing.B) { + bsr := &byteSliceReader{} + slice := make([]int32, 1000) + buf := make([]byte, len(slice)*4) + b.SetBytes(int64(len(buf))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + bsr.remain = buf + bigend.Read(bsr, slice) + } + }) +} + +// func BenchmarkReadStruct(b *testing.B) { +// b.Run("stdlib", func(b *testing.B) { +// }) +// b.Run("litend", func(b *testing.B) { + +// }) +// b.Run("bigend", func(b *testing.B) { + +// }) +// bsr := &byteSliceReader{} +// var buf bytes.Buffer +// binary.Write(&buf, binary.BigEndian, &s) +// b.SetBytes(int64(dataSize(reflect.ValueOf(s)))) +// t := s +// b.ResetTimer() +// for i := 0; i < b.N; i++ { +// bsr.remain = buf.Bytes() +// binary.Read(bsr, binary.BigEndian, &t) +// } +// b.StopTimer() +// if b.N > 0 && !reflect.DeepEqual(s, t) { +// b.Fatalf("struct doesn't match:\ngot %v;\nwant %v", t, s) +// } +// } + +func BenchmarkWriteStruct(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + b.SetBytes(int64(binary.Size(&s))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + binary.Write(io.Discard, binary.BigEndian, &s) + } + }) + b.Run("litend", func(b *testing.B) { + b.SetBytes(int64(litend.Size(&s))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + litend.Write(io.Discard, &s) + } + }) + b.Run("bigend", func(b *testing.B) { + b.SetBytes(int64(bigend.Size(&s))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + bigend.Write(io.Discard, &s) + } + }) +} + +func BenchmarkReadInts(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + var ls Struct + bsr := &byteSliceReader{} + var r io.Reader = bsr + b.SetBytes(2 * (1 + 2 + 4 + 8)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + bsr.remain = big + binary.Read(r, binary.BigEndian, &ls.Int8) + binary.Read(r, binary.BigEndian, &ls.Int16) + binary.Read(r, binary.BigEndian, &ls.Int32) + binary.Read(r, binary.BigEndian, &ls.Int64) + binary.Read(r, binary.BigEndian, &ls.Uint8) + binary.Read(r, binary.BigEndian, &ls.Uint16) + binary.Read(r, binary.BigEndian, &ls.Uint32) + binary.Read(r, binary.BigEndian, &ls.Uint64) + } + b.StopTimer() + want := s + want.Float32 = 0 + want.Float64 = 0 + want.Complex64 = 0 + want.Complex128 = 0 + want.Array = [4]uint8{0, 0, 0, 0} + want.Bool = false + want.BoolArray = [4]bool{false, false, false, false} + if b.N > 0 && !reflect.DeepEqual(ls, want) { + b.Fatalf("struct doesn't match:\ngot %v;\nwant %v", ls, want) + } + }) + b.Run("litend", func(b *testing.B) { + b.Skip() + var ls Struct + bsr := &byteSliceReader{} + var r io.Reader = bsr + b.SetBytes(2 * (1 + 2 + 4 + 8)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + bsr.remain = big + litend.Read(r, &ls.Int8) + litend.Read(r, &ls.Int16) + litend.Read(r, &ls.Int32) + litend.Read(r, &ls.Int64) + litend.Read(r, &ls.Uint8) + litend.Read(r, &ls.Uint16) + litend.Read(r, &ls.Uint32) + litend.Read(r, &ls.Uint64) + } + b.StopTimer() + want := s + want.Float32 = 0 + want.Float64 = 0 + want.Complex64 = 0 + want.Complex128 = 0 + want.Array = [4]uint8{0, 0, 0, 0} + want.Bool = false + want.BoolArray = [4]bool{false, false, false, false} + if b.N > 0 && !reflect.DeepEqual(ls, want) { + b.Fatalf("struct doesn't match:\ngot %v;\nwant %v", ls, want) + } + }) + b.Run("bigend", func(b *testing.B) { + var ls Struct + bsr := &byteSliceReader{} + var r io.Reader = bsr + b.SetBytes(2 * (1 + 2 + 4 + 8)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + bsr.remain = big + bigend.Read(r, &ls.Int8) + bigend.Read(r, &ls.Int16) + bigend.Read(r, &ls.Int32) + bigend.Read(r, &ls.Int64) + bigend.Read(r, &ls.Uint8) + bigend.Read(r, &ls.Uint16) + bigend.Read(r, &ls.Uint32) + bigend.Read(r, &ls.Uint64) + } + b.StopTimer() + want := s + want.Float32 = 0 + want.Float64 = 0 + want.Complex64 = 0 + want.Complex128 = 0 + want.Array = [4]uint8{0, 0, 0, 0} + want.Bool = false + want.BoolArray = [4]bool{false, false, false, false} + if b.N > 0 && !reflect.DeepEqual(ls, want) { + b.Fatalf("struct doesn't match:\ngot %v;\nwant %v", ls, want) + } + }) +} + +func BenchmarkWriteInts(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + buf := new(bytes.Buffer) + var w io.Writer = buf + b.SetBytes(2 * (1 + 2 + 4 + 8)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + buf.Reset() + binary.Write(w, binary.BigEndian, s.Int8) + binary.Write(w, binary.BigEndian, s.Int16) + binary.Write(w, binary.BigEndian, s.Int32) + binary.Write(w, binary.BigEndian, s.Int64) + binary.Write(w, binary.BigEndian, s.Uint8) + binary.Write(w, binary.BigEndian, s.Uint16) + binary.Write(w, binary.BigEndian, s.Uint32) + binary.Write(w, binary.BigEndian, s.Uint64) + } + b.StopTimer() + if b.N > 0 && !bytes.Equal(buf.Bytes(), big[:30]) { + b.Fatalf("first half doesn't match: %x %x", buf.Bytes(), big[:30]) + } + }) + b.Run("litend", func(b *testing.B) { + b.Skip() + buf := new(bytes.Buffer) + var w io.Writer = buf + b.SetBytes(2 * (1 + 2 + 4 + 8)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + buf.Reset() + litend.Write(w, s.Int8) + litend.Write(w, s.Int16) + litend.Write(w, s.Int32) + litend.Write(w, s.Int64) + litend.Write(w, s.Uint8) + litend.Write(w, s.Uint16) + litend.Write(w, s.Uint32) + litend.Write(w, s.Uint64) + } + b.StopTimer() + if b.N > 0 && !bytes.Equal(buf.Bytes(), big[:30]) { + b.Fatalf("first half doesn't match: %x %x", buf.Bytes(), big[:30]) + } + }) + b.Run("bigend", func(b *testing.B) { + buf := new(bytes.Buffer) + var w io.Writer = buf + b.SetBytes(2 * (1 + 2 + 4 + 8)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + buf.Reset() + bigend.Write(w, s.Int8) + bigend.Write(w, s.Int16) + bigend.Write(w, s.Int32) + bigend.Write(w, s.Int64) + bigend.Write(w, s.Uint8) + bigend.Write(w, s.Uint16) + bigend.Write(w, s.Uint32) + bigend.Write(w, s.Uint64) + } + b.StopTimer() + if b.N > 0 && !bytes.Equal(buf.Bytes(), big[:30]) { + b.Fatalf("first half doesn't match: %x %x", buf.Bytes(), big[:30]) + } + }) +} + +func BenchmarkWriteSlice1000Int32s(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + slice := make([]int32, 1000) + buf := new(bytes.Buffer) + var w io.Writer = buf + b.SetBytes(4 * 1000) + b.ResetTimer() + for i := 0; i < b.N; i++ { + buf.Reset() + binary.Write(w, binary.BigEndian, slice) + } + b.StopTimer() + }) + b.Run("litend", func(b *testing.B) { + slice := make([]int32, 1000) + buf := new(bytes.Buffer) + var w io.Writer = buf + b.SetBytes(4 * 1000) + b.ResetTimer() + for i := 0; i < b.N; i++ { + buf.Reset() + litend.Write(w, slice) + } + b.StopTimer() + }) + b.Run("bigend", func(b *testing.B) { + slice := make([]int32, 1000) + buf := new(bytes.Buffer) + var w io.Writer = buf + b.SetBytes(4 * 1000) + b.ResetTimer() + for i := 0; i < b.N; i++ { + buf.Reset() + bigend.Write(w, slice) + } + b.StopTimer() + }) +} + +func BenchmarkPutUint16(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + b.SetBytes(2) + for i := 0; i < b.N; i++ { + binary.BigEndian.PutUint16(putbuf[:2], uint16(i)) + } + }) + b.Run("litend", func(b *testing.B) { + b.SetBytes(2) + for i := 0; i < b.N; i++ { + litend.PutUint16(putbuf[:2], uint16(i)) + } + }) + b.Run("bigend", func(b *testing.B) { + b.SetBytes(2) + for i := 0; i < b.N; i++ { + bigend.PutUint16(putbuf[:2], uint16(i)) + } + }) +} + +func BenchmarkAppendUint16(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + b.SetBytes(2) + for i := 0; i < b.N; i++ { + putbuf = binary.BigEndian.AppendUint16(putbuf[:0], uint16(i)) + } + }) + b.Run("litend", func(b *testing.B) { + b.SetBytes(2) + for i := 0; i < b.N; i++ { + putbuf = litend.AppendUint16(putbuf[:0], uint16(i)) + } + }) + b.Run("bigend", func(b *testing.B) { + b.SetBytes(2) + for i := 0; i < b.N; i++ { + putbuf = bigend.AppendUint16(putbuf[:0], uint16(i)) + } + }) +} + +func BenchmarkPutUint32(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + b.SetBytes(4) + for i := 0; i < b.N; i++ { + binary.BigEndian.PutUint32(putbuf[:4], uint32(i)) + } + }) + b.Run("litend", func(b *testing.B) { + b.SetBytes(4) + for i := 0; i < b.N; i++ { + litend.PutUint32(putbuf[:4], uint32(i)) + } + }) + b.Run("bigend", func(b *testing.B) { + b.SetBytes(4) + for i := 0; i < b.N; i++ { + bigend.PutUint32(putbuf[:4], uint32(i)) + } + }) +} + +func BenchmarkAppendUint32(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + b.SetBytes(4) + for i := 0; i < b.N; i++ { + putbuf = binary.BigEndian.AppendUint32(putbuf[:0], uint32(i)) + } + }) + b.Run("litend", func(b *testing.B) { + b.SetBytes(4) + for i := 0; i < b.N; i++ { + putbuf = litend.AppendUint32(putbuf[:0], uint32(i)) + } + }) + b.Run("bigend", func(b *testing.B) { + b.SetBytes(4) + for i := 0; i < b.N; i++ { + putbuf = bigend.AppendUint32(putbuf[:0], uint32(i)) + } + }) +} + +func BenchmarkPutUint64(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + b.SetBytes(8) + for i := 0; i < b.N; i++ { + binary.BigEndian.PutUint64(putbuf[:8], uint64(i)) + } + }) + b.Run("litend", func(b *testing.B) { + b.SetBytes(8) + for i := 0; i < b.N; i++ { + litend.PutUint64(putbuf[:8], uint64(i)) + } + }) + b.Run("bigend", func(b *testing.B) { + b.SetBytes(8) + for i := 0; i < b.N; i++ { + bigend.PutUint64(putbuf[:8], uint64(i)) + } + }) +} + +func BenchmarkAppendUint64(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + b.SetBytes(8) + for i := 0; i < b.N; i++ { + putbuf = binary.BigEndian.AppendUint64(putbuf[:0], uint64(i)) + } + }) + b.Run("litend", func(b *testing.B) { + b.SetBytes(8) + for i := 0; i < b.N; i++ { + putbuf = litend.AppendUint64(putbuf[:0], uint64(i)) + } + }) + b.Run("bigend", func(b *testing.B) { + b.SetBytes(8) + for i := 0; i < b.N; i++ { + putbuf = bigend.AppendUint64(putbuf[:0], uint64(i)) + } + }) +} + +func BenchmarkLittleEndianPutUint16(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + b.SetBytes(2) + for i := 0; i < b.N; i++ { + binary.LittleEndian.PutUint16(putbuf[:2], uint16(i)) + } + }) + b.Run("litend", func(b *testing.B) { + b.SetBytes(2) + for i := 0; i < b.N; i++ { + litend.PutUint16(putbuf[:2], uint16(i)) + } + }) + b.Run("bigend", func(b *testing.B) { + b.SetBytes(2) + for i := 0; i < b.N; i++ { + bigend.PutUint16(putbuf[:2], uint16(i)) + } + }) +} + +func BenchmarkLittleEndianAppendUint16(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + b.SetBytes(2) + for i := 0; i < b.N; i++ { + putbuf = binary.LittleEndian.AppendUint16(putbuf[:0], uint16(i)) + } + }) + b.Run("litend", func(b *testing.B) { + b.SetBytes(2) + for i := 0; i < b.N; i++ { + putbuf = litend.AppendUint16(putbuf[:0], uint16(i)) + } + }) + b.Run("bigend", func(b *testing.B) { + b.SetBytes(2) + for i := 0; i < b.N; i++ { + putbuf = bigend.AppendUint16(putbuf[:0], uint16(i)) + } + }) +} + +func BenchmarkLittleEndianPutUint32(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + b.SetBytes(4) + for i := 0; i < b.N; i++ { + binary.LittleEndian.PutUint32(putbuf[:4], uint32(i)) + } + }) + b.Run("litend", func(b *testing.B) { + b.SetBytes(4) + for i := 0; i < b.N; i++ { + litend.PutUint32(putbuf[:4], uint32(i)) + } + }) + b.Run("bigend", func(b *testing.B) { + b.SetBytes(4) + for i := 0; i < b.N; i++ { + bigend.PutUint32(putbuf[:4], uint32(i)) + } + }) +} + +func BenchmarkLittleEndianAppendUint32(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + b.SetBytes(4) + for i := 0; i < b.N; i++ { + putbuf = binary.LittleEndian.AppendUint32(putbuf[:0], uint32(i)) + } + }) + b.Run("litend", func(b *testing.B) { + b.SetBytes(4) + for i := 0; i < b.N; i++ { + putbuf = litend.AppendUint32(putbuf[:0], uint32(i)) + } + }) + b.Run("bigend", func(b *testing.B) { + b.SetBytes(4) + for i := 0; i < b.N; i++ { + putbuf = bigend.AppendUint32(putbuf[:0], uint32(i)) + } + }) +} + +func BenchmarkLittleEndianPutUint64(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + b.SetBytes(8) + for i := 0; i < b.N; i++ { + binary.LittleEndian.PutUint64(putbuf[:8], uint64(i)) + } + }) + b.Run("litend", func(b *testing.B) { + b.SetBytes(8) + for i := 0; i < b.N; i++ { + litend.PutUint64(putbuf[:8], uint64(i)) + } + }) + b.Run("bigend", func(b *testing.B) { + b.SetBytes(8) + for i := 0; i < b.N; i++ { + bigend.PutUint64(putbuf[:8], uint64(i)) + } + }) +} + +func BenchmarkLittleEndianAppendUint64(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + b.SetBytes(8) + for i := 0; i < b.N; i++ { + putbuf = binary.LittleEndian.AppendUint64(putbuf[:0], uint64(i)) + } + }) + b.Run("litend", func(b *testing.B) { + b.SetBytes(8) + for i := 0; i < b.N; i++ { + putbuf = litend.AppendUint64(putbuf[:0], uint64(i)) + } + }) + b.Run("bigend", func(b *testing.B) { + b.SetBytes(8) + for i := 0; i < b.N; i++ { + putbuf = bigend.AppendUint64(putbuf[:0], uint64(i)) + } + }) +} + +func BenchmarkReadFloats(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + var ls Struct + bsr := &byteSliceReader{} + var r io.Reader = bsr + b.SetBytes(4 + 8) + b.ResetTimer() + for i := 0; i < b.N; i++ { + bsr.remain = big[30:] + binary.Read(r, binary.BigEndian, &ls.Float32) + binary.Read(r, binary.BigEndian, &ls.Float64) + } + b.StopTimer() + want := s + want.Int8 = 0 + want.Int16 = 0 + want.Int32 = 0 + want.Int64 = 0 + want.Uint8 = 0 + want.Uint16 = 0 + want.Uint32 = 0 + want.Uint64 = 0 + want.Complex64 = 0 + want.Complex128 = 0 + want.Array = [4]uint8{0, 0, 0, 0} + want.Bool = false + want.BoolArray = [4]bool{false, false, false, false} + if b.N > 0 && !reflect.DeepEqual(ls, want) { + b.Fatalf("struct doesn't match:\ngot %v;\nwant %v", ls, want) + } + }) + b.Run("litend", func(b *testing.B) { + b.Skip() + var ls Struct + bsr := &byteSliceReader{} + var r io.Reader = bsr + b.SetBytes(4 + 8) + b.ResetTimer() + for i := 0; i < b.N; i++ { + bsr.remain = big[30:] + litend.Read(r, &ls.Float32) + litend.Read(r, &ls.Float64) + } + b.StopTimer() + want := s + want.Int8 = 0 + want.Int16 = 0 + want.Int32 = 0 + want.Int64 = 0 + want.Uint8 = 0 + want.Uint16 = 0 + want.Uint32 = 0 + want.Uint64 = 0 + want.Complex64 = 0 + want.Complex128 = 0 + want.Array = [4]uint8{0, 0, 0, 0} + want.Bool = false + want.BoolArray = [4]bool{false, false, false, false} + if b.N > 0 && !reflect.DeepEqual(ls, want) { + b.Fatalf("struct doesn't match:\ngot %v;\nwant %v", ls, want) + } + }) + b.Run("bigend", func(b *testing.B) { + var ls Struct + bsr := &byteSliceReader{} + var r io.Reader = bsr + b.SetBytes(4 + 8) + b.ResetTimer() + for i := 0; i < b.N; i++ { + bsr.remain = big[30:] + bigend.Read(r, &ls.Float32) + bigend.Read(r, &ls.Float64) + } + b.StopTimer() + want := s + want.Int8 = 0 + want.Int16 = 0 + want.Int32 = 0 + want.Int64 = 0 + want.Uint8 = 0 + want.Uint16 = 0 + want.Uint32 = 0 + want.Uint64 = 0 + want.Complex64 = 0 + want.Complex128 = 0 + want.Array = [4]uint8{0, 0, 0, 0} + want.Bool = false + want.BoolArray = [4]bool{false, false, false, false} + if b.N > 0 && !reflect.DeepEqual(ls, want) { + b.Fatalf("struct doesn't match:\ngot %v;\nwant %v", ls, want) + } + }) +} + +func BenchmarkWriteFloats(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + buf := new(bytes.Buffer) + var w io.Writer = buf + b.SetBytes(4 + 8) + b.ResetTimer() + for i := 0; i < b.N; i++ { + buf.Reset() + binary.Write(w, binary.BigEndian, s.Float32) + binary.Write(w, binary.BigEndian, s.Float64) + } + b.StopTimer() + if b.N > 0 && !bytes.Equal(buf.Bytes(), big[30:30+4+8]) { + b.Fatalf("first half doesn't match: %x %x", buf.Bytes(), big[30:30+4+8]) + } + }) + b.Run("litend", func(b *testing.B) { + b.Skip() + buf := new(bytes.Buffer) + var w io.Writer = buf + b.SetBytes(4 + 8) + b.ResetTimer() + for i := 0; i < b.N; i++ { + buf.Reset() + litend.Write(w, s.Float32) + litend.Write(w, s.Float64) + } + b.StopTimer() + if b.N > 0 && !bytes.Equal(buf.Bytes(), big[30:30+4+8]) { + b.Fatalf("first half doesn't match: %x %x", buf.Bytes(), big[30:30+4+8]) + } + }) + b.Run("bigend", func(b *testing.B) { + buf := new(bytes.Buffer) + var w io.Writer = buf + b.SetBytes(4 + 8) + b.ResetTimer() + for i := 0; i < b.N; i++ { + buf.Reset() + bigend.Write(w, s.Float32) + bigend.Write(w, s.Float64) + } + b.StopTimer() + if b.N > 0 && !bytes.Equal(buf.Bytes(), big[30:30+4+8]) { + b.Fatalf("first half doesn't match: %x %x", buf.Bytes(), big[30:30+4+8]) + } + }) +} + +func BenchmarkReadSlice1000Float32s(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + bsr := &byteSliceReader{} + slice := make([]float32, 1000) + buf := make([]byte, len(slice)*4) + b.SetBytes(int64(len(buf))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + bsr.remain = buf + binary.Read(bsr, binary.BigEndian, slice) + } + }) + b.Run("litend", func(b *testing.B) { + bsr := &byteSliceReader{} + slice := make([]float32, 1000) + buf := make([]byte, len(slice)*4) + b.SetBytes(int64(len(buf))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + bsr.remain = buf + litend.Read(bsr, slice) + } + }) + b.Run("bigend", func(b *testing.B) { + bsr := &byteSliceReader{} + slice := make([]float32, 1000) + buf := make([]byte, len(slice)*4) + b.SetBytes(int64(len(buf))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + bsr.remain = buf + bigend.Read(bsr, slice) + } + }) +} + +func BenchmarkWriteSlice1000Float32s(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + slice := make([]float32, 1000) + buf := new(bytes.Buffer) + var w io.Writer = buf + b.SetBytes(4 * 1000) + b.ResetTimer() + for i := 0; i < b.N; i++ { + buf.Reset() + binary.Write(w, binary.BigEndian, slice) + } + b.StopTimer() + }) + b.Run("litend", func(b *testing.B) { + slice := make([]float32, 1000) + buf := new(bytes.Buffer) + var w io.Writer = buf + b.SetBytes(4 * 1000) + b.ResetTimer() + for i := 0; i < b.N; i++ { + buf.Reset() + litend.Write(w, slice) + } + b.StopTimer() + }) + b.Run("bigend", func(b *testing.B) { + slice := make([]float32, 1000) + buf := new(bytes.Buffer) + var w io.Writer = buf + b.SetBytes(4 * 1000) + b.ResetTimer() + for i := 0; i < b.N; i++ { + buf.Reset() + bigend.Write(w, slice) + } + b.StopTimer() + }) +} + +func BenchmarkReadSlice1000Uint8s(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + bsr := &byteSliceReader{} + slice := make([]uint8, 1000) + buf := make([]byte, len(slice)) + b.SetBytes(int64(len(buf))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + bsr.remain = buf + binary.Read(bsr, binary.BigEndian, slice) + } + }) + b.Run("litend", func(b *testing.B) { + bsr := &byteSliceReader{} + slice := make([]uint8, 1000) + buf := make([]byte, len(slice)) + b.SetBytes(int64(len(buf))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + bsr.remain = buf + litend.Read(bsr, slice) + } + }) + b.Run("bigend", func(b *testing.B) { + bsr := &byteSliceReader{} + slice := make([]uint8, 1000) + buf := make([]byte, len(slice)) + b.SetBytes(int64(len(buf))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + bsr.remain = buf + bigend.Read(bsr, slice) + } + }) +} + +func BenchmarkWriteSlice1000Uint8s(b *testing.B) { + b.Run("stdlib", func(b *testing.B) { + slice := make([]uint8, 1000) + buf := new(bytes.Buffer) + var w io.Writer = buf + b.SetBytes(1000) + b.ResetTimer() + for i := 0; i < b.N; i++ { + buf.Reset() + binary.Write(w, binary.BigEndian, slice) + } + }) + b.Run("litend", func(b *testing.B) { + slice := make([]uint8, 1000) + buf := new(bytes.Buffer) + var w io.Writer = buf + b.SetBytes(1000) + b.ResetTimer() + for i := 0; i < b.N; i++ { + buf.Reset() + litend.Write(w, slice) + } + }) + b.Run("bigend", func(b *testing.B) { + slice := make([]uint8, 1000) + buf := new(bytes.Buffer) + var w io.Writer = buf + b.SetBytes(1000) + b.ResetTimer() + for i := 0; i < b.N; i++ { + buf.Reset() + bigend.Write(w, slice) + } + }) +} diff --git a/internal/bench/test.go b/internal/bench/test.go new file mode 100644 index 0000000..da88ce6 --- /dev/null +++ b/internal/bench/test.go @@ -0,0 +1,113 @@ +package bench + +import "math" + +type byteSliceReader struct { + remain []byte +} + +func (br *byteSliceReader) Read(p []byte) (int, error) { + n := copy(p, br.remain) + br.remain = br.remain[n:] + return n, nil +} + +type Struct struct { + Int8 int8 + Int16 int16 + Int32 int32 + Int64 int64 + Uint8 uint8 + Uint16 uint16 + Uint32 uint32 + Uint64 uint64 + Float32 float32 + Float64 float64 + Complex64 complex64 + Complex128 complex128 + Array [4]uint8 + Bool bool + BoolArray [4]bool +} + +type T struct { + Int int + Uint uint + Uintptr uintptr + Array [4]int +} + +var s = Struct{ + 0x01, + 0x0203, + 0x04050607, + 0x08090a0b0c0d0e0f, + 0x10, + 0x1112, + 0x13141516, + 0x1718191a1b1c1d1e, + + math.Float32frombits(0x1f202122), + math.Float64frombits(0x232425262728292a), + complex( + math.Float32frombits(0x2b2c2d2e), + math.Float32frombits(0x2f303132), + ), + complex( + math.Float64frombits(0x333435363738393a), + math.Float64frombits(0x3b3c3d3e3f404142), + ), + + [4]uint8{0x43, 0x44, 0x45, 0x46}, + + true, + [4]bool{true, false, true, false}, +} + +var big = []byte{ + 1, + 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, + 17, 18, + 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30, + + 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, + + 67, 68, 69, 70, + + 1, + 1, 0, 1, 0, +} + +var little = []byte{ + 1, + 3, 2, + 7, 6, 5, 4, + 15, 14, 13, 12, 11, 10, 9, 8, + 16, + 18, 17, + 22, 21, 20, 19, + 30, 29, 28, 27, 26, 25, 24, 23, + + 34, 33, 32, 31, + 42, 41, 40, 39, 38, 37, 36, 35, + 46, 45, 44, 43, 50, 49, 48, 47, + 58, 57, 56, 55, 54, 53, 52, 51, 66, 65, 64, 63, 62, 61, 60, 59, + + 67, 68, 69, 70, + + 1, + 1, 0, 1, 0, +} + +var ( + src = []byte{1, 2, 3, 4, 5, 6, 7, 8} + res = []int32{0x01020304, 0x05060708} + putbuf = []byte{0, 0, 0, 0, 0, 0, 0, 0} +) diff --git a/litend/litend.go b/litend/litend.go new file mode 100644 index 0000000..fedf9ed --- /dev/null +++ b/litend/litend.go @@ -0,0 +1,646 @@ +package litend + +import ( + "errors" + "io" + "math" + "reflect" + "sync" +) + +func Uint16(b []byte) uint16 { + _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808 + return uint16(b[0]) | uint16(b[1])<<8 +} + +func PutUint16(b []byte, v uint16) { + _ = b[1] // early bounds check to guarantee safety of writes below + b[0] = byte(v) + b[1] = byte(v >> 8) +} + +func AppendUint16(b []byte, v uint16) []byte { + return append(b, + byte(v), + byte(v>>8), + ) +} + +func Uint32(b []byte) uint32 { + _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808 + return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 +} + +func PutUint32(b []byte, v uint32) { + _ = b[3] // early bounds check to guarantee safety of writes below + b[0] = byte(v) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + b[3] = byte(v >> 24) +} + +func AppendUint32(b []byte, v uint32) []byte { + return append(b, + byte(v), + byte(v>>8), + byte(v>>16), + byte(v>>24), + ) +} + +func Uint64(b []byte) uint64 { + _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 + return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | + uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 +} + +func PutUint64(b []byte, v uint64) { + _ = b[7] // early bounds check to guarantee safety of writes below + b[0] = byte(v) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + b[3] = byte(v >> 24) + b[4] = byte(v >> 32) + b[5] = byte(v >> 40) + b[6] = byte(v >> 48) + b[7] = byte(v >> 56) +} + +func AppendUint64(b []byte, v uint64) []byte { + return append(b, + byte(v), + byte(v>>8), + byte(v>>16), + byte(v>>24), + byte(v>>32), + byte(v>>40), + byte(v>>48), + byte(v>>56), + ) +} + +func Read(r io.Reader, data any) error { + // Fast path for basic types and slices. + if n := intSizeOf(data); n != 0 { + bs := make([]byte, n) + if _, err := io.ReadFull(r, bs); err != nil { + return err + } + switch data := data.(type) { + case *bool: + *data = bs[0] != 0 + case *int8: + *data = int8(bs[0]) + case *uint8: + *data = bs[0] + case *int16: + *data = int16(Uint16(bs)) + case *uint16: + *data = Uint16(bs) + case *int32: + *data = int32(Uint32(bs)) + case *uint32: + *data = Uint32(bs) + case *int64: + *data = int64(Uint64(bs)) + case *uint64: + *data = Uint64(bs) + case *float32: + *data = math.Float32frombits(Uint32(bs)) + case *float64: + *data = math.Float64frombits(Uint64(bs)) + case []bool: + for i, x := range bs { // Easier to loop over the input for 8-bit values. + data[i] = x != 0 + } + case []int8: + for i, x := range bs { + data[i] = int8(x) + } + case []uint8: + copy(data, bs) + case []int16: + for i := range data { + data[i] = int16(Uint16(bs[2*i:])) + } + case []uint16: + for i := range data { + data[i] = Uint16(bs[2*i:]) + } + case []int32: + for i := range data { + data[i] = int32(Uint32(bs[4*i:])) + } + case []uint32: + for i := range data { + data[i] = Uint32(bs[4*i:]) + } + case []int64: + for i := range data { + data[i] = int64(Uint64(bs[8*i:])) + } + case []uint64: + for i := range data { + data[i] = Uint64(bs[8*i:]) + } + case []float32: + for i := range data { + data[i] = math.Float32frombits(Uint32(bs[4*i:])) + } + case []float64: + for i := range data { + data[i] = math.Float64frombits(Uint64(bs[8*i:])) + } + default: + n = 0 // fast path doesn't apply + } + if n != 0 { + return nil + } + } + + // Fallback to reflect-based decoding. + v := reflect.ValueOf(data) + size := -1 + switch v.Kind() { + case reflect.Pointer: + v = v.Elem() + size = SizeOf(v) + case reflect.Slice: + size = SizeOf(v) + } + if size < 0 { + return errors.New("binary.Read: invalid type " + reflect.TypeOf(data).String()) + } + d := &decoder{buf: make([]byte, size)} + if _, err := io.ReadFull(r, d.buf); err != nil { + return err + } + d.value(v) + return nil +} + +func Write(w io.Writer, data any) error { + // Fast path for basic types and slices. + if n := intSizeOf(data); n != 0 { + bs := make([]byte, n) + switch v := data.(type) { + case *bool: + if *v { + bs[0] = 1 + } else { + bs[0] = 0 + } + case bool: + if v { + bs[0] = 1 + } else { + bs[0] = 0 + } + case []bool: + for i, x := range v { + if x { + bs[i] = 1 + } else { + bs[i] = 0 + } + } + case *int8: + bs[0] = byte(*v) + case int8: + bs[0] = byte(v) + case []int8: + for i, x := range v { + bs[i] = byte(x) + } + case *uint8: + bs[0] = *v + case uint8: + bs[0] = v + case []uint8: + bs = v + case *int16: + PutUint16(bs, uint16(*v)) + case int16: + PutUint16(bs, uint16(v)) + case []int16: + for i, x := range v { + PutUint16(bs[2*i:], uint16(x)) + } + case *uint16: + PutUint16(bs, *v) + case uint16: + PutUint16(bs, v) + case []uint16: + for i, x := range v { + PutUint16(bs[2*i:], x) + } + case *int32: + PutUint32(bs, uint32(*v)) + case int32: + PutUint32(bs, uint32(v)) + case []int32: + for i, x := range v { + PutUint32(bs[4*i:], uint32(x)) + } + case *uint32: + PutUint32(bs, *v) + case uint32: + PutUint32(bs, v) + case []uint32: + for i, x := range v { + PutUint32(bs[4*i:], x) + } + case *int64: + PutUint64(bs, uint64(*v)) + case int64: + PutUint64(bs, uint64(v)) + case []int64: + for i, x := range v { + PutUint64(bs[8*i:], uint64(x)) + } + case *uint64: + PutUint64(bs, *v) + case uint64: + PutUint64(bs, v) + case []uint64: + for i, x := range v { + PutUint64(bs[8*i:], x) + } + case *float32: + PutUint32(bs, math.Float32bits(*v)) + case float32: + PutUint32(bs, math.Float32bits(v)) + case []float32: + for i, x := range v { + PutUint32(bs[4*i:], math.Float32bits(x)) + } + case *float64: + PutUint64(bs, math.Float64bits(*v)) + case float64: + PutUint64(bs, math.Float64bits(v)) + case []float64: + for i, x := range v { + PutUint64(bs[8*i:], math.Float64bits(x)) + } + } + _, err := w.Write(bs) + return err + } + + // Fallback to reflect-based encoding. + v := reflect.Indirect(reflect.ValueOf(data)) + size := SizeOf(v) + if size < 0 { + return errors.New("binary.Write: some values are not fixed-sized in type " + reflect.TypeOf(data).String()) + } + buf := make([]byte, size) + e := &encoder{buf: buf} + e.value(v) + _, err := w.Write(buf) + return err +} + +func Size(v any) int { + return SizeOf(reflect.Indirect(reflect.ValueOf(v))) +} + +var structSize sync.Map // map[reflect.Type]int + +func SizeOf(v reflect.Value) int { + switch v.Kind() { + case reflect.Slice: + if s := sizeof(v.Type().Elem()); s >= 0 { + return s * v.Len() + } + + case reflect.Struct: + t := v.Type() + if size, ok := structSize.Load(t); ok { + return size.(int) + } + size := sizeof(t) + structSize.Store(t, size) + return size + + default: + if v.IsValid() { + return sizeof(v.Type()) + } + } + + return -1 +} + +// sizeof returns the size >= 0 of variables for the given type or -1 if the type is not acceptable. +func sizeof(t reflect.Type) int { + switch t.Kind() { + case reflect.Array: + if s := sizeof(t.Elem()); s >= 0 { + return s * t.Len() + } + + case reflect.Struct: + sum := 0 + for i, n := 0, t.NumField(); i < n; i++ { + s := sizeof(t.Field(i).Type) + if s < 0 { + return -1 + } + sum += s + } + return sum + + case reflect.Bool, + reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: + return int(t.Size()) + } + + return -1 +} + +type coder struct { + buf []byte + offset int +} + +type ( + decoder coder + encoder coder +) + +func (d *decoder) bool() bool { + x := d.buf[d.offset] + d.offset++ + return x != 0 +} + +func (e *encoder) bool(x bool) { + if x { + e.buf[e.offset] = 1 + } else { + e.buf[e.offset] = 0 + } + e.offset++ +} + +func (d *decoder) uint8() uint8 { + x := d.buf[d.offset] + d.offset++ + return x +} + +func (e *encoder) uint8(x uint8) { + e.buf[e.offset] = x + e.offset++ +} + +func (d *decoder) uint16() uint16 { + x := Uint16(d.buf[d.offset : d.offset+2]) + d.offset += 2 + return x +} + +func (e *encoder) uint16(x uint16) { + PutUint16(e.buf[e.offset:e.offset+2], x) + e.offset += 2 +} + +func (d *decoder) uint32() uint32 { + x := Uint32(d.buf[d.offset : d.offset+4]) + d.offset += 4 + return x +} + +func (e *encoder) uint32(x uint32) { + PutUint32(e.buf[e.offset:e.offset+4], x) + e.offset += 4 +} + +func (d *decoder) uint64() uint64 { + x := Uint64(d.buf[d.offset : d.offset+8]) + d.offset += 8 + return x +} + +func (e *encoder) uint64(x uint64) { + PutUint64(e.buf[e.offset:e.offset+8], x) + e.offset += 8 +} + +func (d *decoder) int8() int8 { return int8(d.uint8()) } + +func (e *encoder) int8(x int8) { e.uint8(uint8(x)) } + +func (d *decoder) int16() int16 { return int16(d.uint16()) } + +func (e *encoder) int16(x int16) { e.uint16(uint16(x)) } + +func (d *decoder) int32() int32 { return int32(d.uint32()) } + +func (e *encoder) int32(x int32) { e.uint32(uint32(x)) } + +func (d *decoder) int64() int64 { return int64(d.uint64()) } + +func (e *encoder) int64(x int64) { e.uint64(uint64(x)) } + +func (d *decoder) value(v reflect.Value) { + switch v.Kind() { + case reflect.Array: + l := v.Len() + for i := 0; i < l; i++ { + d.value(v.Index(i)) + } + + case reflect.Struct: + t := v.Type() + l := v.NumField() + for i := 0; i < l; i++ { + // Note: Calling v.CanSet() below is an optimization. + // It would be sufficient to check the field name, + // but creating the StructField info for each field is + // costly (run "go test -bench=ReadStruct" and compare + // results when making changes to this code). + if v := v.Field(i); v.CanSet() || t.Field(i).Name != "_" { + d.value(v) + } else { + d.skip(v) + } + } + + case reflect.Slice: + l := v.Len() + for i := 0; i < l; i++ { + d.value(v.Index(i)) + } + + case reflect.Bool: + v.SetBool(d.bool()) + + case reflect.Int8: + v.SetInt(int64(d.int8())) + case reflect.Int16: + v.SetInt(int64(d.int16())) + case reflect.Int32: + v.SetInt(int64(d.int32())) + case reflect.Int64: + v.SetInt(d.int64()) + + case reflect.Uint8: + v.SetUint(uint64(d.uint8())) + case reflect.Uint16: + v.SetUint(uint64(d.uint16())) + case reflect.Uint32: + v.SetUint(uint64(d.uint32())) + case reflect.Uint64: + v.SetUint(d.uint64()) + + case reflect.Float32: + v.SetFloat(float64(math.Float32frombits(d.uint32()))) + case reflect.Float64: + v.SetFloat(math.Float64frombits(d.uint64())) + + case reflect.Complex64: + v.SetComplex(complex( + float64(math.Float32frombits(d.uint32())), + float64(math.Float32frombits(d.uint32())), + )) + case reflect.Complex128: + v.SetComplex(complex( + math.Float64frombits(d.uint64()), + math.Float64frombits(d.uint64()), + )) + } +} + +func (e *encoder) value(v reflect.Value) { + switch v.Kind() { + case reflect.Array: + l := v.Len() + for i := 0; i < l; i++ { + e.value(v.Index(i)) + } + + case reflect.Struct: + t := v.Type() + l := v.NumField() + for i := 0; i < l; i++ { + // see comment for corresponding code in decoder.value() + if v := v.Field(i); v.CanSet() || t.Field(i).Name != "_" { + e.value(v) + } else { + e.skip(v) + } + } + + case reflect.Slice: + l := v.Len() + for i := 0; i < l; i++ { + e.value(v.Index(i)) + } + + case reflect.Bool: + e.bool(v.Bool()) + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch v.Type().Kind() { + case reflect.Int8: + e.int8(int8(v.Int())) + case reflect.Int16: + e.int16(int16(v.Int())) + case reflect.Int32: + e.int32(int32(v.Int())) + case reflect.Int64: + e.int64(v.Int()) + } + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch v.Type().Kind() { + case reflect.Uint8: + e.uint8(uint8(v.Uint())) + case reflect.Uint16: + e.uint16(uint16(v.Uint())) + case reflect.Uint32: + e.uint32(uint32(v.Uint())) + case reflect.Uint64: + e.uint64(v.Uint()) + } + + case reflect.Float32, reflect.Float64: + switch v.Type().Kind() { + case reflect.Float32: + e.uint32(math.Float32bits(float32(v.Float()))) + case reflect.Float64: + e.uint64(math.Float64bits(v.Float())) + } + + case reflect.Complex64, reflect.Complex128: + switch v.Type().Kind() { + case reflect.Complex64: + x := v.Complex() + e.uint32(math.Float32bits(float32(real(x)))) + e.uint32(math.Float32bits(float32(imag(x)))) + case reflect.Complex128: + x := v.Complex() + e.uint64(math.Float64bits(real(x))) + e.uint64(math.Float64bits(imag(x))) + } + } +} + +func (d *decoder) skip(v reflect.Value) { + d.offset += SizeOf(v) +} + +func (e *encoder) skip(v reflect.Value) { + n := SizeOf(v) + zero := e.buf[e.offset : e.offset+n] + for i := range zero { + zero[i] = 0 + } + e.offset += n +} + +// intSizeOf returns the size of the data required to represent the data when encoded. +// It returns zero if the type cannot be implemented by the fast path in Read or Write. +func intSizeOf(data any) int { + switch data := data.(type) { + case bool, int8, uint8, *bool, *int8, *uint8: + return 1 + case []bool: + return len(data) + case []int8: + return len(data) + case []uint8: + return len(data) + case int16, uint16, *int16, *uint16: + return 2 + case []int16: + return 2 * len(data) + case []uint16: + return 2 * len(data) + case int32, uint32, *int32, *uint32: + return 4 + case []int32: + return 4 * len(data) + case []uint32: + return 4 * len(data) + case int64, uint64, *int64, *uint64: + return 8 + case []int64: + return 8 * len(data) + case []uint64: + return 8 * len(data) + case float32, *float32: + return 4 + case float64, *float64: + return 8 + case []float32: + return 4 * len(data) + case []float64: + return 8 * len(data) + } + return 0 +} diff --git a/natend/natend_big.go b/natend/natend_big.go new file mode 100644 index 0000000..788e464 --- /dev/null +++ b/natend/natend_big.go @@ -0,0 +1,57 @@ +//go:build armbe || arm64be || m68k || mips || mips64 || mips64p32 || ppc || ppc64 || s390 || s390x || shbe || sparc || sparc64 + +package natend + +import ( + "io" + + "github.com/go-perf/encoding/bigend" +) + +func Uint16(b []byte) uint16 { + return bigend.Uint16(b) +} + +func PutUint16(b []byte, v uint16) { + bigend.PutUint16(b, v) +} + +func AppendUint16(b []byte, v uint16) []byte { + return bigend.AppendUint16(b, v) +} + +func Uint32(b []byte) uint32 { + return bigend.Uint32(b) +} + +func PutUint32(b []byte, v uint32) { + bigend.PutUint32(b, v) +} + +func AppendUint32(b []byte, v uint32) []byte { + return bigend.AppendUint32(b, v) +} + +func Uint64(b []byte) uint64 { + return bigend.Uint64(b) +} + +func PutUint64(b []byte, v uint64) { + bigend.PutUint64(b, v) +} + +func AppendUint64(b []byte, v uint64) []byte { + return bigend.AppendUint64(b, v) +} + +func Read(r io.Reader, data any) error { + return bigend.Read(r, data) +} + +func Write(w io.Writer, data any) error { + return bigend.Write(w, data) +} + +func Size(v any) int { + return bigend.Size(v) +} diff --git a/natend/natend_lit.go b/natend/natend_lit.go new file mode 100644 index 0000000..78303cf --- /dev/null +++ b/natend/natend_lit.go @@ -0,0 +1,57 @@ +//go:build 386 || amd64 || amd64p32 || alpha || arm || arm64 || loong64 || mipsle || mips64le || mips64p32le || nios2 || ppc64le || riscv || riscv64 || sh || wasm + +package natend + +import ( + "io" + + "github.com/go-perf/encoding/litend" +) + +func Uint16(b []byte) uint16 { + return litend.Uint16(b) +} + +func PutUint16(b []byte, v uint16) { + litend.PutUint16(b, v) +} + +func AppendUint16(b []byte, v uint16) []byte { + return litend.AppendUint16(b, v) +} + +func Uint32(b []byte) uint32 { + return litend.Uint32(b) +} + +func PutUint32(b []byte, v uint32) { + litend.PutUint32(b, v) +} + +func AppendUint32(b []byte, v uint32) []byte { + return litend.AppendUint32(b, v) +} + +func Uint64(b []byte) uint64 { + return litend.Uint64(b) +} + +func PutUint64(b []byte, v uint64) { + litend.PutUint64(b, v) +} + +func AppendUint64(b []byte, v uint64) []byte { + return litend.AppendUint64(b, v) +} + +func Read(r io.Reader, data any) error { + return litend.Read(r, data) +} + +func Write(w io.Writer, data any) error { + return litend.Write(w, data) +} + +func Size(v any) int { + return litend.Size(v) +}