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