你使用的Go版本是什么(go version
)?
$ go version
go version go1.16.6 linux/amd64
这个问题在最新版本中是否会重现?
你正在使用什么操作系统和处理器架构(go env
)?
go env
输出
$ go env
你做了什么?
package main
// translate translates any byte in s between (lower, upper) to (newLower, newLower + upper - lower)
func translate(s string, cap int, lower, upper, newLower byte) string {
// code removed
b := append(make([]byte, 0, cap), s...)
// code removed
return string(b)
}
// toLower returns s with all upper case characters replaced with their lower case counterparts.
func toLower(s string, cap int) string {
return translate(s, cap, 'A', 'Z', 'a')
}
// lookup ASCII case insensitive map lookup.
func lookup(m map[string]string, key string) (string, bool) {
if len(key) > 32 {
return "", false
}
r, ok := m[toLower(key, 32)]
return r, ok
}
你期望看到什么?
translate()内联到toLower(),结果toLower()也会内联到它的调用者。因此,在lookup()中,当toLower()被内联时,期望translate()创建的切片容量是常量,因此可以在栈上预留。
你看到了什么?
make([]byte, 0, cap)
在lookup()中转储到堆(非常量大小)。如果在translate()中使用固定容量,如make([]byte, 0, 32)
,则切片始终可以在栈上预留。
5条答案
按热度按时间0lvr5msh1#
我认为这只是逃逸分析中常传播的限制。内联引入了临时变量,我们不会通过这些临时变量传播。
voase2hg2#
出于好奇,为什么在这种情况下传播常量不会发生?
92vpleto3#
没有人实现它。
在编译器的这个阶段有点棘手,因为你必须证明一个变量的值永远不会改变。这包括查找该变量的其他赋值,包括通过指针。
ezykj2lf4#
我的理解可能有些偏差,但持续传播应该能解决这个问题。也就是说,它不应该接触指针或其他类似的东西。在内联后,它应该能在传播32到
cap
的过程中完成它的工作。当然,如果所有的临时变量都实现为指针,那么我可以看到这个问题。kq4fsx7k5#
在完美实现的情况下,不需要cap参数。
在lookup()中的len()检查之后,编译器应该知道key的长度可能范围是(0, 32)。那么,就不需要使用显式的固定容量,可以使用
make([]byte, 0, len(s))
,编译器会确定上限足够小,总是可以栈分配。通过边界检查消除将较小的非逃逸分配移动到栈上。