Giant exes from go 1.11.5

(Msearle5) #1

Apologies if this has been seen before, but there are appear to be a lot of different reasons for “my binary is too big!” and it’s not so obvious whether this is new.

So I have a smallish (19KLOC and 700KB) Go project of which around 1KLOC and 180KB is dense initialization code that looks like this:

var objList = []Obj{
fg(’.’, “White”, gen(22, 15, desc("Blah blah blah blah. ", grow(“thingy”, speed(10, hp(150, visualrange(3, ac(14, dozy(10, blow(“hit”, “hurt”, “4d8”, blow(“hit”, “hurt”, “4d8”, isHidden(isMimic(isInvisible(isCold(hook(“all”, “none”, resist(“fear confusion sleep”, intel(1, 0, invisithing(“blabla”))))))))))))))))))),
<skip 1000 similar entries>

In the middle of this is a function that returns Obj, which then gets passed back (by value) through each surrounding function call. Obj is a few hundred bytes. This is not expected to be super efficient, but more than good enough given that it only runs once at init, replacing an external config file. As expected it takes <0.1 second - so I would rather not replace it with something more optimal but uglier…

However it appears to make the binary enormous - 323MB, of which 318MB is the .rodata segment containing highly repetitive binary data resembling copies of the Obj structure. Confirming this, adding an array of 128 bytes to the Obj structure increases the size to 444MB, and the binary compresses down to ~4.5MB with lz4.
(Making the added array much larger, say 1KB, gives errors like:
:1: prepwrite: bad off=1073741824 siz=1 s=
suggesting that it is trying to make an exe over 1GB but has hit some internal limit.)

So UPX is one effective workaround (until the exe exceeds 1GB anyway), and gccgo is another (which produces 18MB fully static with debug, down to <4MB dynamic release). However both are slow (gccgo being around 105 seconds vs. 35 for gc) so this is not ideal.

Strip makes no significant difference to the size as it is all in .rodata. Changing compiler parameters doesn’t help either. I’ve looked into it with nm and readelf, but they show no unusually large symbols - just a big, featureless .rodata segment.

I’m using Go 1.11.5 for Linux AMD64.

Any suggestions?

(Msearle5) #2

I’ve just tried out go 1.12rc1 on the same project.
It’s a massive improvement - dropping from 370MB to 6.5MB, and much faster (13 second rebuilds, 3 times faster!) too.
Recommended if you are seeing similar problems.