Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Following up with How do I check the size of a Go project?

The conclusion was:

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.

That's totally understandable. My question is,

Is there anyway that I can know how much space each component is taking in my compiled binary, the Go runtime, the dependencies and sub-dependencies packages, and my own code.

I vaguely remember reading something like this before (when go enhanced its linker maybe).
If there has never been such discussion before, then is there any way the go or even c linker can look into the my compiled binary, and reveal something that I can further parse myself?

like image 314
xpt Avatar asked Oct 20 '25 10:10

xpt


1 Answers

The binary will contain debug symbols which we can use to figure out how many space each package takes up.

I wrote a basic program to do this since I don't know of any tool that does this:

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

Now the actual space used is divided over multiple section(.text for code, .bss for zero initialized data, .data for global vars, ect.). This example lists the size per section, but you can modify the code to get the total if that is what you prefer.

Here is the outputs it generates from its own binary:

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

Note this program isn't perfect, it only works on Linux/Mac since it relies on ELF. I am sure you can do a similar thing for Windows PE files, but that would have taken me to much time.

Also this program ignores some parts of the go runtime, but I am guessing that is not the most important to you.

like image 63
Dylan Reimerink Avatar answered Oct 22 '25 04:10

Dylan Reimerink



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!