-
Notifications
You must be signed in to change notification settings - Fork 24
/
benchmark_marshaler_test.go
139 lines (121 loc) · 2.83 KB
/
benchmark_marshaler_test.go
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package reflect_test
import (
"errors"
"strconv"
"sync"
"testing"
"unsafe"
"github.com/goccy/go-reflect"
)
var (
typeToEncoderMap sync.Map
bufpool = sync.Pool{
New: func() interface{} {
return &buffer{
b: make([]byte, 0, 1024),
}
},
}
)
type buffer struct {
b []byte
}
type encoder func(*buffer, unsafe.Pointer) error
func Marshal(v interface{}) ([]byte, error) {
// Technique 1.
// Get type information and pointer from interface{} value without allocation.
typ, ptr := reflect.TypeAndPtrOf(v)
typeID := reflect.TypeID(v)
// Technique 2.
// Reuse the buffer once allocated using sync.Pool
buf := bufpool.Get().(*buffer)
buf.b = buf.b[:0]
defer bufpool.Put(buf)
// Technique 3.
// builds a optimized path by typeID and caches it
if enc, ok := typeToEncoderMap.Load(typeID); ok {
if err := enc.(encoder)(buf, ptr); err != nil {
return nil, err
}
// allocate a new buffer required length only
b := make([]byte, len(buf.b))
copy(b, buf.b)
return b, nil
}
// First time,
// builds a optimized path by type and caches it with typeID.
enc, err := compile(typ)
if err != nil {
return nil, err
}
typeToEncoderMap.Store(typeID, enc)
if err := enc(buf, ptr); err != nil {
return nil, err
}
// allocate a new buffer required length only
b := make([]byte, len(buf.b))
copy(b, buf.b)
return b, nil
}
func compile(typ reflect.Type) (encoder, error) {
switch typ.Kind() {
case reflect.Struct:
return compileStruct(typ)
case reflect.Int:
return compileInt(typ)
}
return nil, errors.New("unsupported type")
}
func compileStruct(typ reflect.Type) (encoder, error) {
encoders := []encoder{}
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
enc, err := compile(field.Type)
if err != nil {
return nil, err
}
offset := field.Offset
encoders = append(encoders, func(buf *buffer, p unsafe.Pointer) error {
return enc(buf, unsafe.Pointer(uintptr(p)+offset))
})
}
return func(buf *buffer, p unsafe.Pointer) error {
buf.b = append(buf.b, '{')
for i, enc := range encoders {
if i != 0 {
buf.b = append(buf.b, ' ')
}
if err := enc(buf, p); err != nil {
return err
}
}
buf.b = append(buf.b, '}')
return nil
}, nil
}
func compileInt(typ reflect.Type) (encoder, error) {
return func(buf *buffer, p unsafe.Pointer) error {
value := *(*int)(p)
buf.b = strconv.AppendInt(buf.b, int64(value), 10)
return nil
}, nil
}
func Benchmark_Marshal(b *testing.B) {
b.ReportAllocs()
for n := 0; n < b.N; n++ {
bytes, err := Marshal(struct{ I int }{10})
if err != nil {
b.Fatal(err)
}
if string(bytes) != "{10}" {
b.Fatalf("unexpected error: %s", string(bytes))
}
bytes2, err := Marshal(struct{ I, J int }{10, 20})
if err != nil {
b.Fatal(err)
}
if string(bytes2) != "{10 20}" {
b.Fatalf("unexpected error: %s", string(bytes2))
}
}
}