Skip to content

Instantly share code, notes, and snippets.

@rhnvrm
Last active June 11, 2024 09:08
Show Gist options
  • Save rhnvrm/db4567fcd87b2cb8e997999e1366d406 to your computer and use it in GitHub Desktop.
Save rhnvrm/db4567fcd87b2cb8e997999e1366d406 to your computer and use it in GitHub Desktop.
Benchmark Results of govaluate vs cel-go
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)
}
}
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
@rhnvrm
Copy link
Author

rhnvrm commented Jul 24, 2023

@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.

@RegisMelgaco
Copy link

Thx!

@antonmedv
Copy link

antonmedv commented Jun 11, 2024

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