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