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  }