英文:
why the below two code cause escape in golang
问题
我正在调查Golang的逃逸分析,但在这篇文章中(http://npat-efault.github.io/programming/2016/10/10/escape-analysis-and-interfaces.html),我有两个困惑的地方:
代码一:
func Ok(f os.File) []byte {
var x [128]byte
b := x[:]
n, _ := f.Read(b)
r := make([]byte, n)
copy(r, b[:n])
return r
}
func NotOk(c net.Conn) []byte {
var x [128]byte
b := x[:]
n, _ := c.Read(b)
r := make([]byte, n)
copy(r, b[:n])
return r
}
go build -gcflags "-m -l" part3_escape.go
输出结果:
# command-line-arguments
./part3_escape.go:64:9: leaking param: f
./part3_escape.go:68:11: make([]byte, n) escapes to heap
./part3_escape.go:73:12: leaking param: c
./part3_escape.go:74:6: moved to heap: x
./part3_escape.go:77:11: make([]byte, n) escapes to heap
# command-line-arguments
runtime.main_main·f: function main is undeclared in the main package
我不知道为什么x会逃逸(moved to heap: x
)。
代码二:
type S struct {
s1 int
}
func (s *S) M1(i int) { s.s1 = i }
type I interface {
M1(int)
}
func g() {
var s1 S // this escapes
var s2 S // this does not
f1(&s1)
f2(&s2)
}
func f1(s I) { s.M1(42) }
func f2(s *S) { s.M1(42) }
go build -gcflags "-m -l" part3_escape.go
输出结果:
# command-line-arguments
./part3_escape.go:63:7: s does not escape
./part3_escape.go:77:9: leaking param: s
./part3_escape.go:78:9: s does not escape
./part3_escape.go:70:6: moved to heap: s1
<autogenerated>:1: leaking param: .this
# command-line-arguments
runtime.main_main·f: function main is undeclared in the main package
我不知道为什么s1会逃逸(moved to heap: s1
)。
如果有人能帮忙解释一下,那就太好了,谢谢!🙂
英文:
I'm investigating the golang's escape analysis, but in the post http://npat-efault.github.io/programming/2016/10/10/escape-analysis-and-interfaces.html, I have two points of confusion:
Code one:
func Ok(f os.File) []byte {
var x [128]byte
b := x[:]
n, _ := f.Read(b)
r := make([]byte, n)
copy(r, b[:n])
return r
}
func NotOk(c net.Conn) []byte {
var x [128]byte
b := x[:]
n, _ := c.Read(b)
r := make([]byte, n)
copy(r, b[:n])
return r
}
go build -gcflags "-m -l" part3_escape.go
The output:
# command-line-arguments
./part3_escape.go:64:9: leaking param: f
./part3_escape.go:68:11: make([]byte, n) escapes to heap
./part3_escape.go:73:12: leaking param: c
./part3_escape.go:74:6: moved to heap: x
./part3_escape.go:77:11: make([]byte, n) escapes to heap
# command-line-arguments
runtime.main_main·f: function main is undeclared in the main package
I do not know why x escape (moved to heap: x
)
Code two:
type S struct {
s1 int
}
func (s *S) M1(i int) { s.s1 = i }
type I interface {
M1(int)
}
func g() {
var s1 S // this escapes
var s2 S // this does not
f1(&s1)
f2(&s2)
}
func f1(s I) { s.M1(42) }
func f2(s *S) { s.M1(42) }
go build -gcflags "-m -l" part3_escape.go
The output:
# command-line-arguments
./part3_escape.go:63:7: s does not escape
./part3_escape.go:77:9: leaking param: s
./part3_escape.go:78:9: s does not escape
./part3_escape.go:70:6: moved to heap: s1
<autogenerated>:1: leaking param: .this
# command-line-arguments
runtime.main_main·f: function main is undeclared in the main package
I do not know why s1 escape (moved to heap: s1
)
It would be great if someone could help explain it,thx😄
答案1
得分: 2
编译器无法证明通过接口调用不会在某处存储指针。可能存在一种实现接口的方式会存储指针,因此编译器必须假设值可能会逃逸。
较新的Go版本可以在内联时进行虚拟调用的优化,从而避免值的逃逸。当在第二个示例中移除-l
标志时,您将看到这一点。
关于此问题有一些讨论,请参考https://github.com/golang/go/issues/33160。
英文:
The compiler is unable to prove that a call through the interface doesn't store a pointer somewhere. There may be a potential implementation of the interface that stores a pointer, hence it must assume the values can escape.
Newer Go versions can devirtualise calls when inlining and potentially avoid values escaping. You will see this when removing the -l
flag in your second example.
There is some discussion on https://github.com/golang/go/issues/33160.
答案2
得分: 0
基本上,逃逸分析是在编译时进行的。如果编译器无法确定一个内存(函数中的变量)在函数返回后是否会被访问,它将逃逸到堆上,因为函数返回后,栈上的内存将无效。编译器在这里表现得悲观,如果它在编译时无法确定一个内存是否会在后续被访问,它将逃逸到堆上。
针对你的问题:
func g() {
var s1 S // 这个逃逸了
var s2 S // 这个没有逃逸
f1(&s1)
f2(&s2)
}
这里的s1逃逸到堆上,因为它接受接口类型,接口类型在编译时无法确定将执行什么操作,只有在运行时才能确定。所以s1逃逸了。
s2没有逃逸,因为我们将指针传递到了栈上,而不是栈上方。要了解更多信息,请参考https://www.ardanlabs.com/blog/2017/05/language-mechanics-on-escape-analysis.html
代码片段1:
func Ok(f os.File) []byte {
var x [128]byte
b := x[:]
n, _ := f.Read(b)
r := make([]byte, n)
copy(r, b[:n])
return r
}
func NotOk(c net.Conn) []byte {
var x [128]byte
b := x[:]
n, _ := c.Read(b)
r := make([]byte, n)
copy(r, b[:n])
return r
}
问题在于net.Conn是一个接口类型,而os.File不是,它是一个结构体。当编译器看到对net.Conn方法的调用时,无法在编译时知道(在编译时)该方法的实现是否以某种方式保留了它的引用参数的副本。编译器悲观地认为x将逃逸到堆上。
要了解有关逃逸分析的更多信息,可以参考以下两个链接:
https://www.ardanlabs.com/blog/2017/05/language-mechanics-on-escape-analysis.html
https://www.ardanlabs.com/blog/2017/06/language-mechanics-on-memory-profiling.html
英文:
Basically escape analysis happens at the compile time. If the compiler can't decide if a memory(variable in a function) will be accessed even after the function returns, it will be escaped to the heap because memory in the stack will be invalid after the function returns. Compiler here acts pessimistic, if it can't decide at the compile time that a memory will be accessed later it will be escaped to heap.
Coming to your question:
func g() {
var s1 S // this escapes
var s2 S // this does not
f1(&s1)
f2(&s2)
}
Here the s1 escapes to the heap because it's accepting interface type, interface type doesn't know what it will executing at the compile time, it only happens during runtime. So s1 escapes.
s2 doesn't escape because we are passing the pointer down the stack, not up the stack. To know more about this please refer https://www.ardanlabs.com/blog/2017/05/language-mechanics-on-escape-analysis.html
snippet 1:
func Ok(f os.File) []byte {
var x [128]byte
b := x[:]
n, _ := f.Read(b)
r := make([]byte, n)
copy(r, b[:n])
return r
}
func NotOk(c net.Conn) []byte {
var x [128]byte
b := x[:]
n, _ := c.Read(b)
r := make([]byte, n)
copy(r, b[:n])
return r
}
The problem here is same, that net.Conn is an interface type, while os.File is not, it is a struct. When the compiler sees a call to a method of net.Conn there is no way to know (at compile time) if the method’s implementation somehow retains a copy of it’s reference argument. The compiler being pessimistic, x will be escaped to the heap.
To know more about escape analysis, you can follow the below 2 links
https://www.ardanlabs.com/blog/2017/05/language-mechanics-on-escape-analysis.html
https://www.ardanlabs.com/blog/2017/06/language-mechanics-on-memory-profiling.html
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论