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

cmd/compile: stack allocate string and slice headers when passed through non-escaping interfaces #23676

Open
dsnet opened this Issue Feb 3, 2018 · 4 comments

Comments

Projects
None yet
5 participants
@dsnet
Member

dsnet commented Feb 3, 2018

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

@dsnet dsnet added the Performance label Feb 3, 2018

@dsnet dsnet changed the title from cmd/compile: stack allocate string and slice headers when passed through interface to cmd/compile: stack allocate string and slice headers when passed through non-escaping interfaces Feb 3, 2018

@randall77

This comment has been minimized.

Contributor

randall77 commented Feb 3, 2018

@cherrymui

This comment has been minimized.

Contributor

cherrymui commented Feb 4, 2018

the compiler should able to prove that a string header passed into the variadic interfaces neither escaped nor is modified.

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 as x[0] escaping, which causes the string header allocated on heap.

@dsnet

This comment has been minimized.

Member

dsnet commented 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

This comment has been minimized.

Contributor

cherrymui commented Feb 6, 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.

Once we fix the escape problem, this will probably not matter. s can be SSA'd. To make the interface it needs to store s to a temp string header on stack so its address can be taken. There will be only one string header on stack.

@ianlancetaylor ianlancetaylor added this to the Go1.11 milestone Mar 28, 2018

@bradfitz bradfitz modified the milestones: Go1.11, Unplanned May 18, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment