Skip to content

Commit

Permalink
Add parser local caches for better performance and faster reuse (#8)
Browse files Browse the repository at this point in the history
* Add parser local caches for better performance and faster reuse

* Fix metalint
  • Loading branch information
xichen2020 authored Nov 21, 2018
1 parent 0277f95 commit ed94452
Show file tree
Hide file tree
Showing 9 changed files with 409 additions and 137 deletions.
1 change: 1 addition & 0 deletions generated/generics/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
//go:generate sh -c "cat $GOPATH/src/$PACKAGE/x/pool/generic_bucketized_pool.go | awk '/^package/{i++}i' | genny -out=$GOPATH/src/$PACKAGE/value/bucketized_value_array_pool.gen.go -pkg=value gen \"ValueBucket=ArrayBucket ValuePoolOptions=ArrayPoolOptions ValuePool=ArrayPool valueBucketByCapacity=arrayBucketByCapacity bucketPool=arrayBucketPool BucketizedValuePool=BucketizedArrayPool GenericValue=Array\""
//go:generate sh -c "cat $GOPATH/src/$PACKAGE/x/pool/generic_pool.go | awk '/^package/{i++}i' | genny -out=$GOPATH/src/$PACKAGE/value/kv_array_pool.gen.go -pkg=value gen \"ValuePoolOptions=KVArrayPoolOptions valuePoolMetrics=kvArrayPoolMetrics ValuePool=KVArrayPool GenericValue=KVArray\""
//go:generate sh -c "cat $GOPATH/src/$PACKAGE/x/pool/generic_bucketized_pool.go | awk '/^package/{i++}i' | genny -out=$GOPATH/src/$PACKAGE/value/bucketized_kv_array_pool.gen.go -pkg=value gen \"ValueBucket=KVArrayBucket ValuePoolOptions=KVArrayPoolOptions ValuePool=KVArrayPool valueBucketByCapacity=kvArrayBucketByCapacity bucketPool=kvArrayBucketPool BucketizedValuePool=BucketizedKVArrayPool GenericValue=KVArray\""
//go:generate sh -c "cat $GOPATH/src/$PACKAGE/x/pool/generic_pool.go | awk '/^package/{i++}i' | genny -out=$GOPATH/src/$PACKAGE/parser/json/parser_pool.gen.go -pkg=json gen \"ValuePoolOptions=ParserPoolOptions valuePoolMetrics=parserPoolMetrics ValuePool=ParserPool GenericValue=Parser\""

package generics
60 changes: 5 additions & 55 deletions parser/json/options.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,21 @@
package json

import "github.com/xichen2020/eventdb/value"

const (
defaultMaxDepth = 3
)

// Options provide a set of parsing options.
// TODO(xichen): limit the maximum capacity of each caching array via options.
// TODO(xichen): limit the maximum capacity of cached value arrays and KV arrays via options.
type Options struct {
maxDepth int
valuePool *value.Pool
valueArrayPool *value.BucketizedArrayPool
kvArrayPool *value.BucketizedKVArrayPool
maxDepth int
}

// NewOptions creates a new set of parsing options.
func NewOptions() *Options {
o := &Options{
maxDepth: defaultMaxDepth,
valuePool: value.NewPool(nil),
valueArrayPool: value.NewBucketizedArrayPool(nil, nil),
kvArrayPool: value.NewBucketizedKVArrayPool(nil, nil),
return &Options{
maxDepth: defaultMaxDepth,
}
o.initPools()
return o
}

// SetMaxDepth sets the maximum depth eligible for parsing.
Expand All @@ -35,45 +27,3 @@ func (o *Options) SetMaxDepth(v int) *Options {

// MaxDepth returns the maximum depth eligible for parsing.
func (o *Options) MaxDepth() int { return o.maxDepth }

// SetValuePool sets the pool for values.
func (o *Options) SetValuePool(v *value.Pool) *Options {
opts := *o
opts.valuePool = v
return &opts
}

// ValuePool returns the pool for values.
func (o *Options) ValuePool() *value.Pool { return o.valuePool }

// SetValueArrayPool sets the pool for value arrays.
func (o *Options) SetValueArrayPool(v *value.BucketizedArrayPool) *Options {
opts := *o
opts.valueArrayPool = v
return &opts
}

// ValueArrayPool returns the pool for value arrays.
func (o *Options) ValueArrayPool() *value.BucketizedArrayPool { return o.valueArrayPool }

// SetKVArrayPool sets the pool for KV arrays.
func (o *Options) SetKVArrayPool(v *value.BucketizedKVArrayPool) *Options {
opts := *o
opts.kvArrayPool = v
return &opts
}

// KVArrayPool returns the pool for KV arrays.
func (o *Options) KVArrayPool() *value.BucketizedKVArrayPool { return o.kvArrayPool }

func (o *Options) initPools() {
o.valuePool.Init(func() *value.Value { return value.NewEmptyValue(o.valuePool) })
o.valueArrayPool.Init(func(capacity int) value.Array {
values := make([]*value.Value, 0, capacity)
return value.NewArray(values, o.valueArrayPool)
})
o.kvArrayPool.Init(func(capacity int) value.KVArray {
kvs := make([]value.KV, 0, capacity)
return value.NewKVArray(kvs, o.kvArrayPool)
})
}
118 changes: 88 additions & 30 deletions parser/json/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,11 @@ const (
trueString = "true"
falseString = "false"
nullString = "null"

defaultKVArraySize = 16
defaultValueArraySize = 16
)

var (
emptyObjectValue = value.NewObjectValue(value.EmptyObject, nil)
emptyArrayValue = value.NewArrayValue(value.EmptyArray, nil)
emptyObjectValue = value.NewObjectValue(value.Object{}, nil)
emptyArrayValue = value.NewArrayValue(value.Array{}, nil)
nullValue = value.NewNullValue(nil)
trueValue = value.NewBoolValue(true, nil)
falseValue = value.NewBoolValue(false, nil)
Expand All @@ -32,18 +29,78 @@ var (
// Parser parses JSON-encoded values.
// TODO(xichen): Handle parsing to maximum depth.
type Parser interface {
// Reset resets the parser.
Reset()

// Parse parses a JSON-encoded value and returns the parse result.
// The value returned remains valid till the next Parse or ParseBytes call.
Parse(str string) (*value.Value, error)

// ParseBytes parses a byte slice and returns the parse result.
// The value returned remains valid till the next Parse or ParseBytes call.
ParseBytes(b []byte) (*value.Value, error)
}

type cache struct {
values []value.Value
valueArrays []value.Array
kvArrays []value.KVArray
}

func (c *cache) reset() {
for i := 0; i < len(c.values); i++ {
c.values[i].Reset()
}
c.values = c.values[:0]

for i := 0; i < len(c.valueArrays); i++ {
c.valueArrays[i].Reset()
}
c.valueArrays = c.valueArrays[:0]

for i := 0; i < len(c.kvArrays); i++ {
c.kvArrays[i].Reset()
}
c.kvArrays = c.kvArrays[:0]
}

func (c *cache) getValue() *value.Value {
if cap(c.values) > len(c.values) {
c.values = c.values[:len(c.values)+1]
} else {
c.values = append(c.values, value.Value{})
}
v := &c.values[len(c.values)-1]
v.Reset()
return v
}

func (c *cache) getValueArray() *value.Array {
if cap(c.valueArrays) > len(c.valueArrays) {
c.valueArrays = c.valueArrays[:len(c.valueArrays)+1]
} else {
c.valueArrays = append(c.valueArrays, value.NewArray(nil, nil))
}
v := &c.valueArrays[len(c.valueArrays)-1]
v.Reset()
return v
}

func (c *cache) getKVArray() *value.KVArray {
if cap(c.kvArrays) > len(c.kvArrays) {
c.kvArrays = c.kvArrays[:len(c.kvArrays)+1]
} else {
c.kvArrays = append(c.kvArrays, value.NewKVArray(nil, nil))
}
v := &c.kvArrays[len(c.kvArrays)-1]
v.Reset()
return v
}

// NB: Parser is not thread-safe.
type parser struct {
maxDepth int
valuePool *value.Pool
valueArrayPool *value.BucketizedArrayPool
kvArrayPool *value.BucketizedKVArrayPool
maxDepth int
cache cache

str string
pos int
Expand All @@ -55,15 +112,18 @@ func NewParser(opts *Options) Parser {
opts = NewOptions()
}
return &parser{
maxDepth: opts.MaxDepth(),
valuePool: opts.ValuePool(),
valueArrayPool: opts.ValueArrayPool(),
kvArrayPool: opts.KVArrayPool(),
maxDepth: opts.MaxDepth(),
}
}

func (p *parser) Reset() {
p.cache.reset()
p.str = ""
p.pos = 0
}

func (p *parser) Parse(str string) (*value.Value, error) {
p.reset()
p.Reset()
p.str = str
v, err := p.parseValue()
if err != nil {
Expand All @@ -80,11 +140,6 @@ func (p *parser) ParseBytes(b []byte) (*value.Value, error) {
return p.Parse(unsafe.ToString(b))
}

func (p *parser) reset() {
p.str = ""
p.pos = 0
}

func (p *parser) parseValue() (*value.Value, error) {
p.skipWS()
if p.eos() {
Expand Down Expand Up @@ -151,7 +206,7 @@ func (p *parser) parseObject() (*value.Value, error) {
return emptyObjectValue, nil
}

var kvs value.KVArray
var kvs *value.KVArray
for {
p.skipWS()
if p.eos() || p.current() != '"' {
Expand Down Expand Up @@ -185,8 +240,8 @@ func (p *parser) parseObject() (*value.Value, error) {
return nil, newParseError("object separator", p.pos, errors.New("unexpected end of object"))
}

if kvs.Len() == 0 {
kvs = p.kvArrayPool.Get(defaultKVArraySize)
if kvs == nil {
kvs = p.cache.getKVArray()
}
kvs.Append(value.NewKV(k, v))

Expand All @@ -197,8 +252,8 @@ func (p *parser) parseObject() (*value.Value, error) {

if p.current() == '}' {
p.pos++
obj := value.NewObject(kvs)
v := value.NewObjectValue(obj, p.valuePool)
v := p.cache.getValue()
v.SetObject(value.NewObject(*kvs))
return v, nil
}

Expand All @@ -218,7 +273,7 @@ func (p *parser) parseArray() (*value.Value, error) {
return emptyArrayValue, nil
}

var values value.Array
var values *value.Array
for {
p.skipWS()
v, err := p.parseValue()
Expand All @@ -232,8 +287,8 @@ func (p *parser) parseArray() (*value.Value, error) {
return nil, newParseError("value", p.pos, errors.New("unexpected end of array"))
}

if values.Len() == 0 {
values = p.valueArrayPool.Get(defaultValueArraySize)
if values == nil {
values = p.cache.getValueArray()
}
values.Append(v)

Expand All @@ -244,7 +299,8 @@ func (p *parser) parseArray() (*value.Value, error) {

if p.current() == ']' {
p.pos++
v := value.NewArrayValue(values, p.valuePool)
v := p.cache.getValue()
v.SetArray(*values)
return v, nil
}

Expand All @@ -257,7 +313,8 @@ func (p *parser) parseString() (*value.Value, error) {
if err != nil {
return nil, err
}
v := value.NewStringValue(str, p.valuePool)
v := p.cache.getValue()
v.SetString(str)
return v, nil
}

Expand Down Expand Up @@ -354,7 +411,8 @@ outerLoop:
if err != nil {
return nil, newParseError("number", start, err)
}
v := value.NewNumberValue(n, p.valuePool)
v := p.cache.getValue()
v.SetNumber(n)
return v, nil
}

Expand Down
Loading

0 comments on commit ed94452

Please sign in to comment.