如何检查链接到我的 Go 代码中的包的大小

How to check the size of packages linked into my Go code

跟进

结论是:

in order to get a true sense of how much extra weight importing certain packages, one has to look at all of the pkg's sub-dependencies as well.

这完全可以理解。我的问题是,

我是否可以知道 space 每个组件 在我编译的二进制文件、Go 运行时、依赖项和子依赖项包中占用了多少,和我自己的代码。

我依稀记得以前读过类似的东西(可能是在 go 增强其链接器时)。
如果以前从未有过这样的讨论,那么 go 甚至 c 链接器是否可以查看我编译的二进制文件,并揭示一些我可以进一步解析的东西?

二进制文件将包含调试符号,我们可以用它来计算每个包占用了多少space。

我编写了一个基本程序来执行此操作,因为我不知道有任何工具可以执行此操作:

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()
    }
}

现在实际使用的 space 分为多个部分(.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