Skip to content

Commit 492d367

Browse files
authored
internal/compile: build index of local vars (#576)
This eliminates the linear scan over locals in the interpreter when processing named arguments. This reduces the new benchmark from 1081ns to 843ns (-22%). (This was inspired by recent work in the Java-based implementation, but findParams seemed to be a bigger hit in that case, possibly because of the two extra levels of indirection involved in the length comparison (hot path) of a Java array of Java strings, versus Go where they are all in the same cache line.)
1 parent d908c3e commit 492d367

File tree

4 files changed

+36
-12
lines changed

4 files changed

+36
-12
lines changed

internal/compile/compile.go

+21
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,8 @@ type Funcode struct {
345345

346346
lntOnce sync.Once
347347
lnt []pclinecol // decoded line number table
348+
349+
localsMap map[string]int
348350
}
349351

350352
type pclinecol struct {
@@ -403,6 +405,24 @@ type insn struct {
403405
line, col int32
404406
}
405407

408+
// finish is called to populate derived fields of Funcode.
409+
func (fn *Funcode) finish() {
410+
// (We could populate this map on first use,
411+
// but the sync adds 33% to bench_calling_kwargs.)
412+
fn.localsMap = make(map[string]int, len(fn.Locals))
413+
for index, binding := range fn.Locals {
414+
fn.localsMap[binding.Name] = index
415+
}
416+
}
417+
418+
// Local returns the index of the named local, or -1.
419+
func (fn *Funcode) Local(name string) int {
420+
if i, ok := fn.localsMap[name]; ok {
421+
return i
422+
}
423+
return -1
424+
}
425+
406426
// Position returns the source position for program counter pc.
407427
func (fn *Funcode) Position(pc uint32) syntax.Position {
408428
fn.lntOnce.Do(fn.decodeLNT)
@@ -680,6 +700,7 @@ func (pcomp *pcomp) function(name string, pos syntax.Position, stmts []syntax.St
680700
fmt.Fprintf(os.Stderr, "end function(%s @ %s)\n", name, pos)
681701
}
682702

703+
fcomp.fn.finish()
683704
return fn
684705
}
685706

internal/compile/serial.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ func (d *decoder) function() *Funcode {
380380
numKwonlyParams := d.int()
381381
hasVarargs := d.int() != 0
382382
hasKwargs := d.int() != 0
383-
return &Funcode{
383+
fn := &Funcode{
384384
// Prog is filled in later.
385385
Pos: id.Pos,
386386
Name: id.Name,
@@ -396,4 +396,6 @@ func (d *decoder) function() *Funcode {
396396
HasVarargs: hasVarargs,
397397
HasKwargs: hasKwargs,
398398
}
399+
fn.finish()
400+
return fn
399401
}

starlark/eval.go

+3-11
Original file line numberDiff line numberDiff line change
@@ -1491,10 +1491,9 @@ func setArgs(locals []Value, fn *Function, args Tuple, kwargs []Tuple) error {
14911491
}
14921492

14931493
// Bind keyword arguments to parameters.
1494-
paramIdents := fn.funcode.Locals[:nparams]
14951494
for _, pair := range kwargs {
14961495
k, v := pair[0].(String), pair[1]
1497-
if i := findParam(paramIdents, string(k)); i >= 0 {
1496+
if i := fn.funcode.Local(string(k)); i >= 0 && i < nparams {
14981497
if locals[i] != nil {
14991498
return fmt.Errorf("function %s got multiple values for parameter %s", fn.Name(), k)
15001499
}
@@ -1515,6 +1514,8 @@ func setArgs(locals []Value, fn *Function, args Tuple, kwargs []Tuple) error {
15151514
if n < nparams || fn.NumKwonlyParams() > 0 {
15161515
m := nparams - len(fn.defaults) // first default
15171516

1517+
paramIdents := fn.funcode.Locals[:nparams]
1518+
15181519
// Report errors for missing required arguments.
15191520
var missing []string
15201521
var i int
@@ -1544,15 +1545,6 @@ func setArgs(locals []Value, fn *Function, args Tuple, kwargs []Tuple) error {
15441545
return nil
15451546
}
15461547

1547-
func findParam(params []compile.Binding, name string) int {
1548-
for i, param := range params {
1549-
if param.Name == name {
1550-
return i
1551-
}
1552-
}
1553-
return -1
1554-
}
1555-
15561548
// https://github.com/google/starlark-go/blob/master/doc/spec.md#string-interpolation
15571549
func interpolate(format string, x Value) (Value, error) {
15581550
buf := new(strings.Builder)

starlark/testdata/benchmark.star

+9
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ def bench_calling(b):
2525
for _ in range(b.n):
2626
f()
2727

28+
# Test the efficiency of named arguments.
29+
def bench_calling_kwargs(b):
30+
def f(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p):
31+
pass
32+
33+
for _ in range(b.n):
34+
f(a=1, b=2, c=3, d=4, e=5, f=7, g=7, h=8,
35+
i=9, j=10, k=11, l=12, m=13, n=14, o=15, p=16)
36+
2837
# Measure overhead of calling a trivial built-in method.
2938
emptydict = {}
3039
range1000 = range(1000)

0 commit comments

Comments
 (0)