From b0b330a2dd3ca50c34a608d2323653f914ac42d3 Mon Sep 17 00:00:00 2001 From: Masaaki Goshima Date: Wed, 30 Dec 2020 19:32:38 +0900 Subject: [PATCH] Add noescape API for encoder --- .github/workflows/go.yml | 22 +++++++-------- benchmarks/encode_test.go | 57 +++++++++++++++++++++++++++++++++++++++ encode.go | 12 ++++----- json.go | 27 ++++++++++++++++--- 4 files changed, 96 insertions(+), 22 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index bff6800..73eeb0d 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -9,24 +9,20 @@ jobs: go-version: [ "1.13", "1.14", "1.15" ] runs-on: ${{ matrix.os }} steps: - - name: Set up Go ${{ matrix.go-version }} + - name: setup Go ${{ matrix.go-version }} uses: actions/setup-go@v2 with: go-version: ${{ matrix.go-version }} - - - name: Check out code into the Go module directory + - name: checkout uses: actions/checkout@v2 - - - name: Test + - name: simple test run: go test -v ./ -count=1 - - # - name: Test with GC - # run: go test -v ./ -count=1 - # env: - # GOGC: 1 - - # - name: Test with race detector - # run: go test -v -race ./ -count=1 + - name: test with GC pressure + run: go test -v ./ -count=1 + env: + GOGC: 1 + - name: test with race detector + run: go test -v -race ./ -count=1 coverage: name: Coverage runs-on: ubuntu-latest diff --git a/benchmarks/encode_test.go b/benchmarks/encode_test.go index 582b031..683fe0b 100644 --- a/benchmarks/encode_test.go +++ b/benchmarks/encode_test.go @@ -56,6 +56,15 @@ func Benchmark_Encode_SmallStruct_GoJson(b *testing.B) { } } +func Benchmark_Encode_SmallStruct_GoJsonNoEscape(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if _, err := gojson.MarshalNoEscape(NewSmallPayload()); err != nil { + b.Fatal(err) + } + } +} + func Benchmark_Encode_SmallStructCached_EncodingJson(b *testing.B) { cached := NewSmallPayload() b.ReportAllocs() @@ -107,6 +116,16 @@ func Benchmark_Encode_SmallStructCached_GoJson(b *testing.B) { } } +func Benchmark_Encode_SmallStructCached_GoJsonNoEscape(b *testing.B) { + cached := NewSmallPayload() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if _, err := gojson.MarshalNoEscape(cached); err != nil { + b.Fatal(err) + } + } +} + func Benchmark_Encode_MediumStruct_EncodingJson(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { @@ -153,6 +172,15 @@ func Benchmark_Encode_MediumStruct_GoJson(b *testing.B) { } } +func Benchmark_Encode_MediumStruct_GoJsonNoEscape(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if _, err := gojson.MarshalNoEscape(NewMediumPayload()); err != nil { + b.Fatal(err) + } + } +} + func Benchmark_Encode_MediumStructCached_EncodingJson(b *testing.B) { cached := NewMediumPayload() b.ReportAllocs() @@ -204,6 +232,16 @@ func Benchmark_Encode_MediumStructCached_GoJson(b *testing.B) { } } +func Benchmark_Encode_MediumStructCached_GoJsonNoEscape(b *testing.B) { + cached := NewMediumPayload() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if _, err := gojson.MarshalNoEscape(cached); err != nil { + b.Fatal(err) + } + } +} + func Benchmark_Encode_LargeStruct_EncodingJson(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { @@ -250,6 +288,15 @@ func Benchmark_Encode_LargeStruct_GoJson(b *testing.B) { } } +func Benchmark_Encode_LargeStruct_GoJsonNoEscape(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if _, err := gojson.MarshalNoEscape(NewLargePayload()); err != nil { + b.Fatal(err) + } + } +} + func Benchmark_Encode_LargeStructCached_EncodingJson(b *testing.B) { cached := NewLargePayload() b.ReportAllocs() @@ -300,3 +347,13 @@ func Benchmark_Encode_LargeStructCached_GoJson(b *testing.B) { } } } + +func Benchmark_Encode_LargeStructCached_GoJsonNoEscape(b *testing.B) { + cached := NewLargePayload() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if _, err := gojson.MarshalNoEscape(cached); err != nil { + b.Fatal(err) + } + } +} diff --git a/encode.go b/encode.go index 460feea..d258c45 100644 --- a/encode.go +++ b/encode.go @@ -111,7 +111,8 @@ func (e *Encoder) EncodeWithOption(v interface{}, opts ...EncodeOption) error { return err } } - buf, err := e.encode(v) + header := (*interfaceHeader)(unsafe.Pointer(&v)) + buf, err := e.encode(header) if err != nil { return err } @@ -159,8 +160,8 @@ func (e *Encoder) reset() { e.unorderedMap = false } -func (e *Encoder) encodeForMarshal(v interface{}) ([]byte, error) { - buf, err := e.encode(v) +func (e *Encoder) encodeForMarshal(header *interfaceHeader) ([]byte, error) { + buf, err := e.encode(header) if err != nil { return nil, err } @@ -177,9 +178,9 @@ func (e *Encoder) encodeForMarshal(v interface{}) ([]byte, error) { return copied, nil } -func (e *Encoder) encode(v interface{}) ([]byte, error) { +func (e *Encoder) encode(header *interfaceHeader) ([]byte, error) { b := e.buf[:0] - if v == nil { + if header.ptr == nil { b = encodeNull(b) if e.enabledIndent { b = encodeIndentComma(b) @@ -188,7 +189,6 @@ func (e *Encoder) encode(v interface{}) ([]byte, error) { } return b, nil } - header := (*interfaceHeader)(unsafe.Pointer(&v)) typ := header.typ typeptr := uintptr(unsafe.Pointer(typ)) diff --git a/json.go b/json.go index 3d1d77f..28c6094 100644 --- a/json.go +++ b/json.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "strconv" + "unsafe" ) // Marshaler is the interface implemented by types that @@ -157,6 +158,20 @@ func Marshal(v interface{}) ([]byte, error) { return MarshalWithOption(v) } +// MarshalNoEscape +func MarshalNoEscape(v interface{}) ([]byte, error) { + var b *bytes.Buffer + enc := NewEncoder(b) + header := (*interfaceHeader)(unsafe.Pointer(&v)) + bytes, err := enc.encodeForMarshal(header) + if err != nil { + enc.release() + return nil, err + } + enc.release() + return bytes, nil +} + // MarshalWithOption returns the JSON encoding of v with EncodeOption. func MarshalWithOption(v interface{}, opts ...EncodeOption) ([]byte, error) { var b *bytes.Buffer @@ -166,7 +181,9 @@ func MarshalWithOption(v interface{}, opts ...EncodeOption) ([]byte, error) { return nil, err } } - bytes, err := enc.encodeForMarshal(v) + header := (*interfaceHeader)(unsafe.Pointer(&v)) + enc.ptr = header.ptr + bytes, err := enc.encodeForMarshal(header) if err != nil { enc.release() return nil, err @@ -192,7 +209,9 @@ func MarshalIndentWithOption(v interface{}, prefix, indent string, opts ...Encod } } enc.SetIndent(prefix, indent) - bytes, err := enc.encodeForMarshal(v) + header := (*interfaceHeader)(unsafe.Pointer(&v)) + enc.ptr = header.ptr + bytes, err := enc.encodeForMarshal(header) if err != nil { enc.release() return nil, err @@ -393,7 +412,9 @@ func HTMLEscape(dst *bytes.Buffer, src []byte) { } enc := NewEncoder(dst) enc.SetEscapeHTML(true) - enc.buf, _ = enc.encode(v) + header := (*interfaceHeader)(unsafe.Pointer(&v)) + enc.ptr = header.ptr + enc.buf, _ = enc.encode(header) dst.Write(enc.buf[:len(enc.buf)-1]) // remove last ',' character }