github.com/tonalfitness/govaluate@v3.0.2+incompatible/benchmarks_test.go (about)

     1  package govaluate
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/stretchr/testify/assert"
     7  )
     8  
     9  /*
    10    Serves as a "water test" to give an idea of the general overhead of parsing
    11  */
    12  func BenchmarkSingleParse(bench *testing.B) {
    13  
    14  	for i := 0; i < bench.N; i++ {
    15  		NewEvaluableExpression("1")
    16  	}
    17  }
    18  
    19  /*
    20    The most common use case, a single variable, modified slightly, compared to a constant.
    21    This is the "expected" use case of govaluate.
    22  */
    23  func BenchmarkSimpleParse(bench *testing.B) {
    24  
    25  	for i := 0; i < bench.N; i++ {
    26  		NewEvaluableExpression("(requests_made * requests_succeeded / 100) >= 90")
    27  	}
    28  }
    29  
    30  /*
    31    Benchmarks all syntax possibilities in one expression.
    32  */
    33  func BenchmarkFullParse(bench *testing.B) {
    34  
    35  	var expression string
    36  
    37  	// represents all the major syntax possibilities.
    38  	expression = "2 > 1 &&" +
    39  		"'something' != 'nothing' || " +
    40  		"'2014-01-20' < 'Wed Jul  8 23:07:35 MDT 2015' && " +
    41  		"[escapedVariable name with spaces] <= unescaped\\-variableName &&" +
    42  		"modifierTest + 1000 / 2 > (80 * 100 % 2)"
    43  
    44  	for i := 0; i < bench.N; i++ {
    45  		NewEvaluableExpression(expression)
    46  	}
    47  }
    48  
    49  /*
    50    Benchmarks the bare-minimum evaluation time
    51  */
    52  func BenchmarkEvaluationSingle(bench *testing.B) {
    53  
    54  	expression, _ := NewEvaluableExpression("1")
    55  
    56  	bench.ResetTimer()
    57  	for i := 0; i < bench.N; i++ {
    58  		expression.Evaluate(nil)
    59  	}
    60  }
    61  
    62  /*
    63    Benchmarks evaluation times of literals (no variables, no modifiers)
    64  */
    65  func BenchmarkEvaluationNumericLiteral(bench *testing.B) {
    66  
    67  	expression, _ := NewEvaluableExpression("(2) > (1)")
    68  
    69  	bench.ResetTimer()
    70  	for i := 0; i < bench.N; i++ {
    71  		expression.Evaluate(nil)
    72  	}
    73  }
    74  
    75  /*
    76    Benchmarks evaluation times of literals with modifiers
    77  */
    78  func BenchmarkEvaluationLiteralModifiers(bench *testing.B) {
    79  
    80  	expression, _ := NewEvaluableExpression("(2) + (2) == (4)")
    81  
    82  	bench.ResetTimer()
    83  	for i := 0; i < bench.N; i++ {
    84  		expression.Evaluate(nil)
    85  	}
    86  }
    87  
    88  func BenchmarkEvaluationParameter(bench *testing.B) {
    89  
    90  	expression, _ := NewEvaluableExpression("requests_made")
    91  	parameters := map[string]interface{}{
    92  		"requests_made": 99.0,
    93  	}
    94  
    95  	bench.ResetTimer()
    96  	for i := 0; i < bench.N; i++ {
    97  		expression.Evaluate(parameters)
    98  	}
    99  }
   100  
   101  /*
   102    Benchmarks evaluation times of parameters
   103  */
   104  func BenchmarkEvaluationParameters(bench *testing.B) {
   105  
   106  	expression, _ := NewEvaluableExpression("requests_made > requests_succeeded")
   107  	parameters := map[string]interface{}{
   108  		"requests_made":      99.0,
   109  		"requests_succeeded": 90.0,
   110  	}
   111  
   112  	bench.ResetTimer()
   113  	for i := 0; i < bench.N; i++ {
   114  		expression.Evaluate(parameters)
   115  	}
   116  }
   117  
   118  /*
   119    Benchmarks evaluation times of parameters + literals with modifiers
   120  */
   121  func BenchmarkEvaluationParametersModifiers(bench *testing.B) {
   122  
   123  	expression, _ := NewEvaluableExpression("(requests_made * requests_succeeded / 100) >= 90")
   124  	parameters := map[string]interface{}{
   125  		"requests_made":      99.0,
   126  		"requests_succeeded": 90.0,
   127  	}
   128  
   129  	bench.ResetTimer()
   130  	for i := 0; i < bench.N; i++ {
   131  		expression.Evaluate(parameters)
   132  	}
   133  }
   134  
   135  /*
   136    Benchmarks the ludicrously-unlikely worst-case expression,
   137    one which uses all features.
   138    This is largely a canary benchmark to make sure that any syntax additions don't
   139    unnecessarily bloat the evaluation time.
   140  */
   141  func BenchmarkComplexExpression(bench *testing.B) {
   142  
   143  	var expressionString string
   144  
   145  	expressionString = "2 > 1 &&" +
   146  		"'something' != 'nothing' || " +
   147  		"'2014-01-20' < 'Wed Jul  8 23:07:35 MDT 2015' && " +
   148  		"[escapedVariable name with spaces] <= unescaped\\-variableName &&" +
   149  		"modifierTest + 1000 / 2 > (80 * 100 % 2)"
   150  
   151  	expression, _ := NewEvaluableExpression(expressionString)
   152  	parameters := map[string]interface{}{
   153  		"escapedVariable name with spaces": 99.0,
   154  		"unescaped\\-variableName":         90.0,
   155  		"modifierTest":                     5.0,
   156  	}
   157  
   158  	bench.ResetTimer()
   159  	for i := 0; i < bench.N; i++ {
   160  		expression.Evaluate(parameters)
   161  	}
   162  }
   163  
   164  /*
   165    Benchmarks uncompiled parameter regex operators, which are the most expensive of the lot.
   166    Note that regex compilation times are unpredictable and wily things. The regex engine has a lot of edge cases
   167    and possible performance pitfalls. This test doesn't aim to be comprehensive against all possible regex scenarios,
   168    it is primarily concerned with tracking how much longer it takes to compile a regex at evaluation-time than during parse-time.
   169  */
   170  func BenchmarkRegexExpression(bench *testing.B) {
   171  
   172  	var expressionString string
   173  
   174  	expressionString = "(foo !~ bar) && (foobar =~ oba)"
   175  
   176  	expression, _ := NewEvaluableExpression(expressionString)
   177  	parameters := map[string]interface{}{
   178  		"foo": "foo",
   179  		"bar": "bar",
   180  		"baz": "baz",
   181  		"oba": ".*oba.*",
   182  	}
   183  
   184  	bench.ResetTimer()
   185  	for i := 0; i < bench.N; i++ {
   186  		expression.Evaluate(parameters)
   187  	}
   188  }
   189  
   190  /*
   191  	Benchmarks pre-compilable regex patterns. Meant to serve as a sanity check that constant strings used as regex patterns
   192  	are actually being precompiled.
   193  	Also demonstrates that (generally) compiling a regex at evaluation-time takes an order of magnitude more time than pre-compiling.
   194  */
   195  func BenchmarkConstantRegexExpression(bench *testing.B) {
   196  
   197  	expressionString := "(foo !~ '[bB]az') && (bar =~ '[bB]ar')"
   198  	expression, _ := NewEvaluableExpression(expressionString)
   199  
   200  	parameters := map[string]interface{}{
   201  		"foo": "foo",
   202  		"bar": "bar",
   203  	}
   204  
   205  	bench.ResetTimer()
   206  	for i := 0; i < bench.N; i++ {
   207  		expression.Evaluate(parameters)
   208  	}
   209  }
   210  
   211  func BenchmarkAccessors(bench *testing.B) {
   212  
   213  	expressionString := "foo.Int"
   214  	expression, _ := NewEvaluableExpression(expressionString)
   215  
   216  	bench.ResetTimer()
   217  	for i := 0; i < bench.N; i++ {
   218  		expression.Evaluate(fooFailureParameters)
   219  	}
   220  }
   221  
   222  func BenchmarkAccessorMethod(bench *testing.B) {
   223  
   224  	expressionString := "foo.Func()"
   225  	expression, _ := NewEvaluableExpression(expressionString)
   226  
   227  	bench.ResetTimer()
   228  	for i := 0; i < bench.N; i++ {
   229  		expression.Evaluate(fooFailureParameters)
   230  	}
   231  }
   232  
   233  func BenchmarkAccessorMethodParams(bench *testing.B) {
   234  
   235  	expressionString := "foo.FuncArgStr('bonk')"
   236  	expression, _ := NewEvaluableExpression(expressionString)
   237  
   238  	bench.ResetTimer()
   239  	for i := 0; i < bench.N; i++ {
   240  		expression.Evaluate(fooFailureParameters)
   241  	}
   242  }
   243  
   244  func BenchmarkNestedAccessors(bench *testing.B) {
   245  
   246  	expressionString := "foo.Nested.Funk"
   247  	expression, _ := NewEvaluableExpression(expressionString)
   248  
   249  	bench.ResetTimer()
   250  	for i := 0; i < bench.N; i++ {
   251  		expression.Evaluate(fooFailureParameters)
   252  	}
   253  }
   254  
   255  func BenchmarkTokenizer(t *testing.B) {
   256  	for i := 0; i < t.N; i++ {
   257  		tokens, err := Tokenize("x + y**2 - 2/(1 + z**2)")
   258  		if err != nil || len(tokens) != 15 {
   259  			assert.Equal(t, 15, len(tokens))
   260  			assert.Nil(t, err)
   261  			t.FailNow()
   262  		}
   263  	}
   264  }
   265  
   266  func BenchmarkTokenizerOld(t *testing.B) {
   267  	for i := 0; i < t.N; i++ {
   268  		tokens, err := parseTokens("x + y**2 - 2/(1 + z**2)", map[string]ExpressionFunction{})
   269  		if err != nil || len(tokens) != 15 {
   270  			assert.Equal(t, 15, len(tokens))
   271  			assert.Nil(t, err)
   272  			t.FailNow()
   273  		}
   274  	}
   275  }
   276  
   277  func BenchmarkParseSimple(t *testing.B) {
   278  	benchmarkParse(t, "a + 1")
   279  }
   280  
   281  func BenchmarkParseSimpleOld(t *testing.B) {
   282  	benchmarkParseOld(t, "a + 1")
   283  }
   284  
   285  func BenchmarkParseMedium(t *testing.B) {
   286  	benchmarkParse(t, "foo ? (bar > 0.15 && bar < 0.5) : (baz < -0.15 && baz > -0.5)")
   287  }
   288  
   289  func BenchmarkParseMediumOld(t *testing.B) {
   290  	benchmarkParseOld(t, "foo ? (bar > 0.15 && bar < 0.5) : (baz < -0.15 && baz > -0.5)")
   291  }
   292  
   293  func BenchmarkParseComplex(t *testing.B) {
   294  	benchmarkParse(t, "(0 <= x && x < max && ((1 + y) / 2) ** 2 == 0.25 ||"+
   295  		" ((-a + -b) * -(c / d)) >> 2) && (a != 0 ? (1 + 2) * ((10 - 1) / 3) : ~1)")
   296  }
   297  
   298  func BenchmarkParseComplexOld(t *testing.B) {
   299  	benchmarkParseOld(t, "(0 <= x && x < max && ((1 + y) / 2) ** 2 == 0.25 ||"+
   300  		" ((-a + -b) * -(c / d)) >> 2) && (a != 0 ? (1 + 2) * ((10 - 1) / 3) : ~1)")
   301  }
   302  
   303  func benchmarkParse(t *testing.B, input string) {
   304  	t.ResetTimer()
   305  	for i := 0; i < t.N; i++ {
   306  		_, err := Parse(input)
   307  		if err != nil {
   308  			assert.Nil(t, err)
   309  			t.FailNow()
   310  		}
   311  	}
   312  }
   313  
   314  func benchmarkParseOld(t *testing.B, input string) {
   315  	t.ResetTimer()
   316  	for i := 0; i < t.N; i++ {
   317  		_, err := NewEvaluableExpression(input)
   318  		if err != nil {
   319  			assert.Nil(t, err)
   320  			t.FailNow()
   321  		}
   322  	}
   323  }
   324  
   325  func BenchmarkEvalSimple(t *testing.B) {
   326  	expr, err := Parse("a + 1")
   327  	assert.Nil(t, err)
   328  	t.ResetTimer()
   329  	for i := 0; i < t.N; i++ {
   330  		result, err := expr.Eval(NewEvalParams(map[string]interface{}{"a": 8.0}))
   331  		if err != nil || result != 9.0 {
   332  			assert.Nil(t, err)
   333  			assert.Equal(t, 9.0, result)
   334  			t.FailNow()
   335  		}
   336  	}
   337  }
   338  
   339  func BenchmarkEvalSimpleOld(t *testing.B) {
   340  	expr, err := NewEvaluableExpression("a + 1")
   341  	assert.Nil(t, err)
   342  	t.ResetTimer()
   343  	for i := 0; i < t.N; i++ {
   344  		result, err := expr.Evaluate(map[string]interface{}{"a": 8.0})
   345  		if err != nil || result != 9.0 {
   346  			assert.Nil(t, err)
   347  			assert.Equal(t, 9.0, result)
   348  			t.FailNow()
   349  		}
   350  	}
   351  }
   352  
   353  func BenchmarkEvalMedium(t *testing.B) {
   354  	expr, err := Parse("x ? (y > 0.15 && y < 0.5) : (y < -0.15 && y > -0.5)")
   355  	assert.Nil(t, err)
   356  	t.ResetTimer()
   357  	for i := 0; i < t.N; i++ {
   358  		result, err := expr.Eval(NewEvalParams(map[string]interface{}{"x": false, "y": -0.4}))
   359  		if err != nil || result != true {
   360  			assert.Nil(t, err)
   361  			assert.Equal(t, true, result)
   362  			t.FailNow()
   363  		}
   364  	}
   365  }
   366  
   367  func BenchmarkEvalMediumOld(t *testing.B) {
   368  	expr, err := NewEvaluableExpression("x ? (y > 0.15 && y < 0.5) : (y < -0.15 && y > -0.5)")
   369  	assert.Nil(t, err)
   370  	t.ResetTimer()
   371  	for i := 0; i < t.N; i++ {
   372  		result, err := expr.Evaluate(map[string]interface{}{"x": false, "y": -0.4})
   373  		if err != nil || result != true {
   374  			assert.Nil(t, err)
   375  			assert.Equal(t, true, result)
   376  			t.FailNow()
   377  		}
   378  	}
   379  }
   380  
   381  func BenchmarkEvalComplex(t *testing.B) {
   382  	expr, err := Parse("(0 <= x && x < max && ((1 + y) / 2) ** 2 == 0.25 ||" +
   383  		" ((-a + -b) * -(c / d)) >> 2 != 0) && (a != 0 ? (1 + 2) * ((10 - 1) / 3) : ~1) == 9")
   384  	assert.Nil(t, err)
   385  	t.ResetTimer()
   386  	for i := 0; i < t.N; i++ {
   387  		result, err := expr.Eval(NewEvalParams(map[string]interface{}{"x": 1.0, "max": 10.0, "y": 2.0, "a": 5.0, "b": 7.0, "c": 9.0, "d": 3.0}))
   388  		if err != nil || result != true {
   389  			assert.Nil(t, err)
   390  			assert.Equal(t, true, result)
   391  			t.FailNow()
   392  		}
   393  	}
   394  }
   395  
   396  func BenchmarkEvalComplexOld(t *testing.B) {
   397  	expr, err := NewEvaluableExpression("(0 <= x && x < max && ((1 + y) / 2) ** 2 == 0.25 ||" +
   398  		" ((-a + -b) * -(c / d)) >> 2 != 0) && (a != 0 ? (1 + 2) * ((10 - 1) / 3) : ~1) == 9")
   399  	assert.Nil(t, err)
   400  	t.ResetTimer()
   401  	for i := 0; i < t.N; i++ {
   402  		result, err := expr.Evaluate(map[string]interface{}{"x": 1.0, "max": 10.0, "y": 2.0, "a": 5.0, "b": 7.0, "c": 9.0, "d": 3.0})
   403  		if err != nil || result != true {
   404  			assert.Nil(t, err)
   405  			assert.Equal(t, true, result)
   406  			t.FailNow()
   407  		}
   408  	}
   409  }