github.com/tonalfitness/govaluate/v3@v3.1.5/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 }