-
Notifications
You must be signed in to change notification settings - Fork 18.4k
Description
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:
- The input slice is provably never mutated
- The output slice is provably never mutated
- The capacity of the slice is never observable (and by implication the elements between len and cap are never observed).
- 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
Labels
Type
Projects
Status