如何在编译时删除未使用的代码?

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

How to remove unused code at compile time?

问题

我们已经构建了一个Go包,被许多人使用。

它使用标准的import ("package-name")方法进行导入。

然而,在编译时,我们所有的实用程序,包括非常小的实用程序,最终都变成了非常大的二进制文件。

我们提取了实用程序中的所有字符串,并发现整个包被编译到每个实用程序中,包括那些实用程序没有使用的函数。

编辑1:

感谢回答这个问题的人们。

这是我们所看到的情况:

main.go

package main

import "play/subplay"

func main() {
    subplay.A()
}

play/subplay.go

package subplay

func A() {
	fmt.Printf("this is function A()")
}

func B() {
	fmt.Printf("secret string")
}

函数B()从未被调用。然而,在构建二进制文件后,我们发现字符串"secret string"出现在main.exe中。

我们如何在编译时从Go程序中删除未使用的代码?

英文:

We've built a Go package that is used by many of us.

It's imported using the standard import ("package-name") method.

At compile time though, all of our utilities, including the very small ones, end up as very large binaries.

We've extracted all the strings in the utilities and discovered that the entire package is being compiled into each and every utility. Including functions that are not being used by those utilities.

EDIT 1:

Thank you to the people who are responding to this question.

Here's what we are seeing:

main.go

package main

import "play/subplay"

func main() {
    subplay.A()
}

play/subplay.go

package subplay

func A() {
	fmt.Printf("this is function A()")
}

func B() {
	fmt.Printf("secret string")
}

Function B() is never called. Yet, after building the binary, we find the string "secret string" into main.exe.

How can we remove unused code from Go programs at compile time?

答案1

得分: 21

编译器已经实现了这一点。所有的代码都会被放在包文件(.a)中,但在可执行二进制文件中,Go工具并不会包含导入包中的所有内容,只会包含所需的内容(或者更准确地说:它会排除那些可以证明是不可达的内容)。

请参考可能的重复问题Splitting client/server code

需要注意的一点是:如果一个导入的包(你只想包含你引用的内容)导入了其他包,那么这个过程必须递归进行。

例如,如果你导入了这个包:

package subplay

func A() {}

并且没有调用它的任何内容:

package main

import _ "play/subplay"

func main() {
}

结果二进制文件(Go 1.8,linux,amd64)的大小将为960,134字节(大约1MB)。

如果你将subplay改为导入net/http

package subplay

import _ "net/http"

func A() {}

但仍然没有调用net/http中的任何内容,结果将是:5,370,935字节(大约5MB)!(注意,net/http还导入了39个其他包!)

现在,如果你开始使用net/http中的内容:

package subplay

import "net/http"

func A() {
	http.ListenAndServe("", nil)
}

但在main包中仍然没有调用subplay.A(),可执行二进制文件的大小不会改变:仍然是5,370,935字节

当你将main包改为调用subplay.A()时:

package main

import "play/subplay"

func main() {
    subplay.A()
}

结果二进制文件增长为:5,877,919字节

如果你将http.ListenAndServe()改为http.ListenAndServeTLS()

func A() {
	http.ListenAndServeTLS("", "", "", nil)
}

结果二进制文件的大小为:6,041,535字节

正如你所看到的,编译进可执行二进制文件中的内容取决于你调用/使用了哪些导入包中的内容

此外,不要忘记Go是一种静态链接语言,结果的可执行二进制文件必须包含它所需的一切。请参考相关问题:https://stackoverflow.com/questions/28576173/reason-for-huge-size-of-compiled-executable-of-go/28579099#28579099

英文:

The compiler already does this. All code ends up in package files (.a), but in the executable binary the Go tool does not include everything from imported packages, only what is needed (or more precisely: it excludes things that it can prove unreachable).

See possible duplicate Splitting client/server code.

One thing to note here: if an imported package (from which you only want to include things you refer to) imports other packages, this has to be done recursively of course.

For example if you import this package:

package subplay

func A() {}

And call nothing from it:

package main

import _ "play/subplay"

func main() {
}

The result binary (Go 1.8, linux, amd64) will be 960,134 bytes (roughly 1 MB).

If you change subplay to import net/http:

package subplay

import _ "net/http"

func A() {}

But still don't call anything from net/http, the result will be: 5,370,935 bytes (roughly 5 MB)! (Note that net/http also imports 39 other packages!)

Now if you go ahead and start using things from net/http:

package subplay

import "net/http"

func A() {
	http.ListenAndServe("", nil)
}

But in the main package you still don't call subplay.A(), the size of the executable binary does not change: remains 5,370,935 bytes!

And when you change the main package to call subplay.A():

package main

import "play/subplay"

func main() {
    subplay.A()
}

The result binary grows: 5,877,919 bytes!

If you change http.ListenAndServe() to http.ListenAndServeTLS():

func A() {
	http.ListenAndServeTLS("", "", "", nil)
}

Result binary is: 6,041,535 bytes.

As you can see, what gets compiled into the executable binary does depend on what you call / use from the imported packages.

Also don't forget that Go is a statically linked language, the result executable binary has to include everything it needs. See related question: https://stackoverflow.com/questions/28576173/reason-for-huge-size-of-compiled-executable-of-go/28579099#28579099

答案2

得分: -1

你可以将你的包转换成一个插件,如果你不介意一些限制的话(插件需要 Go 1.8,并且目前只能在 Linux 上工作)。

[编辑] 由于你使用的是 Windows,这对你来说不是一个选择。

英文:

You can turn your package into a plugin, if you don’t mind the restrictions (plugins need Go 1.8 and currently only work on Linux).

[edit] Since you are using Windows, this is not an option for you.

huangapple
  • 本文由 发表于 2017年3月16日 13:25:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/42825926.html
匿名

发表评论

匿名网友

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

确定