Skip to content

cmd/compile: idiomatic (returned) iterators are slower than unidiomatic, direct iterators #69411

@eliasnaur

Description

@eliasnaur

Go version

go version devel go1.24-fc968d49c1 Fri Aug 30 11:42:55 2024 -0400 darwin/arm64 (with CL 609095)

Output of go env in your module/workspace:

GO111MODULE=''
GOARCH='arm64'
GOBIN=''
GOCACHE='/Users/a/Library/Caches/go-build'
GOENV='/Users/a/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/Users/a/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/a/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/Users/a/proj/goroot'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/Users/a/proj/goroot/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='devel go1.24-fc968d49c1 Fri Aug 30 11:42:55 2024 -0400'
GODEBUG=''
GOTELEMETRY='local'
GOTELEMETRYDIR='/Users/a/Library/Application Support/go/telemetry'
GCCGO='gccgo'
GOARM64='v8.0'
AR='ar'
CC='clang'
CXX='clang++'
CGO_ENABLED='1'
GOMOD='/Users/a/Downloads/itergarbage/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/9c/t7b8z6m93sxgr4m4m4kbvllw0000gn/T/go-build4265899566=/tmp/go-build -gno-record-gcc-switches -fno-common'

What did you do?

Benchmarked https://go.dev/play/p/bNomKLr7kQX that compared the performance, in particular the allocations, of 3 ways to implement a nested iterator. The benchmarks are synthetic, because they're extracted from a larger project with more complex iterators.

$ go test -bench . -benchmem
goos: darwin
goarch: arm64
pkg: example.com
cpu: Apple M1 Pro
BenchmarkFormatIdiomatic-8         	39902122	        29.64 ns/op	      24 B/op	       2 allocs/op
BenchmarkFormatterIdiomatic-8      	38252599	        29.33 ns/op	      24 B/op	       2 allocs/op
BenchmarkFormatterNonIdiomatic-8   	381599230	         3.155 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	example.com	4.857s

What did you see happen?

The idiomatic way to create an iterator is to return it from a function or method. In my example, the iterator I want is

func FormatIdiomatic() iter.Seq[rune]

however, that and its method variant,

func (f *Formatter) RunesIdiomatic() iter.Seq[rune]

both result in allocations.

Notably, the unidiomatic

func (f *Formatter) RunesNonIdiomatic(yield func(rune) bool)

doesn't allocate and in general these "direct" iterators runs significantly faster than their idiomatic siblings.

What did you expect to see?

No allocations from idiomatic iterators, even if nested.

Note that I ran the tests with https://go-review.googlesource.com/c/go/+/609095 applied, which didn't seem to make a difference.

Related: #66469 and #69015

Metadata

Metadata

Assignees

Labels

FrozenDueToAgeNeedsFixThe path to resolution is known, but the work has not been done.Performancecompiler/runtimeIssues related to the Go compiler and/or runtime.

Type

No type

Projects

Status

Done

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions