diff --git a/bench_test.go b/bench_test.go index c9f2b7182..d70a09356 100644 --- a/bench_test.go +++ b/bench_test.go @@ -266,6 +266,30 @@ func Benchmark_envStruct(b *testing.B) { require.True(b, out.(bool)) } +func Benchmark_envStruct_noEnv(b *testing.B) { + type Price struct { + Value int + } + type Env struct { + Price Price + } + + program, err := expr.Compile(`Price.Value > 0`) + require.NoError(b, err) + + env := Env{Price: Price{Value: 1}} + + var out any + b.ResetTimer() + for n := 0; n < b.N; n++ { + out, err = vm.Run(program, env) + } + b.StopTimer() + + require.NoError(b, err) + require.True(b, out.(bool)) +} + func Benchmark_envMap(b *testing.B) { type Price struct { Value int diff --git a/vm/runtime/runtime.go b/vm/runtime/runtime.go index 56759c531..4780c71e5 100644 --- a/vm/runtime/runtime.go +++ b/vm/runtime/runtime.go @@ -6,10 +6,18 @@ import ( "fmt" "math" "reflect" + "sync" "github.com/expr-lang/expr/internal/deref" ) +var fieldCache sync.Map + +type fieldCacheKey struct { + t reflect.Type + f string +} + func Fetch(from, i any) any { v := reflect.ValueOf(from) if v.Kind() == reflect.Invalid { @@ -63,8 +71,17 @@ func Fetch(from, i any) any { case reflect.Struct: fieldName := i.(string) - value := v.FieldByNameFunc(func(name string) bool { - field, _ := v.Type().FieldByName(name) + t := v.Type() + key := fieldCacheKey{ + t: t, + f: fieldName, + } + if fi, ok := fieldCache.Load(key); ok { + field := fi.(*reflect.StructField) + return v.FieldByIndex(field.Index).Interface() + } + field, ok := t.FieldByNameFunc(func(name string) bool { + field, _ := t.FieldByName(name) switch field.Tag.Get("expr") { case "-": return false @@ -74,8 +91,12 @@ func Fetch(from, i any) any { return name == fieldName } }) - if value.IsValid() { - return value.Interface() + if ok { + value := v.FieldByIndex(field.Index) + if value.IsValid() { + fieldCache.Store(key, &field) + return value.Interface() + } } } panic(fmt.Sprintf("cannot fetch %v from %T", i, from))