github.com/goccy/go-reflect@v1.2.1-0.20220925055700-4646ad15ec8a/benchmark_marshaler_test.go (about) 1 package reflect_test 2 3 import ( 4 "errors" 5 "strconv" 6 "sync" 7 "testing" 8 "unsafe" 9 10 "github.com/goccy/go-reflect" 11 ) 12 13 var ( 14 typeToEncoderMap sync.Map 15 bufpool = sync.Pool{ 16 New: func() interface{} { 17 return &buffer{ 18 b: make([]byte, 0, 1024), 19 } 20 }, 21 } 22 ) 23 24 type buffer struct { 25 b []byte 26 } 27 28 type encoder func(*buffer, unsafe.Pointer) error 29 30 func Marshal(v interface{}) ([]byte, error) { 31 32 // Technique 1. 33 // Get type information and pointer from interface{} value without allocation. 34 typ, ptr := reflect.TypeAndPtrOf(v) 35 typeID := reflect.TypeID(v) 36 37 // Technique 2. 38 // Reuse the buffer once allocated using sync.Pool 39 buf := bufpool.Get().(*buffer) 40 buf.b = buf.b[:0] 41 defer bufpool.Put(buf) 42 43 // Technique 3. 44 // builds a optimized path by typeID and caches it 45 if enc, ok := typeToEncoderMap.Load(typeID); ok { 46 if err := enc.(encoder)(buf, ptr); err != nil { 47 return nil, err 48 } 49 50 // allocate a new buffer required length only 51 b := make([]byte, len(buf.b)) 52 copy(b, buf.b) 53 return b, nil 54 } 55 56 // First time, 57 // builds a optimized path by type and caches it with typeID. 58 enc, err := compile(typ) 59 if err != nil { 60 return nil, err 61 } 62 typeToEncoderMap.Store(typeID, enc) 63 if err := enc(buf, ptr); err != nil { 64 return nil, err 65 } 66 67 // allocate a new buffer required length only 68 b := make([]byte, len(buf.b)) 69 copy(b, buf.b) 70 return b, nil 71 } 72 73 func compile(typ reflect.Type) (encoder, error) { 74 switch typ.Kind() { 75 case reflect.Struct: 76 return compileStruct(typ) 77 case reflect.Int: 78 return compileInt(typ) 79 } 80 return nil, errors.New("unsupported type") 81 } 82 83 func compileStruct(typ reflect.Type) (encoder, error) { 84 85 encoders := []encoder{} 86 87 for i := 0; i < typ.NumField(); i++ { 88 field := typ.Field(i) 89 enc, err := compile(field.Type) 90 if err != nil { 91 return nil, err 92 } 93 offset := field.Offset 94 encoders = append(encoders, func(buf *buffer, p unsafe.Pointer) error { 95 return enc(buf, unsafe.Pointer(uintptr(p)+offset)) 96 }) 97 } 98 return func(buf *buffer, p unsafe.Pointer) error { 99 buf.b = append(buf.b, '{') 100 for i, enc := range encoders { 101 if i != 0 { 102 buf.b = append(buf.b, ' ') 103 } 104 if err := enc(buf, p); err != nil { 105 return err 106 } 107 } 108 buf.b = append(buf.b, '}') 109 return nil 110 }, nil 111 } 112 113 func compileInt(typ reflect.Type) (encoder, error) { 114 return func(buf *buffer, p unsafe.Pointer) error { 115 value := *(*int)(p) 116 buf.b = strconv.AppendInt(buf.b, int64(value), 10) 117 return nil 118 }, nil 119 } 120 121 func Benchmark_Marshal(b *testing.B) { 122 b.ReportAllocs() 123 for n := 0; n < b.N; n++ { 124 bytes, err := Marshal(struct{ I int }{10}) 125 if err != nil { 126 b.Fatal(err) 127 } 128 if string(bytes) != "{10}" { 129 b.Fatalf("unexpected error: %s", string(bytes)) 130 } 131 bytes2, err := Marshal(struct{ I, J int }{10, 20}) 132 if err != nil { 133 b.Fatal(err) 134 } 135 if string(bytes2) != "{10 20}" { 136 b.Fatalf("unexpected error: %s", string(bytes2)) 137 } 138 } 139 }