github.com/goccy/go-reflect@v1.2.1-0.20220925055700-4646ad15ec8a/README.md (about)

     1  # go-reflect
     2  
     3  ![Go](https://github.com/goccy/go-reflect/workflows/Go/badge.svg)
     4  [![GoDoc](https://godoc.org/github.com/goccy/go-reflect?status.svg)](https://pkg.go.dev/github.com/goccy/go-reflect?tab=doc)
     5  [![codecov](https://codecov.io/gh/goccy/go-reflect/branch/master/graph/badge.svg)](https://codecov.io/gh/goccy/go-reflect)
     6  [![Go Report Card](https://goreportcard.com/badge/github.com/goccy/go-reflect)](https://goreportcard.com/report/github.com/goccy/go-reflect)
     7  
     8  Zero-allocation reflection library for Go
     9  
    10  # Features
    11  
    12  - 100% Compatibility APIs with `reflect` library
    13  - No allocation occurs when using the reflect.Type features
    14  - You can choose to escape ( `reflect.ValueOf` ) or noescape ( `reflect.ValueNoEscapeOf` ) when creating reflect.Value
    15  
    16  # Status
    17  
    18  All the tests in the reflect library have been passed
    19  except the tests that use some private functions.
    20  
    21  # Installation
    22  
    23  ```bash
    24  go get github.com/goccy/go-reflect
    25  ```
    26  
    27  # How to use
    28  
    29  Replace import statement from `reflect` to `github.com/goccy/go-reflect`
    30  
    31  ```bash
    32  -import "reflect"
    33  +import "github.com/goccy/go-reflect"
    34  ```
    35  
    36  # Benchmarks
    37  
    38  Source https://github.com/goccy/go-reflect/blob/master/benchmark_test.go
    39  
    40  ## Benchmark about reflect.Type
    41  
    42  ```
    43  $ go test -bench TypeOf
    44  ```
    45  
    46  ```
    47  goos: darwin
    48  goarch: amd64
    49  pkg: github.com/goccy/go-reflect
    50  Benchmark_TypeOf_Reflect-12             100000000               13.8 ns/op             8 B/op          1 allocs/op
    51  Benchmark_TypeOf_GoReflect-12           2000000000               1.70 ns/op            0 B/op          0 allocs/op
    52  PASS
    53  ok      github.com/goccy/go-reflect     5.369s
    54  ```
    55  
    56  ## Benchmark about reflect.Value
    57  
    58  ```
    59  $ go test -bench ValueOf
    60  ```
    61  
    62  ```
    63  goos: darwin
    64  goarch: amd64
    65  pkg: github.com/goccy/go-reflect
    66  Benchmark_ValueOf_Reflect-12            100000000               13.0 ns/op             8 B/op          1 allocs/op
    67  Benchmark_ValueOf_GoReflect-12          300000000                4.64 ns/op            0 B/op          0 allocs/op
    68  PASS
    69  ok      github.com/goccy/go-reflect     3.578s
    70  ```
    71  
    72  # Real World Example
    73  
    74  ## Implements Fast Marshaler
    75  
    76  I would like to introduce the technique I use for [github.com/goccy/go-json](https://github.com/goccy/go-json).  
    77  Using this technique, allocation can be suppressed to once for any marshaler.  
    78  
    79  Original Source is https://github.com/goccy/go-reflect/blob/master/benchmark_marshaler_test.go
    80  
    81  ```go
    82  package reflect_test
    83  
    84  import (
    85      "errors"
    86      "strconv"
    87      "sync"
    88      "testing"
    89      "unsafe"
    90  
    91      "github.com/goccy/go-reflect"
    92  )
    93  
    94  var (
    95      typeToEncoderMap sync.Map
    96      bufpool          = sync.Pool{
    97          New: func() interface{} {
    98              return &buffer{
    99                  b: make([]byte, 0, 1024),
   100              }
   101          },
   102      }
   103  )
   104  
   105  type buffer struct {
   106      b []byte
   107  }
   108  
   109  type encoder func(*buffer, unsafe.Pointer) error
   110  
   111  func Marshal(v interface{}) ([]byte, error) {
   112  
   113      // Technique 1.
   114      // Get type information and pointer from interface{} value without allocation.
   115      typ, ptr := reflect.TypeAndPtrOf(v)
   116      typeID := reflect.TypeID(typ)
   117  
   118      // Technique 2.
   119      // Reuse the buffer once allocated using sync.Pool
   120      buf := bufpool.Get().(*buffer)
   121      buf.b = buf.b[:0]
   122      defer bufpool.Put(buf)
   123  
   124      // Technique 3.
   125      // builds a optimized path by typeID and caches it
   126      if enc, ok := typeToEncoderMap.Load(typeID); ok {
   127          if err := enc.(encoder)(buf, ptr); err != nil {
   128              return nil, err
   129          }
   130  
   131          // allocate a new buffer required length only
   132          b := make([]byte, len(buf.b))
   133          copy(b, buf.b)
   134          return b, nil
   135      }
   136  
   137      // First time,
   138      // builds a optimized path by type and caches it with typeID.
   139      enc, err := compile(typ)
   140      if err != nil {
   141          return nil, err
   142      }
   143      typeToEncoderMap.Store(typeID, enc)
   144      if err := enc(buf, ptr); err != nil {
   145          return nil, err
   146      }
   147  
   148      // allocate a new buffer required length only
   149      b := make([]byte, len(buf.b))
   150      copy(b, buf.b)
   151      return b, nil
   152  }
   153  
   154  func compile(typ reflect.Type) (encoder, error) {
   155      switch typ.Kind() {
   156      case reflect.Struct:
   157          return compileStruct(typ)
   158      case reflect.Int:
   159          return compileInt(typ)
   160      }
   161      return nil, errors.New("unsupported type")
   162  }
   163  
   164  func compileStruct(typ reflect.Type) (encoder, error) {
   165  
   166      encoders := []encoder{}
   167  
   168      for i := 0; i < typ.NumField(); i++ {
   169          field := typ.Field(i)
   170          enc, err := compile(field.Type)
   171          if err != nil {
   172              return nil, err
   173          }
   174          offset := field.Offset
   175          encoders = append(encoders, func(buf *buffer, p unsafe.Pointer) error {
   176              return enc(buf, unsafe.Pointer(uintptr(p)+offset))
   177          })
   178      }
   179      return func(buf *buffer, p unsafe.Pointer) error {
   180          buf.b = append(buf.b, '{')
   181          for _, enc := range encoders {
   182              if err := enc(buf, p); err != nil {
   183                  return err
   184              }
   185          }
   186          buf.b = append(buf.b, '}')
   187          return nil
   188      }, nil
   189  }
   190  
   191  func compileInt(typ reflect.Type) (encoder, error) {
   192      return func(buf *buffer, p unsafe.Pointer) error {
   193          value := *(*int)(p)
   194          buf.b = strconv.AppendInt(buf.b, int64(value), 10)
   195          return nil
   196      }, nil
   197  }
   198  
   199  func Benchmark_Marshal(b *testing.B) {
   200      b.ReportAllocs()
   201      for n := 0; n < b.N; n++ {
   202          bytes, err := Marshal(struct{ I int }{10})
   203          if err != nil {
   204              b.Fatal(err)
   205          }
   206          if string(bytes) != "{10}" {
   207              b.Fatal("unexpected error")
   208          }
   209      }
   210  }
   211  ```
   212  
   213  The benchmark result is as follows.  
   214  
   215  ```bash
   216  
   217  $ go test -bench Benchmark_Marshal
   218  goos: darwin
   219  goarch: amd64
   220  pkg: github.com/goccy/go-reflect
   221  Benchmark_Marshal-16            16586372                71.0 ns/op             4 B/op          1 allocs/op
   222  PASS
   223  ```
   224