github.com/goccy/go-reflect@v1.2.0/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(typ)
    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 _, enc := range encoders {
   101  			if err := enc(buf, p); err != nil {
   102  				return err
   103  			}
   104  		}
   105  		buf.b = append(buf.b, '}')
   106  		return nil
   107  	}, nil
   108  }
   109  
   110  func compileInt(typ reflect.Type) (encoder, error) {
   111  	return func(buf *buffer, p unsafe.Pointer) error {
   112  		value := *(*int)(p)
   113  		buf.b = strconv.AppendInt(buf.b, int64(value), 10)
   114  		return nil
   115  	}, nil
   116  }
   117  
   118  func Benchmark_Marshal(b *testing.B) {
   119  	b.ReportAllocs()
   120  	for n := 0; n < b.N; n++ {
   121  		bytes, err := Marshal(struct{ I int }{10})
   122  		if err != nil {
   123  			b.Fatal(err)
   124  		}
   125  		if string(bytes) != "{10}" {
   126  			b.Fatal("unexpected error")
   127  		}
   128  	}
   129  }