go cmd/compile: better escape analysis of []byte -> string conversions

v64noz0r  于 4个月前  发布在  Go
关注(0)|答案(7)|浏览(56)

@aclements指出了3个在逃逸分析中可以更好地处理的情况:

  1. 不安全地从 []byte 转换为 string :
func unsafeBytesToString(buf []byte) string {
	if len(buf) == 0 {
		return ""
	}
	var str string
	pStr := (*reflect.StringHeader)(unsafe.Pointer(&str))
	pStr.Data = uintptr(unsafe.Pointer(&buf[0]))
	pStr.Len = len(buf)
	return str
}

这将导致 buf 被分配到堆上,因为 pStr.Data = uintptr(unsafe.Pointer(&buf[0])) 的赋值被视为 (*pStr).Data = unsafe.Pointer(&buf[0]) ,而通过指针间接赋值的操作目前总是被视为逃逸到堆上。
如果我们在逃逸分析过程中注意到 pStr 总是指向 str ,那么我们就可以改进这一点。这样,&buf[0] 就可以直接流向 str ,而不是流向堆。因此,整个函数的分析结果应该是 "参数 buf 泄漏到 ~r0 ",而不是 "泄漏到堆"。
2. 直接且非逃逸的转换,例如:

var buf [32]byte
... populate buf ...
strconv.Atoi(string(buf[:]))
... no more uses of buf ...

在这种情况下,strconv.Atoi 没有将其字符串参数泄漏到任何地方,所以我们应该能够轻松地识别出 OBYTES2STR 参数可以安全地提升为 OBYTES2STRTMP
3. 直接且逃逸的转换,例如:

var buf [32]byte
... populate buf ...
return string(buf[:])
... no more uses of buf ...

与上述情况类似,如果我们认识到 buf 在转换为 string 后从未被重用,那么我们应该能够直接重用 string 的字节数组内存,而无需复制。在 dev.regabi 上,闭包分析(即决定是否以值还是引用的方式捕获变量)现在已经成为逃逸分析的一部分,而且将其扩展到处理这种用例也是合理的。我们需要识别出像 string(buf[:]) 这样的操作实际上是按值复制 buf ,而不是对其进行引用。

kqhtkvqz

kqhtkvqz1#

strconv.Atoi 是否会将参数(不是头的副本,而是支持数组本身)泄漏到返回的错误中(在一般情况下,如果有错误处理)?
"类似于上面的2,如果我们认识到buf在转换为字符串后从未重用"。
请注意,字节数组内存目前是在栈上分配的,然后复制到堆上。新的方法可能是在堆上分配32字节,然后直接在返回时重用它。然而,这可能导致性能下降,如果有一个快速路径在例如错误情况下返回一个常量字符串。这可能会导致从未返回的heap分配buf。

var buf [32]byte
... populate buf ...
if SOMECONDITION {
 return ""
}
return string(buf[:])
jtw3ybtb

jtw3ybtb2#

@martisch 感谢,很好的观点。
我忘记了strconv.Atoi有一个错误的结果会泄漏字符串参数。同意这会阻止它被优化(除非我们有额外的调用上下文信息来知道错误永远不会泄漏)。我也想到我们需要以某种方式知道strconv.Atoi不能通过任何隐蔽的旁路修改buf
是的,关于情况3这是一个公平的观点。同样,如果字符串通常很小,但代码保守地分配一个大缓冲区以防万一,我们最终会一直保留整个东西。所以可能我们应该只在字符串不泄漏的情况下优化掉复制操作。我认为这是可行的,并且仍然有益处。

jaql4c8m

jaql4c8m3#

https://golang.org/cl/285232提到了这个问题:[dev.regabi] cmd/compile: more zero-copy []byte-to-string conversions

fzwojiic

fzwojiic4#

CL 285232实现了案例3,基于@martisch的反馈提出了限制(即,只有在字节切片内存已经在栈上并且可以保持在那里时才重用它,因此它不能引入新的堆分配或导致引用保留比它们本应保留的更多内存)。

在标准库中触发了几次,但对我来说似乎没有什么特别大的胜利:

$GOROOT/src/syscall/str.go:9:21: zero-copy string conversion
$GOROOT/src/os/str.go:12:21: zero-copy string conversion
$GOROOT/src/cmd/vendor/golang.org/x/sys/unix/str.go:11:21: zero-copy string conversion
$GOROOT/src/net/parse.go:178:21: zero-copy string conversion
$GOROOT/src/net/dnsclient.go:36:15: zero-copy string conversion
$GOROOT/src/net/dnsclient.go:36:43: zero-copy string conversion
$GOROOT/src/net/dnsclient.go:36:71: zero-copy string conversion
$GOROOT/src/net/dnsclient.go:36:99: zero-copy string conversion
$GOROOT/src/net/ip.go:534:34: zero-copy string conversion
$GOROOT/src/net/http/server.go:1844:98: zero-copy string conversion
$GOROOT/src/net/http/server.go:3544:15: zero-copy string conversion
$GOROOT/src/cmd/compile/internal/escape/escape.go:2180:20: zero-copy string conversion
$GOROOT/src/cmd/compile/internal/escape/escape.go:2206:20: zero-copy string conversion
$GOROOT/src/cmd/compile/internal/escape/escape.go:2234:19: zero-copy string conversion
$GOROOT/src/cmd/compile/internal/escape/escape.go:1890:22: zero-copy string conversion

相关的细化也有助于逃逸分析;这些分配现在可以栈分配(即,诊断信息曾经由编译器打印,但在CL 285232之后不再打印):

$GOROOT/src/cmd/dist/test.go:748:3: moved to heap: nShards
$GOROOT/src/cmd/go/internal/work/exec.go:61:22: moved to heap: ctx
$GOROOT/src/cmd/vendor/golang.org/x/tools/go/analysis/internal/facts/facts.go:203:43: f.PkgPath escapes to heap
$GOROOT/src/go/internal/srcimporter/srcimporter.go:167:2: moved to heap: open
$GOROOT/src/go/printer/printer.go:805:21: "negative indentation:" escapes to heap
$GOROOT/src/go/printer/printer.go:805:47: p.indent escapes to heap
$GOROOT/src/go/printer/printer.go:948:22: "whitespace buffer not empty" escapes to heap
$GOROOT/src/testing/benchmark.go:754:2: moved to heap: grain

最后,它还看起来像std cmd中的21个变量现在可以通过值捕获函数字面量,而它们以前是通过引用捕获的。我不小心更改了诊断行号,所以我无法立即列出它们。稍后我会在拆分CL并更容易检查时识别它们。

dzjeubhm

dzjeubhm5#

@mdempsky,作为另一个有趣的数据点,您可以检查使用-tags=unsafe构建时,my unsafeslice.OfStringunsafeslice.AsString是否对功能产生任何影响。从语义上讲,它们就像情况(1)一样,但实际代码在使用头文件和Data字段方面更加激进,而不是使用len检查。

5tmbdcev

5tmbdcev6#

@bcmills 感谢。我预计处理案例1应该自然地处理这两个功能,但我会确保一旦我开始研究它。

zhte4eai

zhte4eai7#

这个问题是否可以移动到下一个发布里程碑(1.19)或者退回待办事项列表?如果在1.18中没有剩余的CL提交,那么这似乎有意义。

相关问题