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)
```
BenchmarkGJSONGet-8 3000000 440 ns/op 0 B/op 0 allocs/op
BenchmarkJSONUnmarshalMap-8 600000 10738 ns/op 3176 B/op 69 allocs/op
BenchmarkJSONUnmarshalStruct-8 600000 11635 ns/op 1960 B/op 69 allocs/op
BenchmarkJSONDecoder-8 300000 17193 ns/op 4864 B/op 184 allocs/op
BenchmarkFFJSONLexer-8 1500000 3773 ns/op 1024 B/op 8 allocs/op
BenchmarkEasyJSONLexer-8 3000000 1134 ns/op 741 B/op 6 allocs/op
BenchmarkJSONParserGet-8 3000000 596 ns/op 21 B/op 0 allocs/op
BenchmarkGJSONGet-8 3000000 373 ns/op 0 B/op 0 allocs/op
BenchmarkJSONUnmarshalMap-8 600000 8884 ns/op 3048 B/op 69 allocs/op
BenchmarkJSONUnmarshalStruct-8 600000 9045 ns/op 1832 B/op 69 allocs/op
BenchmarkJSONDecoder-8 300000 14134 ns/op 4224 B/op 184 allocs/op
BenchmarkFFJSONLexer-8 1500000 3182 ns/op 896 B/op 8 allocs/op
BenchmarkEasyJSONLexer-8 3000000 932 ns/op 613 B/op 6 allocs/op
BenchmarkJSONParserGet-8 3000000 444 ns/op 21 B/op 0 allocs/op
```
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
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
for i := 0; i < len(path); i++ {
next_part:
if path[i] == '\\' {
// go into escape mode
epart := []byte(path[s:i])
i++
if i < len(path) {
epart = append(epart, path[i])
// be optimistic that the path mostly contains lowercase and
// underscore characters.
if path[i] <= '\\' {
if path[i] == '\\' {
// go into escape mode.
epart := []byte(path[s: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
}
if i < len(path) {
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:]})
@ -187,33 +191,37 @@ end_parts:
// look for first delimiter
for ; i < len(json); i++ {
if json[i] > ' ' {
if json[i] == '{' {
f.stype = '{'
} else if json[i] == '[' {
f.stype = '['
} else {
// not a valid type
return Result{}
}
if json[i] == '{' {
f.stype = '{'
i++
stack[0].stype = f.stype
break
} else if json[i] == '[' {
f.stype = '['
stack[0].stype = f.stype
i++
break
} else if json[i] <= ' ' {
continue
} else {
return Result{}
}
}
stack[0].stype = f.stype
// search for key
// read the next key
read_key:
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.count++
} else {
// for objects we must parse the next string.
for ; i < len(json); i++ {
// read string
if json[i] == '"' {
//read to end of key
i++
// readstr
// the first double-quote has already been read
s = i
for ; i < len(json); i++ {
@ -249,16 +257,17 @@ read_key:
}
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?
if parts[depth-1].wild {
// it's a wildcard path element
matched = wildcardMatch(f.key, parts[depth-1].key)
} else {
// just a straight up equality check
matched = parts[depth-1].key == f.key
}
@ -325,22 +334,16 @@ read_key:
proc_delim:
if (matched && depth == len(parts)) || !matched {
// -- BEGIN SQUASH -- //
// begin squash
// squash the value, ignoring all nested arrays and objects.
s = i - 1
// the first '[' or '{' has already been read
depth := 1
squash:
for ; i < len(json); i++ {
if json[i] >= '"' && json[i] <= '}' {
if json[i] == '{' || json[i] == '[' {
depth++
} else if json[i] == '}' || json[i] == ']' {
depth--
if depth == 0 {
i++
break
}
} else if json[i] == '"' {
switch json[i] {
case '"':
i++
s2 := i
for ; i < len(json); i++ {
@ -361,14 +364,19 @@ proc_delim:
break
}
}
if i == len(json) {
break
case '{', '[':
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
@ -379,6 +387,7 @@ proc_val:
switch vc {
case '{', '[':
value.Type = JSON
value.Raw = json[s:i]
case 'n':
value.Type = Null
case 't':

View File

@ -222,60 +222,31 @@ func TestLess(t *testing.T) {
assert(t, stringLessInsensitive("124abcde", "125abcde"))
}
/*
func TestTwitter(t *testing.T) {
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",
"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;"
}
}
}
*/
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 {
Widget struct {