结论是:
为了真正了解导入某些包的额外重量,还必须查看该 pkg 的所有子依赖项。
这是完全可以理解的。我的问题是,
无论如何,我可以知道每个组件在我编译的二进制文件、Go运行时、依赖项和子依赖项包以及我自己的代码中占用了多少空间。
我依稀记得以前读过类似的东西(当 go 增强它的链接器时)。
如果以前从未有过这样的讨论,那么 go 甚至 c 链接器有什么方法可以查看我编译的二进制文件,并揭示一些我可以进一步解析的内容?
二进制文件将包含调试符号,我们可以使用它们来计算每个包占用了多少空间。
我编写了一个基本程序来执行此操作,因为我不知道有任何工具可以执行此操作:
package main
import (
"debug/elf"
"fmt"
"os"
"runtime"
"sort"
"strings"
"github.com/go-delve/delve/pkg/proc"
)
func main() {
// Use delve to decode the DWARF section
binInfo := proc.NewBinaryInfo(runtime.GOOS, runtime.GOARCH)
err := binInfo.AddImage(os.Args[1], 0)
if err != nil {
panic(err)
}
// Make a list of unique packages
pkgs := make([]string, 0, len(binInfo.PackageMap))
for _, fullPkgs := range binInfo.PackageMap {
for _, fullPkg := range fullPkgs {
exists := false
for _, pkg := range pkgs {
if fullPkg == pkg {
exists = true
break
}
}
if !exists {
pkgs = append(pkgs, fullPkg)
}
}
}
// Sort them for a nice output
sort.Strings(pkgs)
// Parse the ELF file ourselfs
elfFile, err := elf.Open(os.Args[1])
if err != nil {
panic(err)
}
// Get the symbol table
symbols, err := elfFile.Symbols()
if err != nil {
panic(err)
}
usage := make(map[string]map[string]int)
for _, sym := range symbols {
if sym.Section == elf.SHN_UNDEF || sym.Section >= elf.SectionIndex(len(elfFile.Sections)) {
continue
}
sectionName := elfFile.Sections[sym.Section].Name
symPkg := ""
for _, pkg := range pkgs {
if strings.HasPrefix(sym.Name, pkg) {
symPkg = pkg
break
}
}
// Symbol doesn't belong to a known package
if symPkg == "" {
continue
}
pkgStats := usage[symPkg]
if pkgStats == nil {
pkgStats = make(map[string]int)
}
pkgStats[sectionName] += int(sym.Size)
usage[symPkg] = pkgStats
}
for _, pkg := range pkgs {
sections, exists := usage[pkg]
if !exists {
continue
}
fmt.Printf("%s:\n", pkg)
for section, size := range sections {
fmt.Printf("%15s: %8d bytes\n", section, size)
}
fmt.Println()
}
}
现在实际使用的空间被划分为多个部分(.text 用于代码,.bss 用于零初始化数据,.data 用于全局变量,等等)。此示例列出了每个部分的大小,但如果您愿意,您可以修改代码以获取总数。
这是它从自己的二进制文件生成的输出:
bufio:
.text: 12733 bytes
.noptrdata: 64 bytes
.bss: 176 bytes
.rodata: 72 bytes
bytes:
.bss: 48 bytes
.rodata: 64 bytes
.text: 12617 bytes
.noptrdata: 320 bytes
compress/flate:
.text: 20385 bytes
.noptrdata: 248 bytes
.bss: 2112 bytes
.noptrbss: 12 bytes
.rodata: 48 bytes
compress/zlib:
.text: 4138 bytes
.noptrdata: 96 bytes
.bss: 48 bytes
container/list:
.text: 4016 bytes
context:
.text: 387 bytes
.noptrdata: 72 bytes
.bss: 40 bytes
crypto:
.text: 20982 bytes
.noptrdata: 416 bytes
.bss: 96 bytes
.rodata: 58 bytes
.noptrbss: 3 bytes
debug/dwarf:
.rodata: 1088 bytes
.text: 113878 bytes
.noptrdata: 247 bytes
.bss: 64 bytes
debug/elf:
.rodata: 168 bytes
.text: 36557 bytes
.noptrdata: 112 bytes
.data: 5160 bytes
.bss: 16 bytes
debug/macho:
.text: 22980 bytes
.noptrdata: 96 bytes
.data: 456 bytes
.rodata: 80 bytes
debug/pe:
.text: 26004 bytes
.noptrdata: 96 bytes
.rodata: 288 bytes
encoding/base64:
.bss: 32 bytes
.rodata: 48 bytes
.text: 846 bytes
.noptrdata: 56 bytes
encoding/binary:
.text: 27108 bytes
.noptrdata: 72 bytes
.bss: 56 bytes
.rodata: 136 bytes
encoding/hex:
.bss: 16 bytes
.text: 288 bytes
.noptrdata: 64 bytes
encoding/json:
.rodata: 108 bytes
.text: 2930 bytes
.noptrdata: 128 bytes
.bss: 80 bytes
errors:
.rodata: 48 bytes
.text: 744 bytes
.noptrdata: 40 bytes
.bss: 16 bytes
fmt:
.text: 72010 bytes
.noptrdata: 136 bytes
.data: 104 bytes
.bss: 32 bytes
.rodata: 720 bytes
github.com/cilium/ebpf:
.text: 170860 bytes
.noptrdata: 1405 bytes
.bss: 608 bytes
.rodata: 3971 bytes
.data: 16 bytes
.noptrbss: 8 bytes
github.com/go-delve/delve/pkg/dwarf/frame:
.text: 18304 bytes
.noptrdata: 80 bytes
.bss: 8 bytes
.rodata: 211 bytes
github.com/go-delve/delve/pkg/dwarf/godwarf:
.text: 40431 bytes
.noptrdata: 144 bytes
.rodata: 352 bytes
github.com/go-delve/delve/pkg/dwarf/line:
.bss: 48 bytes
.rodata: 160 bytes
.text: 24069 bytes
.noptrdata: 96 bytes
github.com/go-delve/delve/pkg/dwarf/loclist:
.noptrdata: 64 bytes
.rodata: 64 bytes
.text: 4538 bytes
github.com/go-delve/delve/pkg/dwarf/op:
.text: 31142 bytes
.noptrdata: 80 bytes
.bss: 72 bytes
.rodata: 5313 bytes
github.com/go-delve/delve/pkg/dwarf/reader:
.noptrdata: 72 bytes
.bss: 16 bytes
.rodata: 24 bytes
.text: 8037 bytes
github.com/go-delve/delve/pkg/dwarf/regnum:
.bss: 40 bytes
.rodata: 2760 bytes
.text: 3943 bytes
.noptrdata: 48 bytes
github.com/go-delve/delve/pkg/dwarf/util:
.text: 4028 bytes
.noptrdata: 64 bytes
.rodata: 96 bytes
github.com/go-delve/delve/pkg/elfwriter:
.text: 3394 bytes
.noptrdata: 48 bytes
.rodata: 48 bytes
github.com/go-delve/delve/pkg/goversion:
.noptrdata: 104 bytes
.bss: 64 bytes
.rodata: 160 bytes
.text: 4415 bytes
github.com/go-delve/delve/pkg/logflags:
.bss: 32 bytes
.rodata: 40 bytes
.text: 2610 bytes
.noptrdata: 136 bytes
.noptrbss: 3 bytes
github.com/go-delve/delve/pkg/proc:
.text: 432477 bytes
.noptrdata: 718 bytes
.data: 1448 bytes
.bss: 592 bytes
.rodata: 10106 bytes
github.com/go-delve/delve/pkg/version:
.text: 1509 bytes
.noptrdata: 72 bytes
.data: 112 bytes
.rodata: 40 bytes
github.com/hashicorp/golang-lru/simplelru:
.text: 3911 bytes
.noptrdata: 32 bytes
.rodata: 160 bytes
github.com/sirupsen/logrus:
.noptrbss: 20 bytes
.rodata: 696 bytes
.text: 40175 bytes
.noptrdata: 204 bytes
.data: 64 bytes
.bss: 56 bytes
go/ast:
.text: 24407 bytes
.noptrdata: 104 bytes
.data: 112 bytes
.rodata: 120 bytes
go/constant:
.bss: 8 bytes
.rodata: 824 bytes
.text: 33910 bytes
.noptrdata: 88 bytes
go/parser:
.rodata: 1808 bytes
.text: 78751 bytes
.noptrdata: 136 bytes
.bss: 32 bytes
go/printer:
.text: 77202 bytes
.noptrdata: 113 bytes
.data: 24 bytes
.rodata: 1504 bytes
go/scanner:
.rodata: 240 bytes
.text: 18594 bytes
.noptrdata: 93 bytes
.data: 24 bytes
go/token:
.noptrdata: 72 bytes
.data: 1376 bytes
.bss: 8 bytes
.rodata: 192 bytes
.text: 7154 bytes
golang.org/x/arch/arm64/arm64asm:
.rodata: 856 bytes
.text: 116428 bytes
.noptrdata: 80 bytes
.bss: 80 bytes
.data: 46128 bytes
golang.org/x/arch/x86/x86asm:
.noptrdata: 29125 bytes
.bss: 112 bytes
.data: 20928 bytes
.rodata: 1252 bytes
.text: 76721 bytes
golang.org/x/sys/unix:
.text: 1800 bytes
.noptrdata: 128 bytes
.rodata: 70 bytes
.data: 80 bytes
hash/adler32:
.text: 1013 bytes
.noptrdata: 40 bytes
internal/bytealg:
.rodata: 56 bytes
.noptrbss: 8 bytes
.text: 1462 bytes
.noptrdata: 32 bytes
internal/cpu:
.rodata: 500 bytes
.noptrbss: 416 bytes
.noptrdata: 8 bytes
.bss: 24 bytes
.text: 3017 bytes
internal/fmtsort:
.text: 7443 bytes
.noptrdata: 40 bytes
.rodata: 40 bytes
internal/oserror:
.text: 500 bytes
.noptrdata: 40 bytes
.bss: 80 bytes
internal/poll:
.text: 31565 bytes
.rodata: 192 bytes
.noptrdata: 112 bytes
.data: 96 bytes
.bss: 64 bytes
.noptrbss: 12 bytes
internal/reflectlite:
.text: 13761 bytes
.noptrdata: 32 bytes
.data: 456 bytes
.bss: 24 bytes
.rodata: 496 bytes
internal/syscall/unix:
.rodata: 72 bytes
.text: 708 bytes
.noptrdata: 40 bytes
.noptrbss: 4 bytes
internal/testlog:
.text: 827 bytes
.noptrdata: 32 bytes
.noptrbss: 12 bytes
.bss: 16 bytes
.rodata: 72 bytes
io:
.noptrdata: 240 bytes
.bss: 272 bytes
.data: 56 bytes
.noptrbss: 0 bytes
.rodata: 128 bytes
.text: 10824 bytes
log:
.text: 188 bytes
.noptrdata: 80 bytes
.bss: 8 bytes
main:
.text: 3002 bytes
.noptrdata: 80 bytes
.rodata: 104 bytes
math:
.data: 136 bytes
.bss: 2672 bytes
.text: 184385 bytes
.noptrdata: 10211 bytes
.rodata: 2076 bytes
.noptrbss: 2 bytes
net:
.text: 24417 bytes
.noptrdata: 236 bytes
.data: 240 bytes
.bss: 584 bytes
.noptrbss: 16 bytes
.rodata: 48 bytes
os:
.bss: 264 bytes
.data: 32 bytes
.rodata: 352 bytes
.text: 46276 bytes
.noptrdata: 296 bytes
.noptrbss: 1 bytes
path:
.text: 9378 bytes
.noptrdata: 136 bytes
.bss: 48 bytes
.rodata: 48 bytes
reflect:
.noptrbss: 1 bytes
.text: 97417 bytes
.noptrdata: 72 bytes
.rodata: 1728 bytes
.data: 456 bytes
.bss: 160 bytes
regexp:
.rodata: 968 bytes
.text: 126451 bytes
.noptrdata: 558 bytes
.bss: 296 bytes
.noptrbss: 16 bytes
.data: 816 bytes
runtime:
.noptrbss: 20487 bytes
.data: 8520 bytes
.bss: 184836 bytes
.tbss: 8 bytes
.typelink: 9020 bytes
.gopclntab: 0 bytes
.text: 408713 bytes
.noptrdata: 4347 bytes
.rodata: 23102 bytes
.itablink: 2952 bytes
sort:
.text: 13055 bytes
.noptrdata: 32 bytes
.data: 16 bytes
.rodata: 24 bytes
strconv:
.text: 45928 bytes
.noptrdata: 17015 bytes
.data: 1680 bytes
.bss: 32 bytes
.rodata: 144 bytes
strings:
.text: 21070 bytes
.noptrdata: 320 bytes
.rodata: 168 bytes
sync:
.rodata: 476 bytes
.noptrdata: 56 bytes
.bss: 56 bytes
.noptrbss: 8 bytes
.text: 14288 bytes
syscall:
.noptrdata: 127 bytes
.rodata: 978 bytes
.noptrbss: 76 bytes
.bss: 264 bytes
.data: 2720 bytes
.text: 33728 bytes
text/tabwriter:
.data: 96 bytes
.rodata: 88 bytes
.text: 8002 bytes
.noptrdata: 46 bytes
text/template:
.text: 166284 bytes
.noptrdata: 316 bytes
.noptrbss: 8 bytes
.bss: 176 bytes
.data: 376 bytes
.rodata: 3152 bytes
time:
.text: 83290 bytes
.noptrdata: 164 bytes
.data: 912 bytes
.bss: 208 bytes
.noptrbss: 20 bytes
.rodata: 832 bytes
unicode:
.noptrdata: 50398 bytes
.data: 15248 bytes
.bss: 40 bytes
.noptrbss: 0 bytes
.text: 27198 bytes
注意这个程序并不完美,它只能在 Linux/Mac 上运行,因为它依赖于 ELF。我确信您可以对 Windows PE 文件执行类似的操作,但这会花费我很多时间。
这个程序还忽略了 go 运行时的某些部分,但我猜这对你来说并不是最重要的。
您可以运行
nm
来获取二进制文件中所有对象的大小。
例如:nm -S /usr/local/go/bin/gofmt
。第二列是尺寸。
0000000000468700 000000000000011c T unicode/utf8.DecodeLastRuneInString
0000000000468240 00000000000001a6 T unicode/utf8.DecodeRune
0000000000468400 00000000000001a6 T unicode/utf8.DecodeRuneInString
0000000000468820 0000000000000157 T unicode/utf8.EncodeRune
我为此创建了一个工具,其中包含用于可视化的树形图。请参阅https://github.com/Zxilly/go-size-analyzer。
运行
gsa --web binary
结果,得到类似的图表