Last active
June 11, 2024 09:08
-
-
Save rhnvrm/db4567fcd87b2cb8e997999e1366d406 to your computer and use it in GitHub Desktop.
Benchmark Results of govaluate vs cel-go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"testing" | |
"github.com/Knetic/govaluate" | |
"github.com/google/cel-go/cel" | |
"github.com/google/cel-go/checker/decls" | |
"github.com/google/cel-go/common/types" | |
"github.com/google/cel-go/common/types/ref" | |
"github.com/google/cel-go/interpreter/functions" | |
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1" | |
) | |
func BenchmarkCelGo(b *testing.B) { | |
expr := "((TestDouble >= 1.0 || TestString.TestFunction() == 'HelloWorld') && (TestDouble + 1.0 >= 0.0)) || Now > TestTime" | |
decls := cel.Declarations( | |
// Identifiers used within this expression. | |
decls.NewIdent("TestDouble", decls.Double, nil), | |
decls.NewIdent("TestString", decls.String, nil), | |
decls.NewIdent("TestTime", decls.Uint, nil), | |
decls.NewIdent("Now", decls.Uint, nil), | |
decls.NewFunction("TestFunction", decls.NewInstanceOverload("TestFunction", | |
[]*exprpb.Type{decls.String}, | |
decls.String)), | |
) | |
e, err := cel.NewEnv(decls) | |
if err != nil { | |
b.Fatalf("environment creation error: %s\n", err) | |
} | |
// Parse and check the expression. | |
p, iss := e.Parse(expr) | |
if iss != nil && iss.Err() != nil { | |
b.Fatal(iss.Err()) | |
} | |
c, iss := e.Check(p) | |
if iss != nil && iss.Err() != nil { | |
b.Fatal(iss.Err()) | |
} | |
// Create the program. | |
funcs := cel.Functions( | |
&functions.Overload{ | |
Operator: "TestFunction", | |
Unary: func(value ref.Val) ref.Val { | |
ts, ok := value.(types.String) | |
if !ok { | |
return types.NewErr( | |
"invalid key of type '%v' and value '%v' to obj.get(key, def)", | |
value.Type(), ts) | |
} | |
return types.String("Hello" + ts) | |
}, | |
}) | |
prg, err := e.Program(c, funcs) | |
if err != nil { | |
b.Fatalf("program creation error: %s\n", err) | |
} | |
parameters := map[string]interface{}{ | |
"TestDouble": 0.0, | |
"TestString": "World", | |
"TestTime": 0, | |
"Now": 1, | |
} | |
for n := 0; n < b.N; n++ { | |
prg.Eval(parameters) | |
} | |
} | |
func BenchmarkGovaluate(b *testing.B) { | |
expr := "((TestDouble >= 1.0 || TestFunction(TestString) == 'HelloWorld') && (TestDouble + 1.0 >= 0.0)) || Now > TestTime" | |
functions := map[string]govaluate.ExpressionFunction{ | |
"TestFunction": func(args ...interface{}) (interface{}, error) { | |
ts, ok := args[0].(string) | |
if !ok { | |
return nil, fmt.Errorf("error: %v", ts) | |
} | |
return "Hello" + ts, nil | |
}, | |
} | |
expression, _ := govaluate.NewEvaluableExpressionWithFunctions(expr, functions) | |
parameters := map[string]interface{}{ | |
"TestDouble": 0.0, | |
"TestString": "World", | |
"TestTime": 0, | |
"Now": 1, | |
} | |
for n := 0; n < b.N; n++ { | |
expression.Evaluate(parameters) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
go test -cpuprofile cpu.prof -memprofile mem.prof -benchmem -bench . | |
goos: linux | |
goarch: amd64 | |
pkg: benchcelgo | |
BenchmarkCelGo-4 2000000 656 ns/op 88 B/op 5 allocs/op | |
BenchmarkGovaluate-4 2000000 702 ns/op 72 B/op 5 allocs/op | |
PASS | |
ok benchcelgo 4.321s |
@RegisMelgaco We ended up using go-plugins instead for our usecase. You can read more here:
https://zerodha.tech/blog/a-lesson-in-niche-business-dsls-at-scale/
Although, I think there is also a place for expression engines like Govaluate and Cel-Go but it's best to evaluate on a case-by-case basis based on the requirements of your project.
Thx!
Another benchmark for different lang including cel-go and https://expr-lang.org:
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
What do you think about running more tests with different payload sizes so we can get an idea of how it scales? (Sorry for the pt-Br haha)