为什么我要让一个导出的指针变量指向一个未导出的变量,在包级别上?

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

Why would I let an exported pointer variable point to an unexported variable, at package level?

问题

以标准库net/http中的DefaultClient为例,它的定义如下:

var DefaultClient = &Client{}

然而,DefaultServeMux的定义如下:

var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux

为同一个对象定义两个变量的目的是什么?相比于简单的var DefaultServeMux = &ServeMux{},它有什么好处?

英文:

Taking the standard library net/http for example, the DefaultClient is defined as:

var DefaultClient = &Client{}

However, the DefaultServeMux is defined as:

var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux

What's the point of defining two variables for one object? What benefit does it bring over a simple var DefaultServeMux = &ServeMux{}?

答案1

得分: 3

DefaultServeMux部分是由Brad Fitzpatrick进行的优化。我将复制并重新格式化他与Matthew DempskyCL的评论中的对话:

Matthew Dempsky:

> 为了确保我理解正确,问题是这样做
>
> go > var x = newFoo() >
> 即使x未使用,也需要调用newFoo()。

Brad Fitzpatrick:

> 是的,这会生成:
>
> go > var x *T > > func init() { > x = newFoo() > } >
>
> 而且链接器似乎永远不会删除init块,即使它们只分配给其他地方只读的东西。因为newFoo也可能有副作用?

Matthew Dempsky:

> 而
>
> go > var x = &y > var y foo >
>
> 跳过了显式的初始化代码。所以如果x在其他地方未使用,它可以与所有类型foo相关的代码一起被消除?

Brad Fitzpatrick:

> 是的


我已经检查了从最新源代码(提交4f4a9c7fff)编译的Go二进制文件的大小,并发现该提交的更改有很大帮助,但var DefaultServeMux = &ServeMux{}稍微好一些。请参见下表:

实现方式 Go二进制文件大小
var DefaultServeMux = NewServeMux() 15870476
var DefaultServeMux = &defaultServeMux; var defaultServeMux ServeMux 15864704 (-5772)
var DefaultServeMux = &ServeMux{} 15864696 (-5780)

更新

我试图找出在创建该提交时(回到go1.7)var DefaultServeMux = &defaultServeMux; var defaultServeMux ServeMuxvar DefaultServeMux = &ServeMux{}之间的区别,但我无法在我的机器上编译go1.12,所以我放弃了(参见https://stackoverflow.com/a/76030876/1369400)。我发现从go1.7到现在(devel go1.21-4f4a9c7fff),这两种实现对于Go二进制文件的大小影响几乎相同。以下是结果(我的环境是go1.20.3 linux/amd64,我使用./make.bash编译二进制文件):

git标签 var DefaultServeMux = &defaultServeMux<br>var defaultServeMux ServeMux var DefaultServeMux = &ServeMux{} 差异
go1.7 9966320 9966320 0
go1.8 10081258 10081258 0
go1.9 10381836 10381836 0
go1.10 11320890 11320890 0
go1.11 13032188 13032188 0
go1.12 14576719 14576719 0
go1.13 15083862 15083862 0
go1.14 15323624 15323624 0
go1.15 14272775 14272775 0
go1.16 14068974 14068982 +8
go1.17 14022237 14022237 0
go1.18 14545517 14545517 0
go1.19 15302909 15302909 0
go1.20 15579106 15579106 0
4f4a9c7fff 15864704 15864696 -8
英文:

The DefaultServeMux part is an optimization done by Brad Fitzpatrick. I will copy and reformat the conversations between him and Matthew Dempsky from the comments on the CL:

Matthew Dempsky:

> To make sure I understand, the problem is that doing
>
>
&gt; var x = newFoo()
&gt;

> requires newFoo()
> to be called even if x is unused

Brad Fitzpatrick:

> Yes, that generates:
>
>
&gt; var x *T
&gt;
&gt; func init() {
&gt; x = newFoo()
&gt; }
&gt;

>
> And the linker doesn't seem to ever remove init blocks, even if they only assign to things which are otherwise only ever read. Because maybe newFoo has side-effects too?

Matthew Dempsky:

> whereas
>
>
&gt; var x = &amp;y
&gt; var y foo
&gt;

>
> skips the explicit initialization code. So if x is otherwise unused, it can be dead code eliminated along with all of type foo's related code?

Brad Fitzpatrick:

> Yup

I have checked the size of the go binary compiled from the latest source code (commit 4f4a9c7fff), and I found that the change of that commit helps a lot, but var DefaultServeMux = &amp;ServeMux{} is a little better. See the table below:

Implementation size of the go binary
var DefaultServeMux = NewServeMux() 15870476
var DefaultServeMux = &defaultServeMux; var defaultServeMux ServeMux 15864704 (-5772)
var DefaultServeMux = &ServeMux{} 15864696 (-5780)

Update:

I tried to find out the difference between var DefaultServeMux = &amp;defaultServeMux; var defaultServeMux ServeMux and var DefaultServeMux = &amp;ServeMux{} at the time when that commit was created (it's back to go1.7). <del>But I failed to compile go1.12 on my machine, so I gave up</del> (see https://stackoverflow.com/a/76030876/1369400). What I have found is from go1.7 till now (devel go1.21-4f4a9c7fff), the two implementations are almost the same regarding the affect to the size of the go binary. Here is the result (my environment is go1.20.3 linux/amd64 and I compiled the binary with ./make.bash):

git tag var DefaultServeMux = &defaultServeMux<br>var defaultServeMux ServeMux var DefaultServeMux = &ServeMux{} delta
go1.7 9966320 9966320 0
go1.8 10081258 10081258 0
go1.9 10381836 10381836 0
go1.10 11320890 11320890 0
go1.11 13032188 13032188 0
go1.12 14576719 14576719 0
go1.13 15083862 15083862 0
go1.14 15323624 15323624 0
go1.15 14272775 14272775 0
go1.16 14068974 14068982 +8
go1.17 14022237 14022237 0
go1.18 14545517 14545517 0
go1.19 15302909 15302909 0
go1.20 15579106 15579106 0
4f4a9c7fff 15864704 15864696 -8

huangapple
  • 本文由 发表于 2023年4月7日 13:48:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/75955762.html
匿名

发表评论

匿名网友

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

确定