From a6ea71d2f6231dee8d2fae534a40edddd0a1c3af Mon Sep 17 00:00:00 2001 From: Masaaki Goshima Date: Sun, 31 Jan 2021 01:48:54 +0900 Subject: [PATCH 1/3] Improve performance of lookup opcodeSet at encoding --- encode.go | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 83 insertions(+), 7 deletions(-) diff --git a/encode.go b/encode.go index 48791ae..5f2470d 100644 --- a/encode.go +++ b/encode.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding" "encoding/base64" + "fmt" "io" "math" "reflect" @@ -35,7 +36,8 @@ type compiledCode struct { } const ( - bufSize = 1024 + bufSize = 1024 + maxTypeAddrRange = 1024 * 1024 * 16 // 16 Mib ) const ( @@ -67,13 +69,56 @@ func storeOpcodeSet(typ uintptr, set *opcodeSet, m map[uintptr]*opcodeSet) { } var ( - encPool sync.Pool - codePool sync.Pool - cachedOpcode unsafe.Pointer // map[uintptr]*opcodeSet - marshalJSONType reflect.Type - marshalTextType reflect.Type + encPool sync.Pool + codePool sync.Pool + cachedOpcode unsafe.Pointer // map[uintptr]*opcodeSet + marshalJSONType 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 > maxTypeAddrRange { + return fmt.Errorf("too big address range %d", addrRange) + } + cachedOpcodeSets = make([]*opcodeSet, addrRange) + baseTypeAddr = min + return nil +} + func init() { encPool = sync.Pool{ New: func() interface{} { @@ -88,6 +133,9 @@ func init() { } marshalJSONType = reflect.TypeOf((*Marshaler)(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. @@ -233,6 +281,35 @@ func (e *Encoder) encode(header *interfaceHeader, isNil bool) ([]byte, 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() if codeSet, exists := opcodeMap[typeptr]; exists { return codeSet, nil @@ -255,7 +332,6 @@ func (e *Encoder) compileToGetCodeSet(typeptr uintptr) (*opcodeSet, error) { code: code, codeLength: codeLength, } - storeOpcodeSet(typeptr, codeSet, opcodeMap) return codeSet, nil } From 1c0b7e7c7df69bb71c51c935fd0f6f8374b8fdbb Mon Sep 17 00:00:00 2001 From: Masaaki Goshima Date: Sun, 31 Jan 2021 01:49:35 +0900 Subject: [PATCH 2/3] Add benchmark for encoding of bool type --- benchmarks/encode_test.go | 46 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/benchmarks/encode_test.go b/benchmarks/encode_test.go index 7031b58..47c76db 100644 --- a/benchmarks/encode_test.go +++ b/benchmarks/encode_test.go @@ -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) { b.ReportAllocs() for i := 0; i < b.N; i++ { From 90c2560b5229dba322ef07e04582bd82db9ae741 Mon Sep 17 00:00:00 2001 From: Masaaki Goshima Date: Sun, 31 Jan 2021 02:30:48 +0900 Subject: [PATCH 3/3] Fix accepatable address range --- encode.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/encode.go b/encode.go index 5f2470d..a48c4c3 100644 --- a/encode.go +++ b/encode.go @@ -36,8 +36,8 @@ type compiledCode struct { } const ( - bufSize = 1024 - maxTypeAddrRange = 1024 * 1024 * 16 // 16 Mib + bufSize = 1024 + maxAcceptableTypeAddrRange = 1024 * 1024 * 2 // 2 Mib ) const ( @@ -111,7 +111,7 @@ func setupOpcodeSets() error { if addrRange == 0 { return fmt.Errorf("failed to get address range of types") } - if addrRange > maxTypeAddrRange { + if addrRange > maxAcceptableTypeAddrRange { return fmt.Errorf("too big address range %d", addrRange) } cachedOpcodeSets = make([]*opcodeSet, addrRange)