Skip to content

Commit 2edcd98

Browse files
committed
Minor performance improvements in reflector funcWrapper
1 parent 88403aa commit 2edcd98

File tree

6 files changed

+184
-43
lines changed

6 files changed

+184
-43
lines changed

.github/workflows/code_change.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,7 @@ jobs:
1717
uses: actions/checkout@v2
1818

1919
- name: Fetch dependencies, verify build and test
20-
run: make build test benchmark
20+
run: make build test
21+
22+
- name: Run Benchmarks
23+
run: make benchmark

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [0.1.0] - 2020-10-14
8+
## v0.1.1 - 2020-10-14
9+
10+
### Added
11+
12+
- Minor performance improvements in reflector funcWrapper.
13+
14+
## v0.1.0 - 2020-10-14
915

1016
### Added
1117

expr_test.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"testing"
88
)
99

10-
var unknownErr = errors.New("failed")
10+
var errUnknown = errors.New("failed")
1111

1212
func TestConstExpr_Eval(t *testing.T) {
1313
t.Parallel()
@@ -56,10 +56,10 @@ func TestDoExpr_Eval(t *testing.T) {
5656
title: "ExprEvalFail",
5757
expr: func() (Expr, *Env) {
5858
return DoExpr{Exprs: []Expr{
59-
fakeExpr{Err: unknownErr},
59+
fakeExpr{Err: errUnknown},
6060
}}, nil
6161
},
62-
wantErr: unknownErr,
62+
wantErr: errUnknown,
6363
},
6464
{
6565
title: "MultipleExpr",
@@ -102,11 +102,11 @@ func TestDefExpr_Eval(t *testing.T) {
102102
e := New()
103103
return DefExpr{
104104
Name: "foo",
105-
Value: fakeExpr{Err: unknownErr},
105+
Value: fakeExpr{Err: errUnknown},
106106
Env: e,
107107
}, e
108108
},
109-
wantErr: unknownErr,
109+
wantErr: errUnknown,
110110
},
111111
{
112112
title: "ExprValue",
@@ -170,10 +170,10 @@ func TestIfExpr_Eval(t *testing.T) {
170170
title: "TestEvalErr",
171171
expr: func() (Expr, *Env) {
172172
return IfExpr{
173-
Test: fakeExpr{Err: unknownErr},
173+
Test: fakeExpr{Err: errUnknown},
174174
}, New()
175175
},
176-
wantErr: unknownErr,
176+
wantErr: errUnknown,
177177
},
178178
{
179179
title: "Else",
@@ -195,10 +195,10 @@ func TestInvokeExpr_Eval(t *testing.T) {
195195
title: "TargetEvalErr",
196196
expr: func() (Expr, *Env) {
197197
return &InvokeExpr{
198-
Target: fakeExpr{Err: unknownErr},
198+
Target: fakeExpr{Err: errUnknown},
199199
}, New()
200200
},
201-
wantErr: unknownErr,
201+
wantErr: errUnknown,
202202
},
203203
{
204204
title: "NonInvokable",
@@ -238,11 +238,11 @@ func TestInvokeExpr_Eval(t *testing.T) {
238238
return &InvokeExpr{
239239
Target: ConstExpr{Const: fakeInvokable(nil)},
240240
Args: []Expr{
241-
fakeExpr{Err: unknownErr},
241+
fakeExpr{Err: errUnknown},
242242
},
243243
}, New()
244244
},
245-
wantErr: unknownErr,
245+
wantErr: errUnknown,
246246
},
247247
})
248248
}

reflector/func.go

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,37 @@ type funcWrapper struct {
5656
}
5757

5858
func (fw *funcWrapper) Invoke(env *slurp.Env, args ...slurp.Any) (slurp.Any, error) {
59-
argVals := reflectValues(args)
59+
argCount := len(args)
6060
if fw.passScope {
61-
argVals = append([]reflect.Value{reflect.ValueOf(env)}, argVals...)
61+
// we need to pass 'env' also to the underlying function.
62+
// so apart from explicitly passed arguments, one extra
63+
// is needed.
64+
argCount++
6265
}
6366

67+
// allocate argument slice, including the space for 'env' argument
68+
// if the function needs it.
69+
argVals := make([]reflect.Value, argCount, argCount)
70+
if fw.passScope {
71+
argVals[0] = reflect.ValueOf(env)
72+
}
73+
74+
// populate reflect.Value version of each argument.
75+
for i, arg := range args {
76+
if fw.passScope {
77+
// 0th index is reserved for passing env. so offset index
78+
// by 1.
79+
i++
80+
}
81+
argVals[i] = reflect.ValueOf(arg)
82+
}
83+
84+
// verify number of args match the required function parameters.
6485
if err := fw.checkArgCount(len(argVals)); err != nil {
6586
return nil, err
6687
}
6788

68-
argVals, err := fw.convertTypes(argVals...)
69-
if err != nil {
89+
if err := fw.convertTypes(argVals...); err != nil {
7090
return nil, err
7191
}
7292

@@ -106,27 +126,28 @@ func (fw *funcWrapper) argNames() []string {
106126
return argNames
107127
}
108128

109-
func (fw *funcWrapper) convertTypes(args ...reflect.Value) ([]reflect.Value, error) {
110-
var vals []reflect.Value
129+
func (fw *funcWrapper) convertTypes(args ...reflect.Value) error {
130+
lastArgIdx := fw.rt.NumIn() - 1
131+
isVariadic := fw.rt.IsVariadic()
111132

112133
for i := 0; i < fw.rt.NumIn(); i++ {
113-
if fw.rt.IsVariadic() && i == fw.rt.NumIn()-1 {
134+
if i == lastArgIdx && isVariadic {
114135
c, err := convertArgsTo(fw.rt.In(i).Elem(), args[i:]...)
115136
if err != nil {
116-
return nil, err
137+
return err
117138
}
118-
vals = append(vals, c...)
139+
copy(args[i:], c)
119140
break
120141
}
121142

122143
c, err := convertArgsTo(fw.rt.In(i), args[i])
123144
if err != nil {
124-
return nil, err
145+
return err
125146
}
126-
vals = append(vals, c...)
147+
args[i] = c[0]
127148
}
128149

129-
return vals, nil
150+
return nil
130151
}
131152

132153
func (fw *funcWrapper) checkArgCount(count int) error {
@@ -165,26 +186,31 @@ func (fw *funcWrapper) wrapReturns(vals ...reflect.Value) (slurp.Any, error) {
165186
}
166187
}
167188

168-
wrapped := slurpValues(vals[0 : fw.lastOutIdx+1])
169-
if len(wrapped) == 1 {
189+
retValCount := len(vals[0 : fw.lastOutIdx+1])
190+
wrapped := make([]slurp.Any, retValCount, retValCount)
191+
for i := 0; i < retValCount; i++ {
192+
wrapped[i] = vals[i].Interface()
193+
}
194+
195+
if retValCount == 1 {
170196
return wrapped[0], nil
171197
}
172198

173199
return slurp.NewList(wrapped...), nil
174200
}
175201

176202
func convertArgsTo(expected reflect.Type, args ...reflect.Value) ([]reflect.Value, error) {
177-
var converted []reflect.Value
178-
for _, arg := range args {
203+
converted := make([]reflect.Value, len(args), len(args))
204+
for i, arg := range args {
179205
actual := arg.Type()
180-
switch {
181-
case isAssignable(actual, expected):
182-
converted = append(converted, arg)
183-
184-
case actual.ConvertibleTo(expected):
185-
converted = append(converted, arg.Convert(expected))
186-
187-
default:
206+
isAssignable := (actual == expected) ||
207+
actual.AssignableTo(expected) ||
208+
(expected.Kind() == reflect.Interface && actual.Implements(expected))
209+
if isAssignable {
210+
converted[i] = arg
211+
} else if actual.ConvertibleTo(expected) {
212+
converted[i] = arg.Convert(expected)
213+
} else {
188214
return args, fmt.Errorf(
189215
"value of type '%s' cannot be converted to '%s'",
190216
actual, expected,
@@ -195,11 +221,6 @@ func convertArgsTo(expected reflect.Type, args ...reflect.Value) ([]reflect.Valu
195221
return converted, nil
196222
}
197223

198-
func isAssignable(from, to reflect.Type) bool {
199-
return (from == to) || from.AssignableTo(to) ||
200-
(to.Kind() == reflect.Interface && from.Implements(to))
201-
}
202-
203224
func reflectValues(args []slurp.Any) []reflect.Value {
204225
var rvs []reflect.Value
205226
for _, arg := range args {

reflector/func_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package reflector
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
7+
"github.com/spy16/slurp"
8+
)
9+
10+
var (
11+
int64Type = reflect.TypeOf(int64(0))
12+
int8Type = reflect.TypeOf(int8(0))
13+
int8Vals []reflect.Value
14+
)
15+
16+
func init() {
17+
int8Vals = make([]reflect.Value, 1000)
18+
for i := 0; i < len(int8Vals); i++ {
19+
int8Vals[i] = reflect.ValueOf(int8(0))
20+
}
21+
}
22+
23+
func Benchmark_funcWrapper_Invoke(b *testing.B) {
24+
fw := Func("foo", func(a, b int) int { return a + b })
25+
26+
var res int
27+
for i := 0; i < b.N; i++ {
28+
ret, _ := fw.Invoke(nil, 1, 2)
29+
res = ret.(int)
30+
}
31+
b.Logf("final result: %d", res)
32+
}
33+
34+
func Benchmark_funcWrapper_Invoke_Variadic(b *testing.B) {
35+
fw := Func("foo", func(args ...int32) int32 { return args[0] + args[len(args)-1] })
36+
37+
args := make([]slurp.Any, 1000)
38+
for i := 0; i < len(args); i++ {
39+
args[i] = 1
40+
}
41+
42+
var res int32
43+
for i := 0; i < b.N; i++ {
44+
ret, _ := fw.Invoke(nil, args...)
45+
res = ret.(int32)
46+
}
47+
b.Logf("final result: %d", res)
48+
}
49+
50+
func Benchmark_convertArgsTo_Convertible(b *testing.B) {
51+
var retVals []reflect.Value
52+
53+
var ret []reflect.Value
54+
for i := 0; i < b.N; i++ {
55+
ret, _ = convertArgsTo(int64Type, int8Vals...)
56+
retVals = ret
57+
}
58+
dummyPrint(b, retVals)
59+
}
60+
61+
func Benchmark_convertArgsTo_Assign(b *testing.B) {
62+
var retVals []reflect.Value
63+
64+
var ret []reflect.Value
65+
for i := 0; i < b.N; i++ {
66+
ret, _ = convertArgsTo(int8Type, int8Vals...)
67+
retVals = ret
68+
}
69+
dummyPrint(b, retVals)
70+
}
71+
72+
func dummyPrint(b *testing.B, retVals []reflect.Value) {
73+
b.Logf("return values of size=%d", len(retVals))
74+
}

slurp_test.go

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,45 @@ func TestEnv_Resolve(t *testing.T) {
8383
assert(t, got == true, "want=true got=%#v", got)
8484
}
8585

86-
func assert(t *testing.T, cond bool, msg string, args ...interface{}) {
86+
func Benchmark_Eval_QuoteForm(b *testing.B) {
87+
env := New()
88+
quoteForm := QuoteExpr{Form: NewList(1, 2, 3)}
89+
90+
for i := 0; i < b.N; i++ {
91+
_, err := env.Eval(quoteForm)
92+
if err != nil {
93+
b.Fatalf("eval failed: %v", err)
94+
}
95+
}
96+
}
97+
98+
func Benchmark_Eval_Invocation(b *testing.B) {
99+
env := New()
100+
invocation := InvokeExpr{
101+
Env: env,
102+
Name: "test",
103+
Target: ConstExpr{Const: fakeInvokable(func(env *Env, args ...Any) (Any, error) {
104+
return 10, nil
105+
})},
106+
}
107+
108+
var finalRes Any
109+
for i := 0; i < b.N; i++ {
110+
res, err := env.Eval(invocation)
111+
if err != nil {
112+
b.Fatalf("eval failed: %v", err)
113+
}
114+
finalRes = res
115+
}
116+
assert(b, finalRes == 10, "want=10, got=%#v", finalRes)
117+
}
118+
119+
func assert(t testInstance, cond bool, msg string, args ...interface{}) {
87120
if !cond {
88121
t.Errorf(msg, args...)
89122
}
90123
}
124+
125+
type testInstance interface {
126+
Errorf(msg string, args ...interface{})
127+
}

0 commit comments

Comments
 (0)