optimistic optimizations

This commit is contained in:
Josh Baker 2016-08-19 11:22:59 -07:00
parent a721b7ad07
commit 0e6e567424
3 changed files with 104 additions and 124 deletions

View File

@ -129,13 +129,13 @@ Benchmarks of GJSON alongside [encoding/json](https://golang.org/pkg/encoding/js
and [jsonparser](https://github.com/buger/jsonparser) and [jsonparser](https://github.com/buger/jsonparser)
``` ```
BenchmarkGJSONGet-8 3000000 440 ns/op 0 B/op 0 allocs/op BenchmarkGJSONGet-8 3000000 373 ns/op 0 B/op 0 allocs/op
BenchmarkJSONUnmarshalMap-8 600000 10738 ns/op 3176 B/op 69 allocs/op BenchmarkJSONUnmarshalMap-8 600000 8884 ns/op 3048 B/op 69 allocs/op
BenchmarkJSONUnmarshalStruct-8 600000 11635 ns/op 1960 B/op 69 allocs/op BenchmarkJSONUnmarshalStruct-8 600000 9045 ns/op 1832 B/op 69 allocs/op
BenchmarkJSONDecoder-8 300000 17193 ns/op 4864 B/op 184 allocs/op BenchmarkJSONDecoder-8 300000 14134 ns/op 4224 B/op 184 allocs/op
BenchmarkFFJSONLexer-8 1500000 3773 ns/op 1024 B/op 8 allocs/op BenchmarkFFJSONLexer-8 1500000 3182 ns/op 896 B/op 8 allocs/op
BenchmarkEasyJSONLexer-8 3000000 1134 ns/op 741 B/op 6 allocs/op BenchmarkEasyJSONLexer-8 3000000 932 ns/op 613 B/op 6 allocs/op
BenchmarkJSONParserGet-8 3000000 596 ns/op 21 B/op 0 allocs/op BenchmarkJSONParserGet-8 3000000 444 ns/op 21 B/op 0 allocs/op
``` ```
JSON document used: JSON document used:
@ -177,7 +177,7 @@ widget.text.onMouseUp
``` ```
*These are the results from running the benchmarks on a MacBook Pro 15" 2.8 GHz Intel Core i7:* *These benchmarks were run on a MacBook Pro 15" 2.8 GHz Intel Core i7 using Go 1.7.*
## Contact ## Contact
Josh Baker [@tidwall](http://twitter.com/tidwall) Josh Baker [@tidwall](http://twitter.com/tidwall)

View File

@ -133,8 +133,11 @@ func Get(json string, path string) Result {
// parse the path. just split on the dot // parse the path. just split on the dot
for i := 0; i < len(path); i++ { for i := 0; i < len(path); i++ {
next_part: next_part:
// be optimistic that the path mostly contains lowercase and
// underscore characters.
if path[i] <= '\\' {
if path[i] == '\\' { if path[i] == '\\' {
// go into escape mode // go into escape mode.
epart := []byte(path[s:i]) epart := []byte(path[s:i])
i++ i++
if i < len(path) { if i < len(path) {
@ -173,6 +176,7 @@ func Get(json string, path string) Result {
wild = true wild = true
} }
} }
}
parts = append(parts, part{wild: wild, key: path[s:]}) parts = append(parts, part{wild: wild, key: path[s:]})
end_parts: end_parts:
@ -187,33 +191,37 @@ end_parts:
// look for first delimiter // look for first delimiter
for ; i < len(json); i++ { for ; i < len(json); i++ {
if json[i] > ' ' {
if json[i] == '{' { if json[i] == '{' {
f.stype = '{' f.stype = '{'
i++
stack[0].stype = f.stype
break
} else if json[i] == '[' { } else if json[i] == '[' {
f.stype = '[' f.stype = '['
} else { stack[0].stype = f.stype
// not a valid type
return Result{}
}
i++ i++
break break
} else if json[i] <= ' ' {
continue
} else {
return Result{}
} }
} }
stack[0].stype = f.stype // read the next key
// search for key
read_key: read_key:
if f.stype == '[' { if f.stype == '[' {
// for arrays we use the index of the value as the key.
// so "0" is the key for the first value, and "10" is the
// key for the 10th value.
f.key = strconv.FormatInt(int64(f.count), 10) f.key = strconv.FormatInt(int64(f.count), 10)
f.count++ f.count++
} else { } else {
// for objects we must parse the next string.
for ; i < len(json); i++ { for ; i < len(json); i++ {
// read string
if json[i] == '"' { if json[i] == '"' {
//read to end of key
i++ i++
// readstr
// the first double-quote has already been read // the first double-quote has already been read
s = i s = i
for ; i < len(json); i++ { for ; i < len(json); i++ {
@ -249,16 +257,17 @@ read_key:
} }
break break
} }
// end read string
} }
} }
// end readstr
// we have a brand new key. // we have a brand new (possibly shiny) key.
// is it the key that we are looking for? // is it the key that we are looking for?
if parts[depth-1].wild { if parts[depth-1].wild {
// it's a wildcard path element // it's a wildcard path element
matched = wildcardMatch(f.key, parts[depth-1].key) matched = wildcardMatch(f.key, parts[depth-1].key)
} else { } else {
// just a straight up equality check
matched = parts[depth-1].key == f.key matched = parts[depth-1].key == f.key
} }
@ -325,22 +334,16 @@ read_key:
proc_delim: proc_delim:
if (matched && depth == len(parts)) || !matched { if (matched && depth == len(parts)) || !matched {
// -- BEGIN SQUASH -- // // begin squash
// squash the value, ignoring all nested arrays and objects. // squash the value, ignoring all nested arrays and objects.
s = i - 1 s = i - 1
// the first '[' or '{' has already been read // the first '[' or '{' has already been read
depth := 1 depth := 1
squash:
for ; i < len(json); i++ { for ; i < len(json); i++ {
if json[i] >= '"' && json[i] <= '}' { if json[i] >= '"' && json[i] <= '}' {
if json[i] == '{' || json[i] == '[' { switch json[i] {
depth++ case '"':
} else if json[i] == '}' || json[i] == ']' {
depth--
if depth == 0 {
i++
break
}
} else if json[i] == '"' {
i++ i++
s2 := i s2 := i
for ; i < len(json); i++ { for ; i < len(json); i++ {
@ -361,14 +364,19 @@ proc_delim:
break break
} }
} }
if i == len(json) { case '{', '[':
break depth++
case '}', ']':
depth--
if depth == 0 {
i++
break squash
} }
} }
} }
} }
value.Raw = json[s:i] // end squash
// -- END SQUASH -- // // the 'i' and 's' values should fall-though to the proc_val function
} }
// process the value // process the value
@ -379,6 +387,7 @@ proc_val:
switch vc { switch vc {
case '{', '[': case '{', '[':
value.Type = JSON value.Type = JSON
value.Raw = json[s:i]
case 'n': case 'n':
value.Type = Null value.Type = Null
case 't': case 't':

View File

@ -222,37 +222,8 @@ func TestLess(t *testing.T) {
assert(t, stringLessInsensitive("124abcde", "125abcde")) assert(t, stringLessInsensitive("124abcde", "125abcde"))
} }
/* var exampleJSON = `{
func TestTwitter(t *testing.T) { "widget": {
data, err := ioutil.ReadFile("twitter.json")
if err != nil {
return
}
token := Get(string(data), "search_metadata.max_id")
if token.Num != 505874924095815700 {
t.Fatalf("expecting %d\n", 505874924095815700)
}
}
func BenchmarkTwitter(t *testing.B) {
// the twitter.json file must be present
data, err := ioutil.ReadFile("twitter.json")
if err != nil {
return
}
json := string(data)
t.ResetTimer()
for i := 0; i < t.N; i++ {
token := Get(json, "search_metadata.max_id")
if token.Type != Number || token.Raw != "505874924095815700" || token.Num != 505874924095815700 {
t.Fatal("invalid response")
}
}
}
*/
var exampleJSON = `
{"widget": {
"debug": "on", "debug": "on",
"window": { "window": {
"title": "Sample Konfabulator Widget", "title": "Sample Konfabulator Widget",
@ -274,8 +245,8 @@ var exampleJSON = `
"alignment": "center", "alignment": "center",
"onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
} }
}} }
` }`
type BenchStruct struct { type BenchStruct struct {
Widget struct { Widget struct {