[HN Gopher] Life without line numbers for 6% smaller Go binaries ___________________________________________________________________ Life without line numbers for 6% smaller Go binaries Author : caust1c Score : 43 points Date : 2020-07-21 15:35 UTC (7 hours ago) (HTM) web link (commaok.xyz) (TXT) w3m dump (commaok.xyz) | ottoboney wrote: | Your HTTP cert is invalid. Just a heads up. | msbarnett wrote: | If it's showing up as invalid for you, you may have locally | revoked trust of the DST Root CA X3 IdenTrust root certificate. | donmcronald wrote: | It's fine for me and was issued May 26. The URL when I visited | was https://commaok.xyz | rnotaro wrote: | His browser might have oppened the following link: | https://www.commaok.xyz/post/no-line-numbers/ somehow. | | It's serving a certificate for github.com. | jeffbee wrote: | 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... | rowanG077 wrote: | Pretty insane that the compiler for such a popular language is | so shitty. | pantalaimon wrote: | 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. | ascar wrote: | Wait, isn't that trick basically about avoiding optimization | (inlining the function to avoid the context switch) to reduce | code size? | | The C compiler would do something similar here and also | increase code size for performance unless you explicitly use | `-Os` | monocasa wrote: | The better C compilers use better heuristics in these | cases, knowing that binary size can mean I$ pressure and | therefore be a detriment to performance. | jeffbee wrote: | 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. | varbhat wrote: | https://blog.filippo.io/shrink-your-go-binaries-with-this-on... | | This is great article instructing how to shrink the Go Binaries. | Author is lead of cryptography and security team of Go Team at | Google. | ed25519FUUU wrote: | tl;dr for those wondering: strip debugging and then compress | the binary with upx: go build -ldflags="-s | -w" main.go upx --brute main | | Just tried that on one of my binaries and went from 12 mB down | to 3.6 mB. | distortedsignal wrote: | 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. | Karunamon wrote: | Does UPX still trigger virus scanners, though? As much as I | like the idea of tinier binaries, the chance that your user's | endpoint security is going to freak out makes it not worth | the hassle. | kingnothing wrote: | 35MB seems relatively small already. What do you get from an | 8MB binary that you don't from one 4x that size? | Rebelgecko wrote: | I'm not familiar with UPX compression so it might | invalidate what I'm about to say. However it can be | beneficial for performance if you fit more of your | executable into CPU cache (actual results may vary based on | access patterns and many other factors) | makapuf wrote: | UPX is just a (nice) compresser for binaries, where | running the executable really runs a small decompressor | on the binary, gets a binary in ram and runs it. So your | binary is not smaller in cache when executed. | smabie wrote: | 35mb seems small to you? That's an absolutely massive | binary. | | But to answer your question, you're going to see some | performance improvements if your binary can fit into lower | levels of the CPU cache. | | Kdb+/q for example is less than 700kb, which mean that the | entire thing can fit into the L1 instruction cache on high | end server CPUs. And that size is even more impressive | considering its only dynamically linked to libc and | libpthread. | | This small size contributes to the out of this world | performance the system has. And remember that's the | language and the time series db. | qmmmur wrote: | 27MB. Where that is important is context specific | pantalaimon wrote: | > 35MB seems relatively small already | | 35 MB is huge! Are there any assets inside the binary? | cesarb wrote: | > 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. | pantalaimon wrote: | _Were?_ My initrd + vmlinuz is still smaller than that | today on a stock Ubuntu kernel. | birdyrooster wrote: | I wonder how much more from javascript minifying can be reused in | this space. | coldtea wrote: | Probably not much? Javascript minifying is about raw source | code, whereas those are binary instructions + debug info. | kardos wrote: | How much better does a 6% smaller go binary perform? | jeffbee wrote: | 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. | svnpenn wrote: | 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 wrote: | 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 wrote: | 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. | mehrdadn wrote: | Not entirely, there's also a debug directory embedded | inside executables that gets stripped. | kjksf wrote: | 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. ___________________________________________________________________ (page generated 2020-07-21 23:00 UTC)