为什么切片一直从堆栈中逃逸?

huangapple go评论112阅读模式
英文:

Why slice kept escaping from stack?

问题

我正在尝试解决LeetCode问题permutations。但是当我使用-benchmem进行测试时,我发现它分配了太多的内存,当permute([]int{1,2,3,4,5,6})时,每次操作分配了1957个内存。

我发现在生成子数字目标时,它逃逸到了堆上。即使我尝试分配[6]int,并使用unsafe包构建切片,它仍然会被移动到堆上。

我的问题是,为什么切片逃逸到堆上,我如何在栈上分配切片?

这是我的代码:

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. "unsafe"
  6. )
  7. func permute(nums []int) [][]int {
  8. resLen := 1
  9. for i := 1; i<= len(nums);i ++{
  10. resLen *= i
  11. }
  12. // 预分配
  13. res := make([][]int, resLen)
  14. for i := range res{
  15. res[i] = make([]int, 0, len(nums))
  16. }
  17. build(res, nums)
  18. return res
  19. }
  20. func build(res [][]int,targets []int){
  21. step := len(res) / len(targets)
  22. for i := range targets{
  23. for j := i*step; j < (i+1) * step; j ++{
  24. res[j] = append(res[j], targets[i])
  25. }
  26. if len(targets) != 1{
  27. var ab = [6]int{}
  28. var buff []int
  29. var bp *reflect.SliceHeader
  30. bp = (*reflect.SliceHeader)(unsafe.Pointer(&buff))
  31. bp.Data = uintptr(unsafe.Pointer(&ab))
  32. bp.Cap = 6
  33. buff = append(buff, targets[:i]...)
  34. buff = append(buff, targets[i+1:]...)
  35. build(res[i*step:(i+1)*step], buff)
  36. }
  37. }
  38. return
  39. }
  40. func main() {
  41. nums := []int{1,2,3}
  42. res := permute(nums)
  43. fmt.Println(res)
  44. }

build函数没有使用unsafe,但是逃逸到了堆上:

  1. func build(res [][]int, targets []int) {
  2. step := len(res) / len(targets)
  3. for i := range targets {
  4. for j := i * step; j < (i+1)*step; j++ {
  5. res[j] = append(res[j], targets[i])
  6. }
  7. if len(targets) != 1 {
  8. buff := make([]int, 0, 6) // make([]int, 0, 6) 逃逸到了堆上
  9. buff = append(buff, targets[:i]...)
  10. buff = append(buff, targets[i+1:]...)
  11. build(res[i*step:(i+1)*step], buff)
  12. }
  13. }
  14. return
  15. }

我的测试用例:

  1. package main
  2. import "testing"
  3. func Benchmark(b *testing.B){
  4. for i:=0;i<b.N;i++{
  5. permute([]int{1,2,3,4,5,6})
  6. }
  7. }

当我运行go build -gcflags="-m"时,它报告了./main.go:32:8: moved to heap: ab

英文:

I am trying to solve leetcode problem permutations.
But when i test with -benchmem, i found it allocs too much which reach 1957 allocs/op when permute([]int{1,2,3,4,5,6})

I found it escape to heap when generating sub-nums target. Even i try to allocate [6]int, and use unsafe package to build the slice, it still moved to heap.

My question is, why the slice escape to heap, and how could i allocate the slice on stack?

Here's my code:

  1. package main
  2. import (
  3. &quot;fmt&quot;
  4. &quot;reflect&quot;
  5. &quot;unsafe&quot;
  6. )
  7. func permute(nums []int) [][]int {
  8. resLen := 1
  9. for i := 1; i&lt;= len(nums);i ++{
  10. resLen *= i
  11. }
  12. // pre allocate
  13. res := make([][]int, resLen)
  14. for i := range res{
  15. res[i] = make([]int, 0, len(nums))
  16. }
  17. build(res, nums)
  18. return res
  19. }
  20. func build(res [][]int,targets []int){
  21. step := len(res) / len(targets)
  22. for i := range targets{
  23. for j := i*step; j &lt; (i+1) * step; j ++{
  24. res[j] = append(res[j], targets[i])
  25. }
  26. if len(targets) != 1{
  27. var ab = [6]int{}
  28. var buff []int
  29. var bp *reflect.SliceHeader
  30. bp = (*reflect.SliceHeader)(unsafe.Pointer(&amp;buff))
  31. bp.Data = uintptr(unsafe.Pointer(&amp;ab))
  32. bp.Cap = 6
  33. buff = append(buff, targets[:i]...)
  34. buff = append(buff, targets[i+1:]...)
  35. build(res[i*step:(i+1)*step], buff)
  36. }
  37. }
  38. return
  39. }
  40. func main() {
  41. nums := []int{1,2,3}
  42. res := permute(nums)
  43. fmt.Println(res)
  44. }

build function without unsafe but escapes to heap:

  1. func build(res [][]int, targets []int) {
  2. step := len(res) / len(targets)
  3. for i := range targets {
  4. for j := i * step; j &lt; (i+1)*step; j++ {
  5. res[j] = append(res[j], targets[i])
  6. }
  7. if len(targets) != 1 {
  8. buff := make([]int, 0, 6) // make([]int, 0, 6) escapes to heap
  9. buff = append(buff, targets[:i]...)
  10. buff = append(buff, targets[i+1:]...)
  11. build(res[i*step:(i+1)*step], buff)
  12. }
  13. }
  14. return
  15. }

And my test case:

  1. package main
  2. import &quot;testing&quot;
  3. func Benchmark(b *testing.B){
  4. for i:=0;i&lt;b.N;i++{
  5. permute([]int{1,2,3,4,5,6})
  6. }
  7. }

When i run go build -gcflags=&quot;-m&quot;, it reports ./main.go:32:8: moved to heap: ab

答案1

得分: 5

尝试使用unsafe.Pointer来破坏编译器只会让逃逸分析更加困难,从而阻止切片被分配到栈上。只需分配一个单独的切片,并在每次循环迭代中重用它:

  1. func build(res [][]int, targets []int) {
  2. buff := make([]int, 0, 6)
  3. step := len(res) / len(targets)
  4. for i := range targets {
  5. buff = buff[:0]
  6. for j := i * step; j < (i+1)*step; j++ {
  7. res[j] = append(res[j], targets[i])
  8. }
  9. if len(targets) != 1 {
  10. buff = append(buff, targets[:i]...)
  11. buff = append(buff, targets[i+1:]...)
  12. build(res[i*step:(i+1)*step], buff)
  13. }
  14. }
  15. return
  16. }

编译器可以正确优化这段代码:

  1. ./main.go:26:17: make([]int, 0, 6) does not escape

并且只会产生所需的分配:

  1. Benchmark-8 44607 26838 ns/op 52992 B/op 721 allocs/op
英文:

Trying to subvert the compiler using unsafe.Pointer is only making it harder for the escape analysis to do its job, preventing the slice from being stack allocated. Simply allocate a single slice and reuse it for each loop iteration:

  1. func build(res [][]int, targets []int) {
  2. buff := make([]int, 0, 6)
  3. step := len(res) / len(targets)
  4. for i := range targets {
  5. buff = buff[:0]
  6. for j := i * step; j &lt; (i+1)*step; j++ {
  7. res[j] = append(res[j], targets[i])
  8. }
  9. if len(targets) != 1 {
  10. buff = append(buff, targets[:i]...)
  11. buff = append(buff, targets[i+1:]...)
  12. build(res[i*step:(i+1)*step], buff)
  13. }
  14. }
  15. return
  16. }

This can be correctly optimized by the compiler

  1. ./main.go:26:17: make([]int, 0, 6) does not escape

And will result in only the desired allocations:

  1. Benchmark-8 44607 26838 ns/op 52992 B/op 721 allocs/op

huangapple
  • 本文由 发表于 2021年7月13日 01:43:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/68351808.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定