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 ( import (
"fmt" "fmt"
"strings" "strings"
"sync"
"time" "time"
"github.com/google/cel-go/cel" "github.com/google/cel-go/cel"
@ -66,6 +67,27 @@ type CompilationResult struct {
MaxCost uint64 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 // Compile compiles all the XValidations rules (without recursing into the schema) and returns a slice containing a
// CompilationResult for each ValidationRule, or an error. // CompilationResult for each ValidationRule, or an error.
// Each CompilationResult may contain: // Each CompilationResult may contain:
@ -82,13 +104,11 @@ func Compile(s *schema.Structural, isResourceRoot bool, perCallLimit uint64) ([]
var propDecls []*expr.Decl var propDecls []*expr.Decl
var root *celmodel.DeclType var root *celmodel.DeclType
var ok bool var ok bool
env, err := cel.NewEnv( baseEnv, err := getBaseEnv()
cel.HomogeneousAggregateLiterals(),
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
reg := celmodel.NewRegistry(env) reg := celmodel.NewRegistry(baseEnv)
scopedTypeName := generateUniqueSelfTypeName() scopedTypeName := generateUniqueSelfTypeName()
rt, err := celmodel.NewRuleTypes(scopedTypeName, s, isResourceRoot, reg) rt, err := celmodel.NewRuleTypes(scopedTypeName, s, isResourceRoot, reg)
if err != nil { if err != nil {
@ -97,7 +117,7 @@ func Compile(s *schema.Structural, isResourceRoot bool, perCallLimit uint64) ([]
if rt == nil { if rt == nil {
return nil, nil return nil, nil
} }
opts, err := rt.EnvOptions(env.TypeProvider()) opts, err := rt.EnvOptions(baseEnv.TypeProvider())
if err != nil { if err != nil {
return nil, err 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(ScopedVarName, root.ExprType()))
propDecls = append(propDecls, decls.NewVar(OldScopedVarName, root.ExprType())) propDecls = append(propDecls, decls.NewVar(OldScopedVarName, root.ExprType()))
opts = append(opts, cel.Declarations(propDecls...), cel.HomogeneousAggregateLiterals()) opts = append(opts, cel.Declarations(propDecls...))
opts = append(opts, library.ExtensionLibs...) env, err := baseEnv.Extend(opts...)
env, err = env.Extend(opts...)
if err != nil { if err != nil {
return nil, err 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)
}
}
}