I’ve started to play a bit with Go, and a question quickly came to my mind : how can we optimize the size of the binaries ?

Let’s consider the usual “Hello World” :

package main

import (
  "fmt"
)

func main() {
  fmt.Println("Hello world")
}

And then follow what we can find on the first page of Google when looking for “how to build Go program” (yes, because it’s exactly why we ended-up during years having SQL injections everywhere in PHP scripts, because of people who were just copy/pasting the first Google/SO results without thinking a bit further).

$ go build -o /tmp/test && ls -l /tmp/test
-rwxr-xr-x  1 adedommelin  staff   2023776 Mar 21 11:03 /tmp/test

1.9M for a stupid “Hello World” … sounds like we probably have room for improvement !

So, what can we do to reduce the size ? First, let’s remove debug information / DWARF & symbol tables with some linker flags :

$ go build -ldflags "-s -w" -o /tmp/test && ls -l /tmp/test
-rwxr-xr-x  1 adedommelin  staff   1524064 Mar 21 11:05 /tmp/test

~25% size reduction with simple linker flags, not too bad !

Let’s try to push a bit further by looking at upx which is a “a free, portable, extendable, high-performance executable packer for several executable formats” (cf: https://upx.github.io/) and run it against our freshly built hello world :

$ upx /tmp/test
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2020
UPX 3.96        Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 23rd 2020

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
   1524064 ->    610320   40.05%   macho/amd64   test

Packed 1 file.

$ ls -l /tmp/test
-rwxr-xr-x  1 adedommelin  staff  610320 Mar 21 11:06 /tmp/test

That’s almost a 70% reduction in size compared to “standard” go build !

Note : there’s for sure a slight overhead due to unpacking at startup which may worth being measured, but for most cases it should definitely not be an issue.