Skip to content

compiler: ensure iterators are inlined in -opt=z #4512

Open
@eliasnaur

Description

@eliasnaur
$ cat iter_test.go
package inlineiter

import (
	"iter"
	"testing"
)

var slice = make([]int, 1000)

func TestIterAllocs(t *testing.T) {
	tests := []struct {
		name string
		body func() int
	}{
		{
			"for-range-func",
			func() int {
				sum := 0
				for v := range Iterator([]int{0, 1, 2}) {
					sum += v
				}
				return sum
			},
		},
		{
			"callback-func",
			func() int {
				sum := 0
				Iterator([]int{0, 1, 2})(func(v int) bool {
					sum += v
					return true
				})
				return sum
			},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			allocs := testing.Benchmark(func(b *testing.B) {
				for range b.N {
					sum := test.body()
					if sum != 3 {
						t.Errorf("sum %d, want 3", sum)
					}
				}
			}).AllocsPerOp()
			if allocs > 0 {
				t.Errorf("Layout allocates %d, expected %d", allocs, 0)
			}
		})
	}
}

func Iterator(ints []int) iter.Seq[int] {
	return func(yield func(int) bool) {
		for _, v := range ints {
			if !yield(v) {
				break
			}
		}
	}
}
$ go test
PASS
ok  	seedhammer.com/inlineiter	4.616s
$ tinygo test
--- FAIL: TestIterAllocs (3.08s)
    --- FAIL: TestIterAllocs/for-range-func (1.58s)
        Layout allocates 5, expected 0
    --- FAIL: TestIterAllocs/callback-func (1.50s)
        Layout allocates 3, expected 0
FAIL
FAIL	seedhammer.com/inlineiter	3.297s
$ tinygo test -opt 2
ok  	seedhammer.com/inlineiter	3.211s

The idiom for iterator constructors is to return a closure that captures the iterator arguments, if any. It's crucial for the performance of range-over-func that the constructor doesn't incur allocations. Therefore, it seems to me functions that (nearly) immediately return a closure should always be inlined, regardless of optimization level. Or, at the very least, any function that returns a function that can be ranged over.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions