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)

137
gjson.go
View File

@ -133,44 +133,48 @@ 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:
if path[i] == '\\' { // be optimistic that the path mostly contains lowercase and
// go into escape mode // underscore characters.
epart := []byte(path[s:i]) if path[i] <= '\\' {
i++ if path[i] == '\\' {
if i < len(path) { // go into escape mode.
epart = append(epart, path[i]) epart := []byte(path[s:i])
i++ i++
for ; i < len(path); i++ { if i < len(path) {
if path[i] == '\\' {
i++
if i < len(path) {
epart = append(epart, path[i])
}
continue
} else if path[i] == '.' {
parts = append(parts, part{wild: wild, key: string(epart)})
if wild {
wild = false
}
s = i + 1
i++
goto next_part
} else if path[i] == '*' || path[i] == '?' {
wild = true
}
epart = append(epart, path[i]) epart = append(epart, path[i])
i++
for ; i < len(path); i++ {
if path[i] == '\\' {
i++
if i < len(path) {
epart = append(epart, path[i])
}
continue
} else if path[i] == '.' {
parts = append(parts, part{wild: wild, key: string(epart)})
if wild {
wild = false
}
s = i + 1
i++
goto next_part
} else if path[i] == '*' || path[i] == '?' {
wild = true
}
epart = append(epart, path[i])
}
} }
parts = append(parts, part{wild: wild, key: string(epart)})
goto end_parts
} else if path[i] == '.' {
parts = append(parts, part{wild: wild, key: path[s:i]})
if wild {
wild = false
}
s = i + 1
} else if path[i] == '*' || path[i] == '?' {
wild = true
} }
parts = append(parts, part{wild: wild, key: string(epart)})
goto end_parts
} else if path[i] == '.' {
parts = append(parts, part{wild: wild, key: path[s:i]})
if wild {
wild = false
}
s = i + 1
} else if path[i] == '*' || path[i] == '?' {
wild = true
} }
} }
parts = append(parts, part{wild: wild, key: path[s:]}) parts = append(parts, part{wild: wild, key: path[s:]})
@ -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++
} else if json[i] == '[' { stack[0].stype = f.stype
f.stype = '[' break
} else { } else if json[i] == '[' {
// not a valid type f.stype = '['
return Result{} stack[0].stype = f.stype
}
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,60 +222,31 @@ 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") "debug": "on",
if err != nil { "window": {
return "title": "Sample Konfabulator Widget",
} "name": "main_window",
token := Get(string(data), "search_metadata.max_id") "width": 500,
if token.Num != 505874924095815700 { "height": 500
t.Fatalf("expecting %d\n", 505874924095815700) },
} "image": {
"src": "Images/Sun.png",
} "hOffset": 250,
func BenchmarkTwitter(t *testing.B) { "vOffset": 250,
// the twitter.json file must be present "alignment": "center"
data, err := ioutil.ReadFile("twitter.json") },
if err != nil { "text": {
return "data": "Click Here",
} "size": 36,
json := string(data) "style": "bold",
t.ResetTimer() "vOffset": 100,
for i := 0; i < t.N; i++ { "alignment": "center",
token := Get(json, "search_metadata.max_id") "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
if token.Type != Number || token.Raw != "505874924095815700" || token.Num != 505874924095815700 {
t.Fatal("invalid response")
} }
} }
} }`
*/
var exampleJSON = `
{"widget": {
"debug": "on",
"window": {
"title": "Sample Konfabulator Widget",
"name": "main_window",
"width": 500,
"height": 500
},
"image": {
"src": "Images/Sun.png",
"hOffset": 250,
"vOffset": 250,
"alignment": "center"
},
"text": {
"data": "Click Here",
"size": 36,
"style": "bold",
"vOffset": 100,
"alignment": "center",
"onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
}
}}
`
type BenchStruct struct { type BenchStruct struct {
Widget struct { Widget struct {