Merge pull request #103 from goccy/feature/optimize-lookup-opcodeset

Improve performance of lookup of opcodeSet at encoding
This commit is contained in:
Masaaki Goshima 2021-01-31 12:57:05 +09:00 committed by GitHub
commit 1ea4ffc9c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 129 additions and 7 deletions

View File

@ -558,6 +558,52 @@ func Benchmark_Encode_Interface_GoJson(b *testing.B) {
} }
} }
func Benchmark_Encode_Bool_EncodingJson(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if _, err := json.Marshal(true); err != nil {
b.Fatal(err)
}
}
}
func Benchmark_Encode_Bool_JsonIter(b *testing.B) {
var json = jsoniter.ConfigCompatibleWithStandardLibrary
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if _, err := json.Marshal(true); err != nil {
b.Fatal(err)
}
}
}
func Benchmark_Encode_Bool_Jettison(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if _, err := jettison.Marshal(true); err != nil {
b.Fatal(err)
}
}
}
func Benchmark_Encode_Bool_SegmentioJson(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if _, err := segmentiojson.Marshal(true); err != nil {
b.Fatal(err)
}
}
}
func Benchmark_Encode_Bool_GoJson(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if _, err := gojson.Marshal(true); err != nil {
b.Fatal(err)
}
}
}
func Benchmark_Encode_Int_EncodingJson(b *testing.B) { func Benchmark_Encode_Int_EncodingJson(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"encoding" "encoding"
"encoding/base64" "encoding/base64"
"fmt"
"io" "io"
"math" "math"
"reflect" "reflect"
@ -36,6 +37,7 @@ type compiledCode struct {
const ( const (
bufSize = 1024 bufSize = 1024
maxAcceptableTypeAddrRange = 1024 * 1024 * 2 // 2 Mib
) )
const ( const (
@ -72,8 +74,51 @@ var (
cachedOpcode unsafe.Pointer // map[uintptr]*opcodeSet cachedOpcode unsafe.Pointer // map[uintptr]*opcodeSet
marshalJSONType reflect.Type marshalJSONType reflect.Type
marshalTextType reflect.Type marshalTextType reflect.Type
baseTypeAddr uintptr
cachedOpcodeSets []*opcodeSet
) )
//go:linkname typelinks reflect.typelinks
func typelinks() ([]unsafe.Pointer, [][]int32)
//go:linkname rtypeOff reflect.rtypeOff
func rtypeOff(unsafe.Pointer, int32) unsafe.Pointer
func setupOpcodeSets() error {
sections, offsets := typelinks()
if len(sections) != 1 {
return fmt.Errorf("failed to get sections")
}
if len(offsets) != 1 {
return fmt.Errorf("failed to get offsets")
}
section := sections[0]
offset := offsets[0]
var (
min uintptr = uintptr(^uint(0))
max uintptr = 0
)
for i := 0; i < len(offset); i++ {
addr := uintptr(rtypeOff(section, offset[i]))
if min > addr {
min = addr
}
if max < addr {
max = addr
}
}
addrRange := uintptr(max) - uintptr(min)
if addrRange == 0 {
return fmt.Errorf("failed to get address range of types")
}
if addrRange > maxAcceptableTypeAddrRange {
return fmt.Errorf("too big address range %d", addrRange)
}
cachedOpcodeSets = make([]*opcodeSet, addrRange)
baseTypeAddr = min
return nil
}
func init() { func init() {
encPool = sync.Pool{ encPool = sync.Pool{
New: func() interface{} { New: func() interface{} {
@ -88,6 +133,9 @@ func init() {
} }
marshalJSONType = reflect.TypeOf((*Marshaler)(nil)).Elem() marshalJSONType = reflect.TypeOf((*Marshaler)(nil)).Elem()
marshalTextType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() marshalTextType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
if err := setupOpcodeSets(); err != nil {
// fallback to slow path
}
} }
// NewEncoder returns a new encoder that writes to w. // NewEncoder returns a new encoder that writes to w.
@ -233,6 +281,35 @@ func (e *Encoder) encode(header *interfaceHeader, isNil bool) ([]byte, error) {
} }
func (e *Encoder) compileToGetCodeSet(typeptr uintptr) (*opcodeSet, error) { func (e *Encoder) compileToGetCodeSet(typeptr uintptr) (*opcodeSet, error) {
if cachedOpcodeSets == nil {
return e.compileToGetCodeSetSlowPath(typeptr)
}
if codeSet := cachedOpcodeSets[typeptr-baseTypeAddr]; codeSet != nil {
return codeSet, nil
}
// noescape trick for header.typ ( reflect.*rtype )
copiedType := *(**rtype)(unsafe.Pointer(&typeptr))
code, err := e.compileHead(&encodeCompileContext{
typ: copiedType,
root: true,
structTypeToCompiledCode: map[uintptr]*compiledCode{},
})
if err != nil {
return nil, err
}
code = copyOpcode(code)
codeLength := code.totalLength()
codeSet := &opcodeSet{
code: code,
codeLength: codeLength,
}
cachedOpcodeSets[int(typeptr-baseTypeAddr)] = codeSet
return codeSet, nil
}
func (e *Encoder) compileToGetCodeSetSlowPath(typeptr uintptr) (*opcodeSet, error) {
opcodeMap := loadOpcodeMap() opcodeMap := loadOpcodeMap()
if codeSet, exists := opcodeMap[typeptr]; exists { if codeSet, exists := opcodeMap[typeptr]; exists {
return codeSet, nil return codeSet, nil
@ -255,7 +332,6 @@ func (e *Encoder) compileToGetCodeSet(typeptr uintptr) (*opcodeSet, error) {
code: code, code: code,
codeLength: codeLength, codeLength: codeLength,
} }
storeOpcodeSet(typeptr, codeSet, opcodeMap) storeOpcodeSet(typeptr, codeSet, opcodeMap)
return codeSet, nil return codeSet, nil
} }