Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

golang 返回值还是指针? #61

Open
eastany opened this issue Apr 25, 2016 · 0 comments
Open

golang 返回值还是指针? #61

eastany opened this issue Apr 25, 2016 · 0 comments

Comments

@eastany
Copy link
Owner

eastany commented Apr 25, 2016

偶然看到的,摘录下:
Go 1.4 开始,引入 write barrier。对指针的赋值,如果不确定新的指针一定不是堆上的,
那么赋值需要经过 write barrier。

write barrier 是一种和 GC 同步的方式。由于 GC 和用户程序并发执行,所以 GC 需要
知道用户把一个指针存放到另外一个位置(这样可能导致原先访问不到的对象变得可以
被访问了)。

Go 1.5 优化了 write barrier,不再无条件调用 write barrier 函数,而是只在 GC 使能
write barrier 的时候调用函数,所以在 Go 1.5 下面,三个函数几乎是一样快的:

Benchmark1 2000000000 0.54 ns/op
Benchmark2 2000000000 0.80 ns/op
Benchmark3 2000000000 0.53 ns/op

同时我还有一个疑问就是一般函数返回对象的时候,既可以返回值也可以返回指针的时候,您是多大的对象用值返回,多大以上的对象用指针返回
这个问题经常被问到。但是却很难做出一个简单的解答。

尺寸并不是太大的问题(一般用多少个 cache line 大小来衡量),更多的是得
考虑更全局的因素。

  1. 是否必须使用指针?或者一定要使用值?
    比如,凡是带有 sync.Mutex, sync.Cond 之类的值的(非指针),必须使用指针。
    类似的,有些对象是不能复制的,比如带有全局状态的。

当然,也有例子一定要使用值返回,最普遍的情况是你不希望用户能修改数据源
或者返回值是某种形式的快照。例子是 http://tip.golang.org/pkg/database/sql/#DB.Stats
http://golang.org/pkg/crypto/tls/#Conn.ConnectionState (这个例子很典型,
注意到 ConnectionState 结构体非常大,在 amd64 上是 168 字节。它明确地体现
了长度不是唯一的决定因素;如果你仔细考虑下面第三条的建议,也会意识到这个
结构体是用值返回的绝佳例子:一般你调用这个往往是为了查看连接的瞬时状态,
由于这个状态是某种形式的快照,所以这个对象本身没有太大可能会需要很长的
生存期。)

  1. 对象的方法使用指针接收者还是值接收者?
    如果对象的方法大多使用指针接收者,且较复杂(换句话说会导致对象 escape 的),
    那你返回值就没多大意义。

预期会被作为接口保存或者使用的对象,没必要用值返回,因为通过接口调用方法
一定会导致对象被分配到堆上。

  1. 对象的预期生存期是长还是短?是临时使用还是预期会放到堆上?
    对于预期会放到堆上的长生存期(或生存期不确定)的对象,使用值返回意义不大。
    相反,如果对象基本上临时使用,且体积不太大(这里界限比较模糊,参看前面的
    crypto/tls.ConnectionState 的例子),那么值返回就比较合理。

实际上,仔细想想,会发现上面第二条其实是很难使用的:因为很多情况下,方法
既可以接收值,也可以接收指针,同时返回副作用也既可以通过返回新值的方式,
也可以采用直接改写对象本身的方式。

所以,设计代码的时候更多的是 1 和 3,尤其是 3,你应该仔细考虑你返回对象的
预期生存期——换句话说,这个对象放在堆栈上是否有很大的意义。

最后说尺寸的限制,在 x86 上执行的代码,其实 1KB 以下都可以很快速的复制,
但是通常不建议频繁地在堆栈上传递那么大的对象(不必要的扩大 goroutine 的堆
栈尺寸不利于创建很多 goroutine 且 GC 不喜欢大堆栈),一般来说一两个 cache
line 尺寸以内的用值传递是可以的,小于一个 cache line 的用值/指针基本没啥性能
影响。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant