Closed
Description
Consider the following snippet:
func NewA(x ...interface{}) error {
if len(x) > 0 {
return errors.New(x[0].(string))
}
return nil
}
func NewB(s ...string) error {
if len(s) > 0 {
return errors.New(s[0])
}
return nil
}
var sink error
func BenchmarkA(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
var s = "hello"
sink = NewA(s)
}
}
func BenchmarkB(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
var s = "hello"
sink = NewB(s)
}
}
Running benchmarks on this, you find:
BenchmarkA-8 20000000 84.3 ns/op 32 B/op 2 allocs/op
BenchmarkB-8 30000000 42.9 ns/op 16 B/op 1 allocs/op
Version A is more costly by 1 extra allocation. This occurs because calling NewA
has a hidden call to runtime.convT2Estring
where the string header is allocated on the heap.
However, I contend that the compiler should be able to prove that this is unnecessary. The call to NewA
is concrete, and so the compiler should able to prove that a string header passed into the variadic interfaces neither escaped nor is modified. When crafting the interface header, it should be able to directly point to the string header on the stack.
\cc @randall77 @neild
Metadata
Metadata
Assignees
Labels
Type
Projects
Relationships
Development
No branches or pull requests
Activity
[-]cmd/compile: stack allocate string and slice headers when passed through interface[/-][+]cmd/compile: stack allocate string and slice headers when passed through non-escaping interfaces[/+]randall77 commentedon Feb 3, 2018
@dr2chase
cherrymui commentedon Feb 4, 2018
The escape analysis does not track whether things are modified. So it cannot make an interface with its data field directly pointing to s.
That said, it is possible to have it point to a copy of s on stack. However, currently the escape analysis doesn't distinguish the interface and its underlying value, i.e.
x[0].(string)
escaping is seen asx[0]
escaping, which causes the string header allocated on heap.dsnet commentedon Feb 5, 2018
If the caller knew that that the last use of the string was the function call itself, then it wouldn't even need to make a copy on the stack.
cherrymui commentedon Feb 6, 2018
Once we fix the escape problem, this will probably not matter.
s
can be SSA'd. To make the interface it needs to stores
to a temp string header on stack so its address can be taken. There will be only one string header on stack.rogpeppe commentedon May 20, 2020
Isn't this issue a duplicate of #8618 ?
zigo101 commentedon Sep 30, 2021
It looks this is a problem of inlining:
Output:
After removing the
//go:noinline
directive lines, the output becomes into:zigo101 commentedon Sep 30, 2021
And this is not a string specified problem:
The output:
zigo101 commentedon Sep 30, 2021
The smallest case:
It looks the code inlining misleads the escape analysis, so that the arguments in the auto-constructed slice parameter are all allocated on heap.
{update]: if the value
99999
is changed to a value between [0, 255], then there will be no heap allocations.tdakkota commentedon Sep 30, 2021
It seems a optimization introduced by CL 216401.
zigo101 commentedon Nov 1, 2021
It looks this has been fixed on tip.
leitzler commentedon Nov 15, 2022
@dsnet this can be closed, right?
dsnet commentedon Nov 15, 2022
Correct. Confirmed that it is fixed.