Last active
March 9, 2016 15:22
-
-
Save janisz/8b20eaa1197728e09d6a to your computer and use it in GitHub Desktop.
JSON deserialization benchmark
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
//Results from below tests show why we've decided to use ffjson instead of json deserializer | |
import "testing" | |
import ( | |
"encoding/json" | |
) | |
//form https://randomuser.me/ | |
var blob = []byte(`{ | |
"id": "unique-id", | |
"user": { | |
"email": "manuela.velasco50@example.com", | |
"username": "heavybutterfly920", | |
"password": "enterprise", | |
"salt": "egEn6YsO", | |
"md5": "2dd1894ea9d19bf5479992da95713a3a", | |
"sha1": "ba230bc400723f470b68e9609ab7d0e6cf123b59", | |
"registered": "1303647245", | |
"dob": "415458547", | |
"phone": "994-131-106", | |
"cell": "626-695-164", | |
"DNI": "52434048-I" | |
} | |
}`) | |
var wrongBlob = []byte(`{"NOT JSON"...}`) | |
func BenchmarkJsonUnmarshal(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
var userWithId UserWithId | |
if err := json.Unmarshal(blob, &userWithId); err != nil { | |
b.Log("%v %s\n", userWithId, err) | |
} | |
} | |
} | |
func BenchmarkFastJsonUnmarshal(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
var userWithId UserWithId | |
if err := userWithId.UnmarshalJSON(blob); err != nil { | |
b.Log("%v %s\n", userWithId, err) | |
} | |
} | |
} | |
func BenchmarkJsonErrUnmarshal(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
var userWithId UserWithId | |
if err := json.Unmarshal(wrongBlob, &userWithId); err == nil { | |
b.Log("Expected error but got nil") | |
} | |
} | |
} | |
func BenchmarkFastJsonErrUnmarshal(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
var userWithId UserWithId | |
if err := userWithId.UnmarshalJSON(wrongBlob); err == nil { | |
b.Log("Expected error but got nil") | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
go test -bench=. -benchmem | |
testing: warning: no tests to run | |
PASS | |
BenchmarkJsonUnmarshal-4 100000 15354 ns/op 1875 B/op 37 allocs/op | |
BenchmarkFastJsonUnmarshal-4 200000 7200 ns/op 1555 B/op 31 allocs/op | |
BenchmarkJsonErrUnmarshal-4 2000000 1027 ns/op 384 B/op 9 allocs/op | |
BenchmarkFastJsonErrUnmarshal-4 500000 2598 ns/op 528 B/op 13 allocs/op | |
ok stash.allegrogroup.com/test 7.606s |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
type user map[string]string | |
type UserWithId struct { | |
ID string `json:"id"` | |
User user `json:"user"` | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// DO NOT EDIT! | |
// Code generated by ffjson <https://github.com/pquerna/ffjson> | |
// source: ../ALTT/metadata-service/model/user.go | |
// DO NOT EDIT! | |
package main | |
import ( | |
"bytes" | |
"fmt" | |
fflib "github.com/pquerna/ffjson/fflib/v1" | |
) | |
const ( | |
ffj_t_UserWithIdbase = iota | |
ffj_t_UserWithIdno_such_key | |
ffj_t_UserWithId_ID | |
ffj_t_UserWithId_User | |
) | |
var ffj_key_UserWithId_ID = []byte("id") | |
var ffj_key_UserWithId_User = []byte("user") | |
func (uj *UserWithId) UnmarshalJSON(input []byte) error { | |
fs := fflib.NewFFLexer(input) | |
return uj.UnmarshalJSONFFLexer(fs, fflib.FFParse_map_start) | |
} | |
func (uj *UserWithId) UnmarshalJSONFFLexer(fs *fflib.FFLexer, state fflib.FFParseState) error { | |
var err error = nil | |
currentKey := ffj_t_UserWithIdbase | |
_ = currentKey | |
tok := fflib.FFTok_init | |
wantedTok := fflib.FFTok_init | |
mainparse: | |
for { | |
tok = fs.Scan() | |
// println(fmt.Sprintf("debug: tok: %v state: %v", tok, state)) | |
if tok == fflib.FFTok_error { | |
goto tokerror | |
} | |
switch state { | |
case fflib.FFParse_map_start: | |
if tok != fflib.FFTok_left_bracket { | |
wantedTok = fflib.FFTok_left_bracket | |
goto wrongtokenerror | |
} | |
state = fflib.FFParse_want_key | |
continue | |
case fflib.FFParse_after_value: | |
if tok == fflib.FFTok_comma { | |
state = fflib.FFParse_want_key | |
} else if tok == fflib.FFTok_right_bracket { | |
goto done | |
} else { | |
wantedTok = fflib.FFTok_comma | |
goto wrongtokenerror | |
} | |
case fflib.FFParse_want_key: | |
// json {} ended. goto exit. woo. | |
if tok == fflib.FFTok_right_bracket { | |
goto done | |
} | |
if tok != fflib.FFTok_string { | |
wantedTok = fflib.FFTok_string | |
goto wrongtokenerror | |
} | |
kn := fs.Output.Bytes() | |
if len(kn) <= 0 { | |
// "" case. hrm. | |
currentKey = ffj_t_UserWithIdno_such_key | |
state = fflib.FFParse_want_colon | |
goto mainparse | |
} else { | |
switch kn[0] { | |
case 'i': | |
if bytes.Equal(ffj_key_UserWithId_ID, kn) { | |
currentKey = ffj_t_UserWithId_ID | |
state = fflib.FFParse_want_colon | |
goto mainparse | |
} | |
case 'u': | |
if bytes.Equal(ffj_key_UserWithId_User, kn) { | |
currentKey = ffj_t_UserWithId_User | |
state = fflib.FFParse_want_colon | |
goto mainparse | |
} | |
} | |
if fflib.EqualFoldRight(ffj_key_UserWithId_User, kn) { | |
currentKey = ffj_t_UserWithId_User | |
state = fflib.FFParse_want_colon | |
goto mainparse | |
} | |
if fflib.SimpleLetterEqualFold(ffj_key_UserWithId_ID, kn) { | |
currentKey = ffj_t_UserWithId_ID | |
state = fflib.FFParse_want_colon | |
goto mainparse | |
} | |
currentKey = ffj_t_UserWithIdno_such_key | |
state = fflib.FFParse_want_colon | |
goto mainparse | |
} | |
case fflib.FFParse_want_colon: | |
if tok != fflib.FFTok_colon { | |
wantedTok = fflib.FFTok_colon | |
goto wrongtokenerror | |
} | |
state = fflib.FFParse_want_value | |
continue | |
case fflib.FFParse_want_value: | |
if tok == fflib.FFTok_left_brace || tok == fflib.FFTok_left_bracket || tok == fflib.FFTok_integer || tok == fflib.FFTok_double || tok == fflib.FFTok_string || tok == fflib.FFTok_bool || tok == fflib.FFTok_null { | |
switch currentKey { | |
case ffj_t_UserWithId_ID: | |
goto handle_ID | |
case ffj_t_UserWithId_User: | |
goto handle_User | |
case ffj_t_UserWithIdno_such_key: | |
err = fs.SkipField(tok) | |
if err != nil { | |
return fs.WrapErr(err) | |
} | |
state = fflib.FFParse_after_value | |
goto mainparse | |
} | |
} else { | |
goto wantedvalue | |
} | |
} | |
} | |
handle_ID: | |
/* handler: uj.ID type=string kind=string quoted=false*/ | |
{ | |
{ | |
if tok != fflib.FFTok_string && tok != fflib.FFTok_null { | |
return fs.WrapErr(fmt.Errorf("cannot unmarshal %s into Go value for string", tok)) | |
} | |
} | |
if tok == fflib.FFTok_null { | |
} else { | |
outBuf := fs.Output.Bytes() | |
uj.ID = string(string(outBuf)) | |
} | |
} | |
state = fflib.FFParse_after_value | |
goto mainparse | |
handle_User: | |
/* handler: uj.User type=model.user kind=map quoted=false*/ | |
{ | |
{ | |
if tok != fflib.FFTok_left_bracket && tok != fflib.FFTok_null { | |
return fs.WrapErr(fmt.Errorf("cannot unmarshal %s into Go value for user", tok)) | |
} | |
} | |
if tok == fflib.FFTok_null { | |
uj.User = nil | |
} else { | |
uj.User = make(map[string]string, 0) | |
wantVal := true | |
for { | |
var k string | |
var tmp_uj__User string | |
tok = fs.Scan() | |
if tok == fflib.FFTok_error { | |
goto tokerror | |
} | |
if tok == fflib.FFTok_right_bracket { | |
break | |
} | |
if tok == fflib.FFTok_comma { | |
if wantVal == true { | |
// TODO(pquerna): this isn't an ideal error message, this handles | |
// things like [,,,] as an array value. | |
return fs.WrapErr(fmt.Errorf("wanted value token, but got token: %v", tok)) | |
} | |
continue | |
} else { | |
wantVal = true | |
} | |
/* handler: k type=string kind=string quoted=false*/ | |
{ | |
{ | |
if tok != fflib.FFTok_string && tok != fflib.FFTok_null { | |
return fs.WrapErr(fmt.Errorf("cannot unmarshal %s into Go value for string", tok)) | |
} | |
} | |
if tok == fflib.FFTok_null { | |
} else { | |
outBuf := fs.Output.Bytes() | |
k = string(string(outBuf)) | |
} | |
} | |
// Expect ':' after key | |
tok = fs.Scan() | |
if tok != fflib.FFTok_colon { | |
return fs.WrapErr(fmt.Errorf("wanted colon token, but got token: %v", tok)) | |
} | |
tok = fs.Scan() | |
/* handler: tmp_uj__User type=string kind=string quoted=false*/ | |
{ | |
{ | |
if tok != fflib.FFTok_string && tok != fflib.FFTok_null { | |
return fs.WrapErr(fmt.Errorf("cannot unmarshal %s into Go value for string", tok)) | |
} | |
} | |
if tok == fflib.FFTok_null { | |
} else { | |
outBuf := fs.Output.Bytes() | |
tmp_uj__User = string(string(outBuf)) | |
} | |
} | |
uj.User[k] = tmp_uj__User | |
wantVal = false | |
} | |
} | |
} | |
state = fflib.FFParse_after_value | |
goto mainparse | |
wantedvalue: | |
return fs.WrapErr(fmt.Errorf("wanted value token, but got token: %v", tok)) | |
wrongtokenerror: | |
return fs.WrapErr(fmt.Errorf("ffjson: wanted token: %v, but got token: %v output=%s", wantedTok, tok, fs.Output.String())) | |
tokerror: | |
if fs.BigError != nil { | |
return fs.WrapErr(fs.BigError) | |
} | |
err = fs.Error.ToError() | |
if err != nil { | |
return fs.WrapErr(err) | |
} | |
panic("ffjson-generated: unreachable, please report bug.") | |
done: | |
return nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment