Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions EvaluableExpression.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package govaluate
import (
"errors"
"fmt"
"sync"
)

const isoDateFormat string = "2006-01-02T15:04:05.999999999Z0700"
Expand Down Expand Up @@ -137,6 +138,12 @@ func (this EvaluableExpression) Evaluate(parameters map[string]interface{}) (int
return this.Eval(MapParameters(parameters))
}

var sanitizedParamsPool = sync.Pool{
New: func() interface{} {
return &sanitizedParameters{}
},
}

/*
Runs the entire expression using the given [parameters].
e.g., If the expression contains a reference to the variable "foo", it will be taken from `parameters.Get("foo")`.
Expand All @@ -154,13 +161,21 @@ func (this EvaluableExpression) Eval(parameters Parameters) (interface{}, error)
return nil, nil
}

free := false
if parameters != nil {
parameters = &sanitizedParameters{parameters}
free = true
tmp := sanitizedParamsPool.Get().(*sanitizedParameters)
tmp.orig = parameters
parameters = tmp
} else {
parameters = DUMMY_PARAMETERS
}

return this.evaluateStage(this.evaluationStages, parameters)
ret, err := this.evaluateStage(this.evaluationStages, parameters)
if free {
sanitizedParamsPool.Put(parameters)
}
return ret, err
}

func (this EvaluableExpression) evaluateStage(stage *evaluationStage, parameters Parameters) (interface{}, error) {
Expand Down Expand Up @@ -274,3 +289,10 @@ func (this EvaluableExpression) Vars() []string {
}
return varlist
}

/*
Removes the tokens from the EvaluableExpression. This will cause the Tokens() and Vars() functions to no longer operate, but will save memory.
*/
func (this *EvaluableExpression) CleanupTokens() {
this.tokens = this.tokens[:0]
}
29 changes: 16 additions & 13 deletions benchmarks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
Serves as a "water test" to give an idea of the general overhead of parsing
*/
func BenchmarkSingleParse(bench *testing.B) {

bench.ReportAllocs()
for i := 0; i < bench.N; i++ {
_, _ = NewEvaluableExpression("1")
}
Expand All @@ -19,7 +19,7 @@ The most common use case, a single variable, modified slightly, compared to a co
This is the "expected" use case of govaluate.
*/
func BenchmarkSimpleParse(bench *testing.B) {

bench.ReportAllocs()
for i := 0; i < bench.N; i++ {
_, _ = NewEvaluableExpression("(requests_made * requests_succeeded / 100) >= 90")
}
Expand All @@ -29,6 +29,7 @@ func BenchmarkSimpleParse(bench *testing.B) {
Benchmarks all syntax possibilities in one expression.
*/
func BenchmarkFullParse(bench *testing.B) {
bench.ReportAllocs()
// represents all the major syntax possibilities.
expression := "2 > 1 &&" +
"'something' != 'nothing' || " +
Expand All @@ -45,7 +46,7 @@ func BenchmarkFullParse(bench *testing.B) {
Benchmarks the bare-minimum evaluation time
*/
func BenchmarkEvaluationSingle(bench *testing.B) {

bench.ReportAllocs()
expression, _ := NewEvaluableExpression("1")

bench.ResetTimer()
Expand All @@ -58,7 +59,7 @@ func BenchmarkEvaluationSingle(bench *testing.B) {
Benchmarks evaluation times of literals (no variables, no modifiers)
*/
func BenchmarkEvaluationNumericLiteral(bench *testing.B) {

bench.ReportAllocs()
expression, _ := NewEvaluableExpression("(2) > (1)")

bench.ResetTimer()
Expand All @@ -71,7 +72,7 @@ func BenchmarkEvaluationNumericLiteral(bench *testing.B) {
Benchmarks evaluation times of literals with modifiers
*/
func BenchmarkEvaluationLiteralModifiers(bench *testing.B) {

bench.ReportAllocs()
expression, _ := NewEvaluableExpression("(2) + (2) == (4)")

bench.ResetTimer()
Expand All @@ -81,7 +82,7 @@ func BenchmarkEvaluationLiteralModifiers(bench *testing.B) {
}

func BenchmarkEvaluationParameter(bench *testing.B) {

bench.ReportAllocs()
expression, _ := NewEvaluableExpression("requests_made")
parameters := map[string]interface{}{
"requests_made": 99.0,
Expand All @@ -97,7 +98,7 @@ func BenchmarkEvaluationParameter(bench *testing.B) {
Benchmarks evaluation times of parameters
*/
func BenchmarkEvaluationParameters(bench *testing.B) {

bench.ReportAllocs()
expression, _ := NewEvaluableExpression("requests_made > requests_succeeded")
parameters := map[string]interface{}{
"requests_made": 99.0,
Expand All @@ -114,7 +115,7 @@ func BenchmarkEvaluationParameters(bench *testing.B) {
Benchmarks evaluation times of parameters + literals with modifiers
*/
func BenchmarkEvaluationParametersModifiers(bench *testing.B) {

bench.ReportAllocs()
expression, _ := NewEvaluableExpression("(requests_made * requests_succeeded / 100) >= 90")
parameters := map[string]interface{}{
"requests_made": 99.0,
Expand All @@ -134,6 +135,7 @@ This is largely a canary benchmark to make sure that any syntax additions don't
unnecessarily bloat the evaluation time.
*/
func BenchmarkComplexExpression(bench *testing.B) {
bench.ReportAllocs()
expressionString := "2 > 1 &&" +
"'something' != 'nothing' || " +
"'2014-01-20' < 'Wed Jul 8 23:07:35 MDT 2015' && " +
Expand All @@ -160,6 +162,7 @@ and possible performance pitfalls. This test doesn't aim to be comprehensive aga
it is primarily concerned with tracking how much longer it takes to compile a regex at evaluation-time than during parse-time.
*/
func BenchmarkRegexExpression(bench *testing.B) {
bench.ReportAllocs()
expressionString := "(foo !~ bar) && (foobar =~ oba)"

expression, _ := NewEvaluableExpression(expressionString)
Expand All @@ -182,7 +185,7 @@ are actually being precompiled.
Also demonstrates that (generally) compiling a regex at evaluation-time takes an order of magnitude more time than pre-compiling.
*/
func BenchmarkConstantRegexExpression(bench *testing.B) {

bench.ReportAllocs()
expressionString := "(foo !~ '[bB]az') && (bar =~ '[bB]ar')"
expression, _ := NewEvaluableExpression(expressionString)

Expand All @@ -198,7 +201,7 @@ func BenchmarkConstantRegexExpression(bench *testing.B) {
}

func BenchmarkAccessors(bench *testing.B) {

bench.ReportAllocs()
expressionString := "foo.Int"
expression, _ := NewEvaluableExpression(expressionString)

Expand All @@ -209,7 +212,7 @@ func BenchmarkAccessors(bench *testing.B) {
}

func BenchmarkAccessorMethod(bench *testing.B) {

bench.ReportAllocs()
expressionString := "foo.Func()"
expression, _ := NewEvaluableExpression(expressionString)

Expand All @@ -220,7 +223,7 @@ func BenchmarkAccessorMethod(bench *testing.B) {
}

func BenchmarkAccessorMethodParams(bench *testing.B) {

bench.ReportAllocs()
expressionString := "foo.FuncArgStr('bonk')"
expression, _ := NewEvaluableExpression(expressionString)

Expand All @@ -231,7 +234,7 @@ func BenchmarkAccessorMethodParams(bench *testing.B) {
}

func BenchmarkNestedAccessors(bench *testing.B) {

bench.ReportAllocs()
expressionString := "foo.Nested.Funk"
expression, _ := NewEvaluableExpression(expressionString)

Expand Down
2 changes: 1 addition & 1 deletion evaluationStage.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ func regexStage(left interface{}, right interface{}, parameters Parameters) (int
pattern = right
}

return pattern.Match([]byte(left.(string))), nil
return pattern.MatchString(left.(string)), nil
}

func notRegexStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
Expand Down
38 changes: 25 additions & 13 deletions lexerStream.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,35 @@
package govaluate

import "sync"

type lexerStream struct {
source []rune
position int
length int
sourceString string
source []rune
position int
length int
}

func newLexerStream(source string) *lexerStream {

var ret *lexerStream
var runes []rune
var lexerStreamPool = sync.Pool{
New: func() interface{} {
return new(lexerStream)
},
}

func newLexerStream(source string) *lexerStream {
ret := lexerStreamPool.Get().(*lexerStream)
if ret.source == nil {
ret.source = make([]rune, 0, len(source))
}
for _, character := range source {
runes = append(runes, character)
ret.source = append(ret.source, character)
}

ret = new(lexerStream)
ret.source = runes
ret.length = len(runes)
ret.sourceString = source
ret.position = 0
ret.length = len(ret.source)
return ret
}

func (this *lexerStream) readCharacter() rune {

character := this.source[this.position]
this.position += 1
return character
Expand All @@ -35,3 +42,8 @@ func (this *lexerStream) rewind(amount int) {
func (this lexerStream) canRead() bool {
return this.position < this.length
}

func (this *lexerStream) close() {
this.source = this.source[:0]
lexerStreamPool.Put(this)
}
Loading
Loading