常常有人用 go 与 c语言比较二进制的大小,而且举例 go 生成的二进制多么的大 c 生成的二进制多么的小,这点我是看不下去的。因为 go 官方的编译器生成的二进制是静态的,而 c 语言一般使用 gcc 生成 二进制,而 gcc 默认是动态的。用静态和动态比较是非常不公平的。如果实在需要比较的话,就静态对静态 动态对动态。
linux 可以使用 ldd
查看 可执行文件是否包含动态链
接下来就开始演示了。
编写一个 c 语言的 hello world 接下来会用到它 hello.c
# include <stdio.h>
int main() {
printf("Hello World!\n");
return 0;
}
再编写一个 go 语言的 hello World hello_fmt.go
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello World!")
}
再用 go 内置 的 print 编写一个 hello world hello_builtin.go
package main
func main() {
println("Hello World!") // 不建议在生产环境中使用
}
写入好后目录结构大概如下
[email protected]:~/code$ du -h *
4.0K hello_builtin.go
4.0K hello.c
4.0K hello_fmt.go
[email protected]:~/code$
写接下来编译它们,为了方便区分,我们在文件名前加上标识
gcc -o c_hello hello.c
go build -o go_hello_fmt hello_fmt.go
go build -o go_hello_builtin hello_builtin.go
编译好后 大小如下
[email protected]:~/code$ gcc -o c_hello hello.c
[email protected]:~/code$ go build -o go_hello_fmt hello_fmt.go
[email protected]:~/code$ go build -o go_hello_builtin hello_builtin.go
[email protected]:~/code$ du -h *
12K c_hello
1.1M go_hello_builtin
1.9M go_hello_fmt
4.0K hello_builtin.go
4.0K hello.c
4.0K hello_fmt.go
[email protected]:~/code$
是不是感觉 体积差别巨大,那是因为 go默认编译器 的二进制文件默认包含调试信息,而 gcc 生成的代码默认不包含调试信息。我们可以使用 -ldflags "-s -w"
删除它
go build -ldflags "-s -w" -o delDebug_go_hello_fmt hello_fmt.go
go build -ldflags "-s -w" -o delDebug_go_hello_builtin hello_builtin.go
编译好后大小如下
[email protected]:~/code$ go build -ldflags "-s -w" -o delDebug_go_hello_fmt hello_fmt.go
[email protected]:~/code$ go build -ldflags "-s -w" -o delDebug_go_hello_builtin hello_builtin.go
[email protected]:~/code$ du -h * 12K c_hello
744K delDebug_go_hello_builtin
1.3M delDebug_go_hello_fmt
1.1M go_hello_builtin
1.9M go_hello_fmt
4.0K hello_builtin.go
4.0K hello.c
4.0K hello_fmt.go
[email protected]:~/code$
卧槽!大小差距还是很大,别着急那是因为 go编译器默认是采用的静态键而 gcc 默认采用的是动态链,关于什么是动态键什么是静态链大家可以自行 google 一下这里就不细说了。 linux 下可以使用 ldd
命令查看 可执行文件是否是 动态链。
为了公平起见我们将 c 版的 hello world 转成静态的
gccgo -static -o static_c_hello hello.c
大小如下
[email protected]:~/code$ gcc -static -o static_c_hello hello.c
[email protected]:~/code$ du -h *
12K c_hello
744K delDebug_go_hello_builtin
1.3M delDebug_go_hello_fmt
1.1M go_hello_builtin
1.9M go_hello_fmt
4.0K hello_builtin.go
4.0K hello.c
4.0K hello_fmt.go
792K static_c_hello
[email protected]:~/code$
这下是不是就感觉差不多了,甚至 go 使用了内置关键字生成的代码还要小一点 (逃
为了动态比动态静态比静态我们将 go 的代码也使用 动态链的方式进行编译。
关注 go 语言代码的话动态链方式生成最简单的是使用 gccgo 当然go 默认编译器 gc 也支持动态链方式编译。这里为了简单演示就使用 gccgo 了。如果你编写的代码使用了新的语法的话建议使用 gc 而不是 gccgo 毕竟gc 是 go 语言官方维护的编译器关于 gccgo 请查看 https://golang.org/doc/install/gccgo
gccgo 请自行安装毕竟各个 linux 发行版关于 gccgo 的包名有一点微小的不同,不建议编译安装,建议使用发行版自带的包管理器进行安装。
debian 系安装 gccgo
sudo apt install gccgo
arch 安装 gccgo
sudo pacman -S gcc-go
其他发行版就不演示了,一般来说用包管理器搜索一下就出来了
gccgo 安装好后开始生成 二进制
gccgo -o gccgo_hello_fmt hello_fmt.go
gccgo -o gccgo_hello_builtin hello_builtin.go
以下是生成结果
[email protected]:~/code$ gccgo -o gccgo_hello_fmt hello_fmt.go
[email protected]:~/code$ gccgo -o gccgo_hello_builtin hello_builtin.go
[email protected]:~/code$ du -h *
12K c_hello
744K delDebug_go_hello_builtin
1.3M delDebug_go_hello_fmt
28K gccgo_hello_builtin
32K gccgo_hello_fmt
1.1M go_hello_builtin
1.9M go_hello_fmt
4.0K hello_builtin.go
4.0K hello.c
4.0K hello_fmt.go
792K static_c_hello
[email protected]:~/code$
是不是感觉小多了虽然还是比 c 大一倍左右不过也没有之前夸张了。
当然 gccgo 也可以生成静态链的代码这里我稍微演示一下
gccgo -static -o static_gccgo_hello_fmt hello_fmt.go
gccgo -static -o static_gccgo_hello_builtin hello_builtin.go
以下是输出的结果
[email protected]:~/code$ gccgo -static -o static_gccgo_hello_fmt hello_fmt.go
[email protected]:~/code$ gccgo -static -o static_gccgo_hello_builtin hello_builtin.go
[email protected]:~/code$ du -h *
12K c_hello
744K delDebug_go_hello_builtin
1.3M delDebug_go_hello_fmt
28K gccgo_hello_builtin
32K gccgo_hello_fmt
1.1M go_hello_builtin
1.9M go_hello_fmt
4.0K hello_builtin.go
4.0K hello.c
4.0K hello_fmt.go
792K static_c_hello
1.8M static_gccgo_hello_builtin
4.7M static_gccgo_hello_fmt
[email protected]:~/code$
这里生成静态链的 go 代码 相比较之下 gc 生成的代码要小一点
动态链和静态链,一般来说我比较倾向于静态链,因为静态链的移植性比较好 而动态链比较省空间,相同的库只存在一份,大家共同使用。
参考
除了上面这些方法你还可以删除连接信息不过并不能减少多少尺寸不过能让你的程序在发布后更安全
类似于这样go build -trimpath
可以和删除调试信息组合起来删除多余的信息
go build -trimpath -ldflags "-s -w"
关于 go 的二进制压缩可以参考我的另外一篇文章 upx 教程