英文:
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 Dempsky在CL的评论中的对话:
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 ServeMux
和var 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
>
>
> var x = newFoo()
>
> requires newFoo()
> to be called even if x is unused
Brad Fitzpatrick:
> Yes, that generates:
>
>
> var x *T
>
> func init() {
> x = newFoo()
> }
>
>
> 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
>
>
> var x = &y
> var y foo
>
>
> 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 = &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 = &defaultServeMux; var defaultServeMux ServeMux
and var DefaultServeMux = &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 |
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论