Skip to content

Commit

Permalink
feat(dec): zero alloc ObjBytes
Browse files Browse the repository at this point in the history
  • Loading branch information
ernado committed Nov 7, 2021
1 parent b50d1fe commit 65b21f1
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 23 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ _bin/*

*.out
*.dump
*.test

corpus
23 changes: 23 additions & 0 deletions alloc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package jx

import (
"testing"

"github.com/go-faster/errors"
)

const defaultAllocRuns = 10
Expand Down Expand Up @@ -41,6 +43,27 @@ func TestZeroAlloc(t *testing.T) {
return d.Validate()
})
})
t.Run("ObjBytes", func(t *testing.T) {
zeroAllocDec(t, benchData, func(d *Decoder) error {
return d.Arr(func(d *Decoder) error {
return d.ObjBytes(func(d *Decoder, key []byte) error {
switch string(key) {
case "person", "company": // ok
default:
return errors.New("unexpected key")
}
switch d.Next() {
case Object:
return d.ObjBytes(func(d *Decoder, key []byte) error {
return d.Skip()
})
default:
return d.Skip()
}
})
})
})
})
t.Run("Int", func(t *testing.T) {
zeroAllocDecStr(t, "12345", func(d *Decoder) error {
v, err := d.Int()
Expand Down
2 changes: 1 addition & 1 deletion any_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func (v *Any) Read(d *Decoder) error {
}
v.Str = s
v.Type = AnyStr
case Nil:
case Null:
if err := d.Null(); err != nil {
return errors.Wrap(err, "null")
}
Expand Down
153 changes: 145 additions & 8 deletions bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,162 @@
package jx

import (
"bytes"
_ "embed"
"encoding/json"
"testing"

"github.com/go-faster/errors"
)

//go:embed testdata/file.json
var benchData []byte

func Benchmark_large_file(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(len(benchData)))
d := Decode(nil, 4096)
b.Run("JX", func(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(len(benchData)))
d := Decode(nil, 4096)

for n := 0; n < b.N; n++ {
d.ResetBytes(benchData)
if err := d.Arr(nil); err != nil {
b.Fatal(err)
for n := 0; n < b.N; n++ {
d.ResetBytes(benchData)
if err := d.Arr(func(d *Decoder) error {
return d.ObjBytes(func(d *Decoder, key []byte) error {
switch string(key) {
case "person", "company": // ok
default:
return errors.New("unexpected key")
}
switch d.Next() {
case Object:
return d.ObjBytes(func(d *Decoder, key []byte) error {
switch d.Next() {
case String:
_, err := d.StrBytes()
return err
case Number:
_, err := d.Num()
return err
case Null:
return d.Null()
default:
return d.Skip()
}
})
default:
return d.Skip()
}
})
}); err != nil {
b.Fatal(err)
}
}
}
})
b.Run("Std", func(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(len(benchData)))

type T struct {
Person struct {
ID string `json:"id"`
Name struct {
FullName string `json:"fullName"`
GivenName string `json:"givenName"`
FamilyName string `json:"familyName"`
} `json:"name"`
Email string `json:"email"`
Gender string `json:"gender"`
Location string `json:"location"`
Geo struct {
City string `json:"city"`
State string `json:"state"`
Country string `json:"country"`
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
} `json:"geo"`
Bio string `json:"bio"`
Site string `json:"site"`
Avatar string `json:"avatar"`
Employment struct {
Name string `json:"name"`
Title string `json:"title"`
Domain string `json:"domain"`
} `json:"employment"`
Facebook struct {
Handle string `json:"handle"`
} `json:"facebook"`
Github struct {
Handle string `json:"handle"`
ID int `json:"id"`
Avatar string `json:"avatar"`
Company string `json:"company"`
Blog string `json:"blog"`
Followers int `json:"followers"`
Following int `json:"following"`
} `json:"github"`
Twitter struct {
Handle string `json:"handle"`
ID int `json:"id"`
Bio json.RawMessage `json:"bio"`
Followers int `json:"followers"`
Following int `json:"following"`
Statuses int `json:"statuses"`
Favorites int `json:"favorites"`
Location string `json:"location"`
Site string `json:"site"`
Avatar json.RawMessage `json:"avatar"`
} `json:"twitter"`
Linkedin struct {
Handle string `json:"handle"`
} `json:"linkedin"`
Googleplus struct {
Handle json.RawMessage `json:"handle"`
} `json:"googleplus"`
Angellist struct {
Handle string `json:"handle"`
ID int `json:"id"`
Bio string `json:"bio"`
Blog string `json:"blog"`
Site string `json:"site"`
Followers int `json:"followers"`
Avatar string `json:"avatar"`
} `json:"angellist"`
Klout struct {
Handle json.RawMessage `json:"handle"`
Score json.RawMessage `json:"score"`
} `json:"klout"`
Foursquare struct {
Handle json.RawMessage `json:"handle"`
} `json:"foursquare"`
Aboutme struct {
Handle string `json:"handle"`
Bio json.RawMessage `json:"bio"`
Avatar json.RawMessage `json:"avatar"`
} `json:"aboutme"`
Gravatar struct {
Handle string `json:"handle"`
Urls json.RawMessage `json:"urls"`
Avatar string `json:"avatar"`
Avatars []struct {
URL string `json:"url"`
Type string `json:"type"`
} `json:"avatars"`
} `json:"gravatar"`
Fuzzy bool `json:"fuzzy"`
} `json:"person"`
Company string `json:"company"`
}

buf := new(bytes.Reader)
d := json.NewDecoder(buf)
var target []T
for n := 0; n < b.N; n++ {
buf.Reset(benchData)
if err := d.Decode(&target); err != nil {
b.Fatal(err)
}
}
})
}

func BenchmarkValid(b *testing.B) {
Expand Down
20 changes: 10 additions & 10 deletions dec.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func (t Type) String() string {
return "string"
case Number:
return "number"
case Nil:
case Null:
return "nil"
case Bool:
return "bool"
Expand All @@ -31,19 +31,19 @@ func (t Type) String() string {
}

const (
// Invalid invalid JSON element
// Invalid json value.
Invalid Type = iota
// String JSON element "string"
// String json value, like "foo".
String
// Number JSON element 100 or 0.10
// Number json value, like 100 or 1.01.
Number
// Nil JSON element null
Nil
// Bool JSON element true or false
// Null json value.
Null
// Bool json value, true or false.
Bool
// Array JSON element []
// Array json value, like [1, 2, 3].
Array
// Object JSON element {}
// Object json value, like {"foo": 1}.
Object
)

Expand Down Expand Up @@ -82,7 +82,7 @@ func init() {
types['9'] = Number
types['t'] = Bool
types['f'] = Bool
types['n'] = Nil
types['n'] = Null
types['['] = Array
types['{'] = Object
}
Expand Down
4 changes: 2 additions & 2 deletions dec_b64.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
//
// Same as encoding/json, base64.StdEncoding or RFC 4648.
func (d *Decoder) Base64() ([]byte, error) {
if d.Next() == Nil {
if d.Next() == Null {
if err := d.Null(); err != nil {
return nil, errors.Wrap(err, "read null")
}
Expand All @@ -23,7 +23,7 @@ func (d *Decoder) Base64() ([]byte, error) {
//
// Same as encoding/json, base64.StdEncoding or RFC 4648.
func (d *Decoder) Base64Append(b []byte) ([]byte, error) {
if d.Next() == Nil {
if d.Next() == Null {
if err := d.Null(); err != nil {
return nil, errors.Wrap(err, "read null")
}
Expand Down
4 changes: 2 additions & 2 deletions dec_obj.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func (d *Decoder) ObjBytes(f func(d *Decoder, key []byte) error) error {
}
d.unread()

k, err := d.str(value{ignore: skip})
k, err := d.str(value{ignore: skip, raw: true})
if err != nil {
return errors.Wrap(err, "str")
}
Expand All @@ -52,7 +52,7 @@ func (d *Decoder) ObjBytes(f func(d *Decoder, key []byte) error) error {
return errors.Wrap(err, "next")
}
for c == ',' {
k, err := d.str(value{ignore: skip})
k, err := d.str(value{ignore: skip, raw: true})
if err != nil {
return errors.Wrap(err, "str")
}
Expand Down

0 comments on commit 65b21f1

Please sign in to comment.