Skip to content

cmd/compile: avoid slices.Clone for non-mutating slices #75620

@dsnet

Description

@dsnet

Go version

go1.25

Output of go env in your module/workspace:

n/a

What did you do?

I have code like:

func Benchmark(b *testing.B) {
	b.ReportAllocs()
	for b.Loop() {
		Select([]reflect.SelectCase{
			{},
			{},
			{},
		}...)
	}
}

func Select(cases ...reflect.SelectCase) int {
	return selectImmutableCases(slices.Clone(cases)...)
}

//go:noinline
func selectImmutableCases(cases ...reflect.SelectCase) int {
	// Pretend this is the rest of the implementation of reflect.Select,
	// which does not copy, escape, or mutate cases.
	return 0
}

What did you see happen?

It allocates the []reflect.SelectCase.

What did you expect to see?

It doesn't allocate at all.

This is a hypothetical idea for how to optimize reflect.Select, which always allocates when there are enough cases. This is unfortunate as many calls to reflect.Select are given a slice literal with a statically known number of cases.

The purpose of slices.Clone in Select is to ensure that Select can operate on the slice with the assurance that the caller isn't messing with the slice while blocked. In a vast majority of cases, I suspect Select is called with a slice literal where it is never mutated, so this is an unnecessary amount of defensive copying.

Ideally, we can make slices.Clone a noop (i.e., return the input verbatim) if the following conditions are true:

  1. The input slice is provably never mutated
  2. The output slice is provably never mutated
  3. The capacity of the slice is never observable (and by implication the elements between len and cap are never observed).
  4. The address of individual element are never observed.

By making Select inlineable with just the slices.Clone, it allows the compiler to better analyze the calling context and see that the input slice is never mutated.

This would be a useful optimization technique as there are many patterns (not just in reflect.Select) that does defensive copying of a slice, when it otherwise was already safe to use verbatim.

Note that I'm just using slices.Clone as a signal to trigger this optimization, the same could be said about:

func Select(cases ...reflect.SelectCase) int {
	return selectImmutableCases(append([]reflect.SelectCases(nil), cases...)...)
}

but the compiler analysis would further need to prove that nil-ness of the output slice never mattered.

\cc @bradfitz

Metadata

Metadata

Assignees

No one assigned

    Labels

    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.Performancecompiler/runtimeIssues related to the Go compiler and/or runtime.

    Type

    No type

    Projects

    Status

    No status

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions