Skip to content

Commit cf53913

Browse files
montag451antonmedv
andauthored
Improve the performance of runtime.Fetch for structures (#833)
* Improve the performance of runtime.Fetch for structures The improvement comes from the use of a cache to speed up the retrieval of struct fields. This commit also adds a new benchmark to measure the performance gain. goos: linux goarch: amd64 pkg: github.com/expr-lang/expr cpu: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz │ /tmp/old.txt │ /tmp/new.txt │ │ sec/op │ sec/op vs base │ _envStruct_noEnv-8 503.3n ± 4% 228.8n ± 3% -54.53% (p=0.000 n=10) │ /tmp/old.txt │ /tmp/new.txt │ │ B/op │ B/op vs base │ _envStruct_noEnv-8 48.00 ± 0% 32.00 ± 0% -33.33% (p=0.000 n=10) │ /tmp/old.txt │ /tmp/new.txt │ │ allocs/op │ allocs/op vs base │ _envStruct_noEnv-8 3.000 ± 0% 1.000 ± 0% -66.67% (p=0.000 n=10) * Store only the index in the cache instead of the whole StructField --------- Co-authored-by: Anton Medvedev <anton@medv.io>
1 parent e5ee6c2 commit cf53913

File tree

2 files changed

+48
-4
lines changed

2 files changed

+48
-4
lines changed

bench_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,30 @@ func Benchmark_envStruct(b *testing.B) {
266266
require.True(b, out.(bool))
267267
}
268268

269+
func Benchmark_envStruct_noEnv(b *testing.B) {
270+
type Price struct {
271+
Value int
272+
}
273+
type Env struct {
274+
Price Price
275+
}
276+
277+
program, err := expr.Compile(`Price.Value > 0`)
278+
require.NoError(b, err)
279+
280+
env := Env{Price: Price{Value: 1}}
281+
282+
var out any
283+
b.ResetTimer()
284+
for n := 0; n < b.N; n++ {
285+
out, err = vm.Run(program, env)
286+
}
287+
b.StopTimer()
288+
289+
require.NoError(b, err)
290+
require.True(b, out.(bool))
291+
}
292+
269293
func Benchmark_envMap(b *testing.B) {
270294
type Price struct {
271295
Value int

vm/runtime/runtime.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,18 @@ import (
66
"fmt"
77
"math"
88
"reflect"
9+
"sync"
910

1011
"github.com/expr-lang/expr/internal/deref"
1112
)
1213

14+
var fieldCache sync.Map
15+
16+
type fieldCacheKey struct {
17+
t reflect.Type
18+
f string
19+
}
20+
1321
func Fetch(from, i any) any {
1422
v := reflect.ValueOf(from)
1523
if v.Kind() == reflect.Invalid {
@@ -63,8 +71,16 @@ func Fetch(from, i any) any {
6371

6472
case reflect.Struct:
6573
fieldName := i.(string)
66-
value := v.FieldByNameFunc(func(name string) bool {
67-
field, _ := v.Type().FieldByName(name)
74+
t := v.Type()
75+
key := fieldCacheKey{
76+
t: t,
77+
f: fieldName,
78+
}
79+
if cv, ok := fieldCache.Load(key); ok {
80+
return v.FieldByIndex(cv.([]int)).Interface()
81+
}
82+
field, ok := t.FieldByNameFunc(func(name string) bool {
83+
field, _ := t.FieldByName(name)
6884
switch field.Tag.Get("expr") {
6985
case "-":
7086
return false
@@ -74,8 +90,12 @@ func Fetch(from, i any) any {
7490
return name == fieldName
7591
}
7692
})
77-
if value.IsValid() {
78-
return value.Interface()
93+
if ok {
94+
value := v.FieldByIndex(field.Index)
95+
if value.IsValid() {
96+
fieldCache.Store(key, field.Index)
97+
return value.Interface()
98+
}
7999
}
80100
}
81101
panic(fmt.Sprintf("cannot fetch %v from %T", i, from))

0 commit comments

Comments
 (0)