Initialize a base CEL env and share it to avoid repeated function declaration validation

This commit is contained in:
Joe Betz 2022-03-23 23:46:01 -04:00
parent 4c90653d19
commit 6c6d76c69e
2 changed files with 43 additions and 8 deletions

View File

@ -19,6 +19,7 @@ package cel
import (
"fmt"
"strings"
"sync"
"time"
"github.com/google/cel-go/cel"
@ -66,6 +67,27 @@ type CompilationResult struct {
MaxCost uint64
}
var (
initEnvOnce sync.Once
initEnv *cel.Env
initEnvErr error
)
func getBaseEnv() (*cel.Env, error) {
initEnvOnce.Do(func() {
var opts []cel.EnvOption
opts = append(opts, cel.HomogeneousAggregateLiterals())
// Validate function declarations once during base env initialization,
// so they don't need to be evaluated each time a CEL rule is compiled.
// This is a relatively expensive operation.
opts = append(opts, cel.EagerlyValidateDeclarations(true))
opts = append(opts, library.ExtensionLibs...)
initEnv, initEnvErr = cel.NewEnv(opts...)
})
return initEnv, initEnvErr
}
// Compile compiles all the XValidations rules (without recursing into the schema) and returns a slice containing a
// CompilationResult for each ValidationRule, or an error.
// Each CompilationResult may contain:
@ -82,13 +104,11 @@ func Compile(s *schema.Structural, isResourceRoot bool, perCallLimit uint64) ([]
var propDecls []*expr.Decl
var root *celmodel.DeclType
var ok bool
env, err := cel.NewEnv(
cel.HomogeneousAggregateLiterals(),
)
baseEnv, err := getBaseEnv()
if err != nil {
return nil, err
}
reg := celmodel.NewRegistry(env)
reg := celmodel.NewRegistry(baseEnv)
scopedTypeName := generateUniqueSelfTypeName()
rt, err := celmodel.NewRuleTypes(scopedTypeName, s, isResourceRoot, reg)
if err != nil {
@ -97,7 +117,7 @@ func Compile(s *schema.Structural, isResourceRoot bool, perCallLimit uint64) ([]
if rt == nil {
return nil, nil
}
opts, err := rt.EnvOptions(env.TypeProvider())
opts, err := rt.EnvOptions(baseEnv.TypeProvider())
if err != nil {
return nil, err
}
@ -111,9 +131,8 @@ func Compile(s *schema.Structural, isResourceRoot bool, perCallLimit uint64) ([]
}
propDecls = append(propDecls, decls.NewVar(ScopedVarName, root.ExprType()))
propDecls = append(propDecls, decls.NewVar(OldScopedVarName, root.ExprType()))
opts = append(opts, cel.Declarations(propDecls...), cel.HomogeneousAggregateLiterals())
opts = append(opts, library.ExtensionLibs...)
env, err = env.Extend(opts...)
opts = append(opts, cel.Declarations(propDecls...))
env, err := baseEnv.Extend(opts...)
if err != nil {
return nil, err
}

View File

@ -1605,3 +1605,19 @@ func TestCostEstimation(t *testing.T) {
})
}
}
func BenchmarkCompile(b *testing.B) {
_, err := getBaseEnv() // prime the baseEnv
if err != nil {
b.Fatal(err)
}
s := genArrayWithRule("number", "true")(nil)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := Compile(s, false, uint64(math.MaxInt64))
if err != nil {
b.Fatal(err)
}
}
}