Trimming go binaries of dpcmder
Tags: dpcmder, datapower, croz, go
Published: 2020-01-13 11:43:16

This weekend I spent some time thinking that (Golang built) binaries of [dpcmder](https://github.com/croz-ltd/dpcmder) are a bit too huge for my taste. Some would say that ~10 megs is not so much for today's world but ~10 megabytes seemed to me to be too much so I set out on a mission to find out if I can trim dpcmder size a bit.


After some googling, I found this nice ["shrink your go binaries" blog](https://blog.filippo.io/shrink-your-go-binaries-with-this-one-weird-trick/) which explains exactly what I needed to do to achieve my goal.

It boils down to one change in the current build process (remove debugging info & system table) and one additional step after the build (compress binaries).

## Removing debugging info & system table

Instead of using default go build command, for example:
`GOOS=linux GOARCH=amd64 go build -o release/dpcmder-linux-amd64 dpcmder.go`

You have to add additional ldflags to instruct the linker to remove all debugging info (-w) & symbol table (-s):
`GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o release/dpcmder-linux-amd64 dpcmder.go`

Actually, this research of ldflags brought me to one other issue on my dpcmder todo list - adding application version for each release. This caused me to use one more ldflag (-X) to change values of version variables:
`GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X 'github.com/croz-ltd/dpcmder/help.Version=$(git tag | tail -n1)' -X 'github.com/croz-ltd/dpcmder/help.Platform=linux/amd64' -X 'github.com/croz-ltd/dpcmder/help.BuildTime=$(git tag | tail -n1).$(date -u -Iseconds)'" -o release/dpcmder-linux-amd64 dpcmder.go`

## Compressing binaries

> [UPX](https://upx.github.io/) is a free, portable, extendable, high-performance executable packer for several executable formats.

After installing UPX it is easy to use it and for the best compression results I added the flag "--brute" mentioned in the blog above:
`upx --brute release/*`

```
$ upx --brute release/*
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2013
UPX 3.91        Markus Oberhumer, Laszlo Molnar & John Reiser   Sep 30th 2013

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
   7925760 ->   2538980   32.03%  linux/elf386   dpcmder-linux-386             
   9105408 ->   2686192   29.50%  linux/ElfAMD   dpcmder-linux-amd64           
   8421068 ->   2772992   32.93%    Mach/i386    dpcmder-mac-386               
   9653400 ->   2785280   28.85%   Mach/AMD64    dpcmder-mac-amd64             
   7698432 ->   2449408   31.82%    win32/pe     dpcmder-win-386.exe           
   8865792 ->   2597376   29.30%    win64/pe     dpcmder-win-amd64.exe         
   --------------------   ------   -----------   -----------
  51669860 ->  15830228   30.64%                 [ 6 files ]

Packed 6 files.
```
This step took quite a long time to complete but results were worth waiting. As you can see below from binary files update times, some binaries were compressed faster and some were compressed slower (compression of win/386 binaries lasted almost 30 minutes while compression of Linux binaries lasted for around 3 minutes).

```
$ stat -c "%.19z %n" release/*
2020-01-13 10:23:02 release/dpcmder-linux-386
2020-01-13 10:26:57 release/dpcmder-linux-amd64
2020-01-13 10:29:52 release/dpcmder-mac-386
2020-01-13 10:36:54 release/dpcmder-mac-amd64
2020-01-13 11:03:45 release/dpcmder-win-386.exe
2020-01-13 11:10:54 release/dpcmder-win-amd64.exe
```

Note that these 6 binaries could be compressed in parallel to achieve much better times on the modern-day multi-core machines.

## How much did we trimmed?

After the whole process, all 6 binaries are now much smaller. The total size of all binaries shrank from the original 65M to a more acceptable 16M. Sizes for all 6 binaries are given below.

### default go build without any -ldflags

```
$ ls -sh1 release/
total 65M
11M dpcmder-linux-386
12M dpcmder-linux-amd64
11M dpcmder-mac-386
12M dpcmder-mac-amd64
11M dpcmder-win-386.exe
12M dpcmder-win-amd64.exe
```

### go build with -ldflags="-s -w"

```
$ ls -sh1 release/
total 50M
7,6M dpcmder-linux-386
8,7M dpcmder-linux-amd64
8,1M dpcmder-mac-386
9,3M dpcmder-mac-amd64
7,4M dpcmder-win-386.exe
8,5M dpcmder-win-amd64.exe
```

### result of running UPX on go build (with -ldflags="-s -w")

```
$ ls -sh1 release/
total 16M
2,5M dpcmder-linux-386
2,6M dpcmder-linux-amd64
2,7M dpcmder-mac-386
2,7M dpcmder-mac-amd64
2,4M dpcmder-win-386.exe
2,5M dpcmder-win-amd64.exe
```

After comparing the [latest dpcmder release](https://github.com/croz-ltd/dpcmder/releases/tag/v0.3.4) assets sizes to the [previous dpcmder release](https://github.com/croz-ltd/dpcmder/releases/tag/v0.3.3) asset sizes I must conclude that my mission from the start of this blog is successfully accomplished :)