
Life without line numbers for 6% smaller Go binaries - caust1c
https://commaok.xyz/post/no-line-numbers/
======
varbhat
[https://blog.filippo.io/shrink-your-go-binaries-with-this-
on...](https://blog.filippo.io/shrink-your-go-binaries-with-this-one-weird-
trick/)

This is great article instructing how to shrink the Go Binaries. Author is
lead of cryptography and security team of Go Team at Google.

~~~
distortedsignal
Completely agree.

I performed the steps in the article and reduced the output binary size from
~35 MB for raw go build to around 29 MB for go build with the ldflags all the
way down to 8.3 MB for compressed with upx.

Really impressive results.

~~~
kingnothing
35MB seems relatively small already. What do you get from an 8MB binary that
you don't from one 4x that size?

~~~
pantalaimon
> 35MB seems relatively small already

35 MB is huge! Are there any assets inside the binary?

~~~
cesarb
> 35 MB is huge!

For context, 35MB is two and a half _boxes_ of high-density floppy disks.
There were whole operating systems, complete with applications, smaller than
that.

~~~
qiqitori
Aren't Go binaries completely static? That sort of is a large part of an
operating system (the libraries minus hardware-related libraries) -- Just add
a kernel (tens of MB) and whatever userland stuff you need to get your
hardware to work (which could be "nothing" in some cases) and you're ready to
go.

~~~
pjmlp
QNX used to be shipped on a single high density floppy.

------
jeffbee
Since the Go compiler is so primitive you can strongly influence the size of
the program by just restructuring it. An example: if you have a function that
converts integer return codes to errors, and you have another function that
calls it a lot:

    
    
      func codeToError(i int) error {
        switch i {
           case ...
           ...
        }
      }
    
      func stuff() error {
        if rv := callJunk(); rv < 0 {
          return codeToError(rv)
        }
    
        if rv := moreJunk(); rv < 0 {
          return codeToError(rv)
        }
    
        ...
    
        return nil
      }
    

You're going to get multiple whole copies of the inner function inlined into
the outer one. This can add up, if the outer function is long and/or if the
inner switch statement is huge.

You can save a lot of code by jumping through an adapter function instead,
like:

    
    
      func callWithErrorsConverted(f func() int, g func(int) error) error {
        return g(f())
      }
    

... because now the compiler doesn't get to inline g into f. These things make
a big difference in whole-program size. I wrote about it in the context of
tailscale and protobufs here:

[https://paper.dropbox.com/doc/Bloaty-Puffs-and-the-Go-
Compil...](https://paper.dropbox.com/doc/Bloaty-Puffs-and-the-Go-Compiler--
A4NFFR1SRcfOaz0kMw0ITuQ0AQ-YiblPOe6AD2tKfEj5RzFT)

~~~
pantalaimon
I’m pretty happy I don’t have to worry about such details in C and can rely on
the compiler to do the easy optimizations for me.

~~~
jeffbee
It's not really a language problem, it's a compiler problem, so at least in
the future we might get a compiler that recognizes error-handling idioms. Or
we just get llvm? The language problems, like the performance-hostile calling
convention, are harder to fix.

~~~
AmericanChopper
> It's not really a language problem, it's a compiler problem

This has never seemed like such a clear distinction to me. A language is
essentially just an instruction set for a compiler.

~~~
sli
Consider that a hobbyist C compiler isn't going to perform the same
optimizations as gcc. Likewise, clang and gcc aren't totally equivalent,
either, despite being based on the same language standard. Most languages
don't have multiple compilers, but any non-commercial language can in theory
have multiple competing compilers that don't give equivalent output for the
same input.

~~~
eru
Even commercial languages can have multiple competing compilers.

------
svnpenn
This didnt seem to change anything for me. I am currently using Go 1.14.4
(Windows), and after trying go1.15beta1, the resultant executables are
actually larger.

Another thing Ive always found strange is people always recommend (-ldflags
-w), when (-ldflags -s) gives smaller result, and (-ldflags '-s -w') is
identical to (-ldflags -s).

~~~
skykooler
Given the article is talking about DWARF debugging formats, I believe the
numbers in the article are for Linux binaries. I'm not sure if Windows
binaries store line numbers in the same way.

~~~
ygra
Doesn't Windows usually just use a .pdb file next to the exe/dll, containing
the debugging information? So stripping debug information on Windows is just
deleting (or not distributing) that file.

~~~
kjksf
Yes, .pdb files are used by most toolchains for debug information on Windows.

No, Go doesn't use .pdb files.

On all platforms Go stores a significant amount of meta-data in the binary, in
the same format so that they can access it using the same code on all
platforms.

This information is used by the runtime. Precise garbage collector needs to
know the layout of structures and stack to know which fields are pointers; To
generate readable callstack Go needs info about function and where they come
from (source code file name, position). etc.

A sidenote: you can convert DWARF debug info to .pdb using external tool which
allows debugging Go programs with Windows native debuggers like WinDBG.

------
pansa2
> You can usually save double-digit percentages by stripping debugging
> information: pass `-ldflags=-w` to `go build`.

Is there a way to make `go build` for a particular project always use
`-ldflags=-w`? Or do I have to remember to type it in every time (or hide my
`go build` command inside a Makefile or `build.sh`)?

------
kardos
How much better does a 6% smaller go binary perform?

~~~
jeffbee
Shrinking the line table will be irrelevant to performance. As I understand
it, the tailscale binary has a hard size requirement to run as a VPN under
iOS.

------
ottoboney
Your HTTP cert is invalid. Just a heads up.

~~~
donmcronald
It's fine for me and was issued May 26. The URL when I visited was
[https://commaok.xyz](https://commaok.xyz)

~~~
rnotaro
His browser might have oppened the following link:
[https://www.commaok.xyz/post/no-line-
numbers/](https://www.commaok.xyz/post/no-line-numbers/) somehow.

It's serving a certificate for github.com.

------
birdyrooster
I wonder how much more from javascript minifying can be reused in this space.

~~~
coldtea
Probably not much? Javascript minifying is about raw source code, whereas
those are binary instructions + debug info.

