Skip to content

Commit

Permalink
test: Add benchmark
Browse files Browse the repository at this point in the history
  • Loading branch information
romshark committed Mar 20, 2024
1 parent f9bf59d commit 37bfc39
Showing 1 changed file with 263 additions and 0 deletions.
263 changes: 263 additions & 0 deletions bench/any_complex_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
package bench_test

import (
json "encoding/json"
"runtime"
"testing"

jscandec "github.com/romshark/jscan-experimental-decoder"
"github.com/romshark/jscan-experimental-decoder/bench"
"github.com/romshark/jscan-experimental-decoder/bench/easyjsongen"
"github.com/romshark/jscan-experimental-decoder/bench/ffjsongen"

jsonv2 "github.com/go-json-experiment/json"
goccy "github.com/goccy/go-json"
jsoniter "github.com/json-iterator/go"
easyjson "github.com/mailru/easyjson"
ffjson "github.com/pquerna/ffjson/ffjson"
jscan "github.com/romshark/jscan/v2"
segmentio "github.com/segmentio/encoding/json"
"github.com/stretchr/testify/require"
)

func TestImplementationsAnyComplex(t *testing.T) {
in := `{
"name": "Test name",
"number": 100553,
"boolean": true,
"null": null,
"tags": ["sports", "portable"]
}`
expect := func() any {
return map[string]any{
"name": "Test name",
"number": float64(100553),
"boolean": true,
"null": nil,
"tags": []any{"sports", "portable"},
}
}

t.Run("unmr/encoding_json", func(t *testing.T) {
var v any
require.NoError(t, json.Unmarshal([]byte(in), &v))
require.Equal(t, expect(), v)
})

t.Run("unmr/jsoniter", func(t *testing.T) {
var v any
require.NoError(t, jsoniter.Unmarshal([]byte(in), &v))
require.Equal(t, expect(), v)
})

t.Run("unmr/goccy", func(t *testing.T) {
var v any
require.NoError(t, goccy.Unmarshal([]byte(in), &v))
require.Equal(t, expect(), v)
})

t.Run("unmr/jsonv2", func(t *testing.T) {
var v any
opts := jsonv2.MatchCaseInsensitiveNames(false)
require.NoError(t, jsonv2.Unmarshal([]byte(in), &v, opts))
require.Equal(t, expect(), v)
})

t.Run("unmr/segmentio", func(t *testing.T) {
var v any
require.NoError(t, segmentio.Unmarshal([]byte(in), &v))
require.Equal(t, expect(), v)
})

t.Run("unmr/jscan", func(t *testing.T) {
d, err := jscandec.NewDecoder[[]byte, any](
jscan.NewTokenizer[[]byte](32, 128), jscandec.DefaultInitOptions,
)
// Disable unescaping and insensitive field matching for maximum performance.
opts := &jscandec.DecodeOptions{
DisableFieldNameUnescaping: true,
DisableCaseInsensitiveMatching: true,
}
require.NoError(t, err)
var v any
if err := d.Decode([]byte(in), &v, opts); err.IsErr() {
t.Fatal(err)
}
require.Equal(t, expect(), v)
})

t.Run("unmr/jscan_unmarshal", func(t *testing.T) {
var v any
if err := jscandec.Unmarshal([]byte(in), &v); err != nil {
t.Fatal(err)
}
require.Equal(t, expect(), v)
})

t.Run("genr/easyjson", func(t *testing.T) {
// We need to wrap the original input string into an object
// since easyjson only supports struct unmarshalers
in := []byte(`{"data":` + string(in) + `}`)
var v easyjsongen.Any
require.NoError(t, easyjson.Unmarshal(in, &v))
require.Equal(t, expect(), v.Data)
})

t.Run("genr/ffjson", func(t *testing.T) {
// We need to wrap the original input string into an object
// since ffjson only supports struct unmarshalers
in := []byte(`{"data":` + string(in) + `}`)
var v ffjsongen.Any
require.NoError(t, ffjson.Unmarshal(in, &v))
require.Equal(t, expect(), v.Data)
})

t.Run("hand/gjson", func(t *testing.T) {
v, err := bench.GJSONAny([]byte(in))
require.NoError(t, err)
require.Equal(t, expect(), v)
})

t.Run("hand/jscan", func(t *testing.T) {
tokenizer := jscan.NewTokenizer[[]byte](8, len(in)/2)
v, err := bench.JscanAny(tokenizer, []byte(in))
require.NoError(t, err)
require.Equal(t, expect(), v)
})
}

func BenchmarkDecodeAnyComplex(b *testing.B) {
in := []byte(`{
"name": "Test name",
"number": 100553,
"tags": ["sports", "portable"]
}`)

b.Run("unmr/encoding_json", func(b *testing.B) {
for n := 0; n < b.N; n++ {
var v any
if err := json.Unmarshal(in, &v); err != nil {
b.Fatal(err)
}
}
})

b.Run("unmr/jsoniter", func(b *testing.B) {
for n := 0; n < b.N; n++ {
var v any
if err := jsoniter.Unmarshal(in, &v); err != nil {
b.Fatal(err)
}
}
})

b.Run("unmr/goccy", func(b *testing.B) {
for n := 0; n < b.N; n++ {
var v any
if err := goccy.Unmarshal(in, &v); err != nil {
b.Fatal(err)
}
}
})

b.Run("unmr/jsonv2", func(b *testing.B) {
opts := jsonv2.MatchCaseInsensitiveNames(false)
b.ResetTimer()
for n := 0; n < b.N; n++ {
var v any
if err := jsonv2.Unmarshal(in, &v, opts); err != nil {
b.Fatal(err)
}
}
})

b.Run("unmr/segmentio", func(b *testing.B) {
for n := 0; n < b.N; n++ {
var v any
if err := segmentio.Unmarshal(in, &v); err != nil {
b.Fatal(err)
}
}
})

b.Run("unmr/jscan", func(b *testing.B) {
tokenizer := jscan.NewTokenizer[[]byte](32, 128)
d, err := jscandec.NewDecoder[[]byte, any](
tokenizer, jscandec.DefaultInitOptions,
)
if err != nil {
b.Fatal(err)
}
// Disable unescaping and insensitive field matching for maximum performance.
opts := &jscandec.DecodeOptions{
DisableFieldNameUnescaping: true,
DisableCaseInsensitiveMatching: true,
}
b.ResetTimer()
for n := 0; n < b.N; n++ {
var v any
if err := d.Decode(in, &v, opts); err.IsErr() {
b.Fatal(err)
}
}
})

b.Run("unmr/jscan_unmarshal", func(b *testing.B) {
for n := 0; n < b.N; n++ {
var v any
if err := jscandec.Unmarshal(in, &v); err != nil {
b.Fatal(err)
}
}
})

b.Run("genr/easyjson", func(b *testing.B) {
// We need to wrap the original input string into an object
// since easyjson only supports struct unmarshalers
in := []byte(`{"data":` + string(in) + `}`)
b.ResetTimer()
for n := 0; n < b.N; n++ {
var v easyjsongen.Any
if err := easyjson.Unmarshal(in, &v); err != nil {
b.Fatal(err)
}
}
})

b.Run("genr/ffjson", func(b *testing.B) {
// We need to wrap the original input string into an object
// since ffjson only supports struct unmarshalers
in := []byte(`{"data":` + string(in) + `}`)
b.ResetTimer()
for n := 0; n < b.N; n++ {
var v ffjsongen.Any
if err := ffjson.Unmarshal(in, &v); err != nil {
b.Fatal(err)
}
}
})

b.Run("hand/gjson", func(b *testing.B) {
var v any
var err error
for n := 0; n < b.N; n++ {
if v, err = bench.GJSONAny(in); err != nil {
b.Fatal(err)
}
}
runtime.KeepAlive(v)
})

b.Run("hand/jscan", func(b *testing.B) {
tokenizer := jscan.NewTokenizer[[]byte](8, len(in)/2)
var v any
b.ResetTimer()
for n := 0; n < b.N; n++ {
var err error
if v, err = bench.JscanAny(tokenizer, in); err != nil {
b.Fatal(err)
}
}
runtime.KeepAlive(v)
})
}

0 comments on commit 37bfc39

Please sign in to comment.