Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
eliasnaur opened this issue Oct 9, 2024 · 0 comments
Open

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

eliasnaur opened this issue Oct 9, 2024 · 0 comments

Comments

@eliasnaur
Copy link
Contributor

$ 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant