-
Notifications
You must be signed in to change notification settings - Fork 412
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable dead code elimination to reduce binary size #1477
Comments
compressed (XZ) image size reduction: 4325624 -> 3684448 (15%) |
I am confused, how do you invoke it? My familiarity with DCE was when Go's linker was Ken's linker from Plan 9, which just always did DCE. When did it become an option and why, I wonder? We should always do DCE; I just assumed we did. |
Go always does DCE, hen it can be sure that code is indeed dead. |
https://pastebin.com/wYLyp58R is the quick and dirty hack for go std library and elvish that re-enables DCE and that i used to get the numbers above. |
so, wow, that is very interesting, I had no idea. Would it be worth extending the bb tool to warn when it sees code practices that disable gce? Or is there a tool usage that can do that for us? |
Neat, if you want to put in that elvish fix I'll approve :-) |
How does the fmt package affect us? Doesn't fmt use reflection, and fmt is used by nearly everything? |
@rminnich i think it would, but first we need to figure out what to do about the go std library deps. re: elvish - i'm pretty sure it breaks stuff, so no, it's not ready to be checked as is :) i'm not familiar with elvish enough (= at all) to be able to tell what exactly breaks. but since it's only builtin functions we're talking about, i guess they can be enumerated and the list made explicit, instead of just allowing anything via reflection? @hugelgupf it does, but tno the parts that prevent DCE from working, i.e. not Value.Call or struct method lookup. it does seem that all that keeps us from getting DCE working is the text/template stuff, which i'm pretty sure is not too important. however, since changes are all in the go runtime, i'm not sure what would be the best way to go about it. provide a patched runtime? this is not very user-friendly. but i guess could be done, e.g. provide a docker container with patched go that can be used to build smaller binary. so, i can start by looking at elvish and coming up with a proper fix. |
Thanks @rojer! That's a really cool find. For text/template, I don't grasp the dependency graph. I thought go/build was only used at build time? Also, is there an easy test to determine if a binary was built without DCE? It would be helpful for the u-root build to print a warning because this could easily regress again in the future. |
@rjoleary so, i investigated the text/template use in u-root. turns out, the situation with unused code elimination in Go is pretty bad - unused global variables like maps and lists just cannot be eliminated (golang/go#31704). the template functionality is not even used anywhere: the way we end up with this anchor on our neck is through i filed golang/go#36021 - that way, if as for ways to detect if DCE is working, it is possible to have a sentinel method on some struct that should normally be eliminated and have a check at the end of build if it has been - |
With #1358 I can probably get rid of the go/build dependency on pkg/golang. |
Let me put my useless cents here :)
In my very humble opinion all such stuff that prevents DCE (like Also in gcc there's
Also just an useless thought-food:
Also there's Also disassembled code of a binary made by Go compiler does not look compact. I believe that DCE is not the only strategy to shrink the binary, but also In total:
|
@xaionaro, ooh, this is very interesting. so gcc-built binary trims a lot more fat. i wonder how/if reflection works in gcc-go built binaries. it may be that LTO/DCE (--gc-section?) is disabled in that case. definitely something to look into. |
Eh... I've just tried to compile
I also was have to do a lot of dirty hacks to make it work, but the size is even worse than with simple Looks like Also I've just tried to compile every tool separately:
Total:
So this numbers gives hope. It looks like gccgo's DCE does work with the most of tools. But, for example, gccgo's May be I'll be able to investigate this. I'll try to find it out in Monday :) Update: I will try to spend a little-bit of time on investigation of the |
Created a ticket: golang/go#36073
An update: it appears I just used |
An update:
The line to compile:
What is required to do to use it:
|
#1486 removes use of reflection from elvish without (i hope) breaking any of the functionality.
unfortunately, the aforementioned patch will be integrated into Go 1.15 which will be rleeased in aug 2020, so we need to find some way of incorporating it into builds before that. i'll look into it. |
An update from my side:
|
Use of `reflect.Value.Call` disables unused public method elimination at build time, which leaves unused code in the resulting binary, see #1477. Elvish uses `reflect.Value.Call` to invoke builtin functions. Replace its use with a static set of possible function signatures: these cover all existing use cases and can be extended if needed. Saves approx. 3.2M on amd64 (go 1.13.5 + text/template patch): ``` [rojer9@rojer9-t470 ~/go/src/github.com/u-root/u-root/bb master]$ GOROOT=$HOME/go_210284 go build && ls -l bb -rwxrwxr-x. 1 rojer9 rojer9 20645771 Dec 16 16:40 bb [rojer9@rojer9-t470 ~/go/src/github.com/u-root/u-root/bb master]$ gc elvish_noreflect Switched to branch 'elvish_noreflect' [rojer9@rojer9-t470 ~/go/src/github.com/u-root/u-root/bb elvish_noreflect]$ GOROOT=$HOME/go_210284 go build && ls -l bb -rwxrwxr-x. 1 rojer9 rojer9 17257671 Dec 16 16:41 bb ``` Signed-off-by: Deomid "rojer" Ryabkov <rojer9@fb.com>
for DCE to work on libraries, they need to be built with -ffunction-sections as well, which they may not already be. |
as an interim solution, i have created a docker image that contains go with patched stdlib - #1491 it's pretty easy to use and delivers the result:
|
Use of `reflect.Value.Call` disables unused public method elimination at build time, which leaves unused code in the resulting binary, see u-root#1477. Elvish uses `reflect.Value.Call` to invoke builtin functions. Replace its use with a static set of possible function signatures: these cover all existing use cases and can be extended if needed. Saves approx. 3.2M on amd64 (go 1.13.5 + text/template patch): ``` [rojer9@rojer9-t470 ~/go/src/github.com/u-root/u-root/bb master]$ GOROOT=$HOME/go_210284 go build && ls -l bb -rwxrwxr-x. 1 rojer9 rojer9 20645771 Dec 16 16:40 bb [rojer9@rojer9-t470 ~/go/src/github.com/u-root/u-root/bb master]$ gc elvish_noreflect Switched to branch 'elvish_noreflect' [rojer9@rojer9-t470 ~/go/src/github.com/u-root/u-root/bb elvish_noreflect]$ GOROOT=$HOME/go_210284 go build && ls -l bb -rwxrwxr-x. 1 rojer9 rojer9 17257671 Dec 16 16:41 bb ``` Signed-off-by: Deomid "rojer" Ryabkov <rojer9@fb.com> Signed-off-by: Rob Vandermeulen <rvandermeulen@google.com>
Could be partly related problem: golang/go#36313
UPD: Just for future ideas, @rojer's approach with NOOP-ing |
Flag
( I'll prepare PR to use this flag. May be we should create a metatask for any size-reduction methods or rename this task? UPD: The PR: #1512 |
wow, awesome!
yes, i think so. |
@rojer: JFYI, I summarized all found ways to reduce a Golang binary here: For example we may also consider adding UPD: It's a link to an obsolete document above. I need to update it :( |
#1477 Signed-off-by: Deomid "rojer" Ryabkov <rojer9@fb.com>
so, DCE is working, we are using it and #1808 added a test to make sure we don't regress. |
Go's dead code elimination algorithm is described and implemented here.
TLDR is: elimination of unused public methods at link time will be inhibited if reflection function calling are used anywhere.
quick and dirty experiment shows that if DCE remains enabled, the uncompressed CPIO image size of just core goes down by ~2.3M: 15165376 -> 12707776, a 16% reduction.
with all exp commands, it's about the same: 19130524 - 16562332, about 14%
current uses of reflection in u-root are:
(3) is the toughest to address, it's all in go core libraries.
but the payoff is significant, might be worth exploring.
The text was updated successfully, but these errors were encountered: