Reason for huge size of compiled executable of Go

GoExecutable

Go Problem Overview


I complied a hello world Go program which generated native executable on my linux machine. But I was surprised to see the size of the simple Hello world Go program, it was 1.9MB !

Why is it that the executable of such a simple program in Go is so huge?

Go Solutions


Solution 1 - Go

This exact question appears in the official FAQ: Why is my trivial program such a large binary?

Quoting the answer:

>The linkers in the gc tool chain (5l, 6l, and 8l) do static linking. All Go binaries therefore include the Go run-time, along with the run-time type information necessary to support dynamic type checks, reflection, and even panic-time stack traces. > > A simple C "hello, world" program compiled and linked statically using gcc on Linux is around 750 kB, including an implementation of printf. An equivalent Go program using fmt.Printf is around 1.9 MB, but that includes more powerful run-time support and type information.

So the native executable of your Hello World is 1.9 MB because it contains a runtime which provides garbage collection, reflection and many other features (which your program might not really use, but it's there). And the implementation of the fmt package which you used to print the "Hello World" text (plus its dependencies).

Now try the following: add another fmt.Println("Hello World! Again") line to your program and compile it again. The result will not be 2x 1.9MB, but still just 1.9 MB! Yes, because all the used libraries (fmt and its dependencies) and the runtime are already added to the executable (and so just a few more bytes will be added to print the 2nd text which you just added).

Solution 2 - Go

Consider the following program:

package main

import "fmt"

func main() {
    fmt.Println("Hello World!")
}

If I build this on my Linux AMD64 machine (Go 1.9), like this:

$ go build
$ ls -la helloworld
-rwxr-xr-x 1 janf group 2029206 Sep 11 16:58 helloworld

I get a a binary that is about 2 Mb in size.

The reason for this (which has been explained in other answers) is that we are using the "fmt" package which is quite large, but the binary has also not been stripped and this means that the symbol table is still there. If we instead instruct the compiler to strip the binary, it will become much smaller:

$ go build -ldflags "-s -w"
$ ls -la helloworld
-rwxr-xr-x 1 janf group 1323616 Sep 11 17:01 helloworld

However, if we rewrite the program to use the builtin function print, instead of fmt.Println, like this:

package main

func main() {
    print("Hello World!\n")
}

And then compile it:

$ go build -ldflags "-s -w"
$ ls -la helloworld
-rwxr-xr-x 1 janf group 714176 Sep 11 17:06 helloworld

We end up with an even smaller binary. This is as small as we can get it without resorting to tricks like UPX-packing, so the overhead of the Go-runtime is roughly 700 Kb.

Solution 3 - Go

Note that the binary size issue is tracked by issue 6853 in the golang/go project.

For instance, commit a26c01a (for Go 1.4) cut hello world by 70kB:

> because we don't write those names into the symbol table.

Considering the compiler, assembler, linker, and runtime for 1.5 will be entirely in Go, you can expect further optimization.


Update 2016 Go 1.7: this has been optimized: see "Smaller Go 1.7 binaries".

But these day (April 2019), what takes the most place is runtime.pclntab.
See "Why are my Go executable files so large? Size visualization of Go executables using D3" from Raphael ‘kena’ Poss.

> It is not too well documented however this comment from the Go source code suggests its purpose: > > // A LineTable is a data structure mapping program counters to line numbers. > > The purpose of this data structure is to enable the Go runtime system to produce descriptive stack traces upon a crash or upon internal requests via the runtime.GetStack API. > > So it seems useful. But why is it so large? > > The URL https://golang.org/s/go12symtab hidden in the aforelinked source file redirects to a document that explains what happened between Go 1.0 and 1.2. To paraphrase: > > prior to 1.2, the Go linker was emitting a compressed line table, and the program would decompress it upon initialization at run-time. > > in Go 1.2, a decision was made to pre-expand the line table in the executable file into its final format suitable for direct use at run-time, without an additional decompression step. > > In other words, the Go team decided to make executable files larger to save up on initialization time. > > Also, looking at the data structure, it appears that its overall size in compiled binaries is super-linear in the number of functions in the program, in addition to how large each function is.

https://science.raphael.poss.name/go-executable-size-visualization-with-d3/size-demo-ss.png

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKarthic RaoView Question on Stackoverflow
Solution 1 - GoiczaView Answer on Stackoverflow
Solution 2 - GoJoppeView Answer on Stackoverflow
Solution 3 - GoVonCView Answer on Stackoverflow