一个逃逸分析问题,求指教

coderyw · 2022-06-23 18:50:37 · 2859 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2022-06-23 18:50:37 的主题,其中的信息可能已经有所发展或是发生改变。

今天在分析代码时候,突然想到一个事情,在使用channel时候,我看了代码,里面传递的数据时指针类型的。我当时就在考虑,这种做法会不会导致GC压力过大,然后我进行的测试发现的确会比没有指针类型的压力大点(也不是很多)。 后面我考虑想着能不能换成非指针数据,然后我突然考虑到了一个问题,我们一般接收channel数据时候,都是在一个func中处理,比如:

func rcv() {
    for {
        p := <-pc
        p.A = 1
    }
}

这种方式。这里的pc传递的是非指针数据。我想了一下,也没有发生逃逸,那么问题来了: 这里的p会分配在栈上,那我的程序一直跑着,不会导致栈空间不足吗?然后还是被分配在堆上? 求解这里问题,有点没搞懂


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2859 次点击  
加入收藏 微博
11 回复  |  直到 2022-07-13 00:16:44
jan-bar
jan-bar · #1 · 3年之前

看到一篇文章:Golang 元素值在经过通道传递时会被复制,这个复制是浅复制

如果通道传递值类型,实际就是完整的复制,发送端和接收端的数据不是同一个,所以不存在逃逸。

如果传递引用类型,由于只是引用被复制,例如指针地址被传过去了,这时候就存在逃逸。

coderyw
coderyw · #2 · 3年之前
jan-barjan-bar #1 回复

看到一篇文章:[Golang 元素值在经过通道传递时会被复制,这个复制是浅复制](https://blog.csdn.net/wohu1104/article/details/109608608) 如果通道传递值类型,实际就是完整的复制,发送端和接收端的数据不是同一个,所以不存在逃逸。 如果传递引用类型,由于只是引用被复制,例如指针地址被传过去了,这时候就存在逃逸。

这个我知道,问题是,就我了解的,没有逃逸的数据分配在函数栈上,然后等函数结束时候,就被一起释放。但是我这个函数,是一直运行,一直接收数据,问题就是数据会一直分配在栈上,不会导致栈空间不足吗?还是我对栈这里的理解有问题。。

jan-bar
jan-bar · #3 · 3年之前

我运行了如下代码,浏览器打开 http://127.0.0.1:4500/debug/pprof/ ,然后点击allocs,一直刷新页面看到Stack =栈分配没有增加,所以我觉得不会随着程序的运行导致栈空间占满。点击heap里面的Stack =也是没有增加的。

package main

import (
    "fmt"
    "net/http"
    _ "net/http/pprof"
    "time"
)

func main() {
    pc := make(chan t)
    go rcv(pc)
    go func() {
        for {
            tt := t{}
            pc <- tt
            time.Sleep(time.Second)
        }
    }()
    _ = http.ListenAndServe(":4500", nil)
}

type t struct {
    A int
}

func rcv(pc chan t) {
    for {
        p := <-pc
        p.A = 1
        fmt.Printf("p: %p\n", &p)
    }
}
etog
etog · #4 · 3年之前

在for循环里面,变量始终是同一个。

比如 p := <-pc 里面的 p 在for循环里面始终是同一个栈空间,如果没有其它变化内存并不会增长。

coderyw
coderyw · #5 · 3年之前
jan-barjan-bar #3 回复

我运行了如下代码,浏览器打开 http://127.0.0.1:4500/debug/pprof/ ,然后点击`allocs`,一直刷新页面看到`Stack =`栈分配没有增加,所以我觉得不会随着程序的运行导致栈空间占满。点击`heap`里面的`Stack =`也是没有增加的。 ```go package main import ( "fmt" "net/http" _ "net/http/pprof" "time" ) func main() { pc := make(chan t) go rcv(pc) go func() { for { tt := t{} pc <- tt time.Sleep(time.Second) } }() _ = http.ListenAndServe(":4500", nil) } type t struct { A int } func rcv(pc chan t) { for { p := <-pc p.A = 1 fmt.Printf("p: %p\n", &p) } } ```

我自己也实践了一下,我跑了十几分钟,使用了指针的channel和非指针的channel,但是看了trace的gc图,感觉差不多。 image.png

上面是参数是指针的

image.png 这个是非指针的

coderyw
coderyw · #6 · 3年之前
etogetog #4 回复

在for循环里面,变量始终是同一个。 比如 `p := <-pc` 里面的 `p` 在for循环里面始终是同一个栈空间,如果没有其它变化内存并不会增长。

我草了,好像是这样的。类似于 for v:=range channel

jan-bar
jan-bar · #7 · 3年之前
coderywcoderyw #6 回复

#4楼 @etog 我草了,好像是这样的。类似于 `for v:=range channel`

一语惊醒梦中人。

jan-bar
jan-bar · #8 · 3年之前
etogetog #4 回复

在for循环里面,变量始终是同一个。 比如 `p := <-pc` 里面的 `p` 在for循环里面始终是同一个栈空间,如果没有其它变化内存并不会增长。

我打印了变量地址,貌似每次都是新生成的,不是同一个吧。

lysShub
lysShub · #9 · 3年之前

p的地址就一个每次循环值不同, 就行for range一样

GO_go_GO1
GO_go_GO1 · #10 · 3年之前

p 在执行完即释放 不会栈溢出

slclub
slclub · #11 · 3年之前
coderywcoderyw #2 回复

#1楼 @jan-bar 这个我知道,问题是,就我了解的,没有逃逸的数据分配在函数栈上,然后等函数结束时候,就被一起释放。但是我这个函数,是一直运行,一直接收数据,问题就是数据会一直分配在栈上,不会导致栈空间不足吗?还是我对栈这里的理解有问题。。

每个goroutine 都有各自的栈空间。单单一个就更是占不了什么空间。

如果p 过大还是会分在堆上的。

各自的的routine 又是抢占式的。也只能并行cpu 线程数个数来运行。

栈溢出一般至少需要很多M。具体的忘记怎么算来。

添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传