Skip to content

Commit

Permalink
Optimize package perf/json
Browse files Browse the repository at this point in the history
Change-Id: Ie9ef9de9fbb0a9828442641e3a384df4694670b2
  • Loading branch information
jxskiss committed Nov 17, 2023
1 parent 4c1d85a commit e37f488
Show file tree
Hide file tree
Showing 7 changed files with 29 additions and 56 deletions.
6 changes: 3 additions & 3 deletions perf/json/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ It gives much better performance than `encoding/json` and good compatibility wit

User may use the method `Config` to customize the behavior of this library.

For marshalling map data where key ordering does not matter,
user may use the shortcut function `MarshalNoMapOrdering`,
which disables map key ordering in case that jsoniter or sonic is used.
For best performance, user may use `MarshalFastest` when this library is
configured to use jsoniter or sonic. The result is not compatible with
std `encoding/json` in some ways, especially that map keys are not sorted.

### Benchmark

Expand Down
20 changes: 10 additions & 10 deletions perf/json/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func init() {
// when it's fully ready for production deployment.

//{
// if isSonicJIT {
// if supportSonicJIT {
// _J.useSonicConfig(sonicDefault)
// } else {
// _J.useJSONIterConfig(jsoniterDefault)
Expand Down Expand Up @@ -92,8 +92,8 @@ type apiProxy struct {
HTMLEscape func(dst *bytes.Buffer, src []byte)
Indent func(dst *bytes.Buffer, src []byte, prefix, indent string) error

MarshalNoMapOrdering func(v any) ([]byte, error)
MarshalNoHTMLEscape func(v any, prefix, indent string) ([]byte, error)
MarshalFastest func(v any) ([]byte, error)
MarshalNoHTMLEscape func(v any, prefix, indent string) ([]byte, error)

NewEncoder func(w io.Writer) underlyingEncoder
NewDecoder func(r io.Reader) underlyingDecoder
Expand All @@ -113,8 +113,8 @@ func (p *apiProxy) useStdlib() {
HTMLEscape: std.HTMLEscape,
Indent: std.Indent,

MarshalNoMapOrdering: std.Marshal,
MarshalNoHTMLEscape: stdMarshalNoHTMLEscape,
MarshalFastest: std.Marshal,
MarshalNoHTMLEscape: stdMarshalNoHTMLEscape,

NewEncoder: stdNewEncoder,
NewDecoder: stdNewDecoder,
Expand All @@ -135,16 +135,16 @@ func (p *apiProxy) useJSONIterConfig(api jsoniter.API) {
HTMLEscape: std.HTMLEscape,
Indent: std.Indent,

MarshalNoMapOrdering: jsoniterMarshalNoMapOrdering,
MarshalNoHTMLEscape: jsoniterMarshalNoHTMLEscape(api),
MarshalFastest: jsoniterMarshalFastest,
MarshalNoHTMLEscape: jsoniterMarshalNoHTMLEscape(api),

NewEncoder: jsoniterNewEncoder(api),
NewDecoder: jsoniterNewDecoder(api),
}
}

func (p *apiProxy) useSonicConfig(api sonic.API) {
if !isSonicJIT {
if !supportSonicJIT {
log.Println("[WARN] json: bytedance/sonic is not supported, fallback to jsoniterDefault")
p.useJSONIterConfig(jsoniterDefault)
return
Expand All @@ -162,8 +162,8 @@ func (p *apiProxy) useSonicConfig(api sonic.API) {
HTMLEscape: std.HTMLEscape,
Indent: std.Indent,

MarshalNoMapOrdering: sonicMarshalNoMapOrdering,
MarshalNoHTMLEscape: sonicMarshalNoHTMLEscape(api),
MarshalFastest: sonicMarshalFastest,
MarshalNoHTMLEscape: sonicMarshalNoHTMLEscape(api),

NewEncoder: sonicNewEncoder(api),
NewDecoder: sonicNewDecoder(api),
Expand Down
13 changes: 6 additions & 7 deletions perf/json/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,12 @@ func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
return _J.Indent(dst, src, prefix, indent)
}

// MarshalNoMapOrdering is like Marshal but does not sort map keys.
// It's useful to optimize performance where map key ordering is not needed.
//
// It has effect only the underlying implementation is sonic,
// else it is an alias name of Marshal.
func MarshalNoMapOrdering(v any) ([]byte, error) {
return _J.MarshalNoMapOrdering(v)
// MarshalFastest uses the fastest config when this library is configured
// to use jsoniter or sonic.
// The result is not compatible with std [encoding/json] in some ways,
// especially that map keys are not sorted.
func MarshalFastest(v any) ([]byte, error) {
return _J.MarshalFastest(v)
}

// MarshalNoHTMLEscape is like Marshal but does not escape HTML characters.
Expand Down
10 changes: 0 additions & 10 deletions perf/json/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,3 @@ func TestCompatibility(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, got1, got2)
}

func TestCompatibility_NoHTMLEscape_Indent(t *testing.T) {
stdOutput, err := stdMarshalNoHTMLEscape(testStringInterfaceMap, " ", " ")
assert.Nil(t, err)

sonicOutput, err := sonicMarshalNoHTMLEscape(sonicDefault)(testStringInterfaceMap, " ", " ")
assert.Nil(t, err)

assert.Equal(t, string(stdOutput), string(sonicOutput))
}
26 changes: 5 additions & 21 deletions perf/json/jsoniter.go
Original file line number Diff line number Diff line change
@@ -1,35 +1,19 @@
package json

import (
"bytes"
"io"

jsoniter "github.com/json-iterator/go"
)

func jsoniterMarshalNoMapOrdering(v any) ([]byte, error) {
func jsoniterMarshalFastest(v any) ([]byte, error) {
return jsoniter.ConfigFastest.Marshal(v)
}

func jsoniterMarshalNoHTMLEscape(cfg jsoniter.API) func(v any, prefix, indent string) ([]byte, error) {
return func(v any, prefix, indent string) ([]byte, error) {
var buf bytes.Buffer
enc := cfg.NewEncoder(&buf)
enc.SetEscapeHTML(false)
enc.SetIndent(prefix, indent)
err := enc.Encode(v)
if err != nil {
return nil, err
}

// json.Encoder always appends '\n' after encoding,
// which is not same with json.Marshal.
out := buf.Bytes()
if len(out) > 0 && out[len(out)-1] == '\n' {
out = out[:len(out)-1]
}
return out, nil
}
// MarshalNoHTMLEscape is not designed for performance critical use-case,
// we use the std [encoding/json] implementation.
func jsoniterMarshalNoHTMLEscape(_ jsoniter.API) func(v any, prefix, indent string) ([]byte, error) {
return stdMarshalNoHTMLEscape
}

func jsoniterNewEncoder(cfg jsoniter.API) func(w io.Writer) underlyingEncoder {
Expand Down
6 changes: 3 additions & 3 deletions perf/json/sonic_fallback.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import (
"github.com/bytedance/sonic"
)

const isSonicJIT = false
const supportSonicJIT = false

func sonicMarshalNoMapOrdering(v any) ([]byte, error) {
return jsoniterMarshalNoMapOrdering(v)
func sonicMarshalFastest(v any) ([]byte, error) {
return jsoniterMarshalFastest(v)
}

func sonicMarshalNoHTMLEscape(_ sonic.API) func(v any, prefix, indent string) ([]byte, error) {
Expand Down
4 changes: 2 additions & 2 deletions perf/json/sonic_jit.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import (
"github.com/bytedance/sonic/encoder"
)

const isSonicJIT = true
const supportSonicJIT = true

func sonicMarshalNoMapOrdering(v any) ([]byte, error) {
func sonicMarshalFastest(v any) ([]byte, error) {
return sonic.ConfigFastest.Marshal(v)
}

Expand Down

0 comments on commit e37f488

Please sign in to comment.