110

Lets compare c and go: Hello_world.c :

#include<stdio.h>
int main(){
    printf("Hello world!");
}

Hello_world.go:

package main
import "fmt"
func main(){
    fmt.Printf("Hello world!")
}

Compile both:

$gcc Hello_world.c -o Hello_c 
$8g Hello_world.go -o Hello_go.8
$8l Hello_go.8 -o Hello_go

and ... what is it?

$ls -ls
... 5,4K 2010-10-05 11:09 Hello_c
... 991K 2010-10-05 11:17 Hello_go

About 1Mb Hello world. Are you kidding me? What I do wrong?

(strip Hello_go -> 893K only)

Alvaro
  • 11,797
  • 9
  • 40
  • 57
zyrg
  • 1,323
  • 2
  • 10
  • 7
  • 2
    On a x86_64 Mac the "Hello World" binary is 1.3 MB like on a x64 Linux machine I assume. In contrast the ARM x32 binary is just as big as the x86_32 binary. The size significantly depend on the "word" length of the respective architecture. On x32 machines it's 32 bit on x64 it's 64 bit wide. Therefore the x32 "Hello World" binary is about 30% smaller. – Alex Oct 06 '10 at 04:48
  • 18
    @Nick: Considering that GO is marketed as a systems language I think it is a fair question. I work in systems and we don't always have the luxury of 4GB+ of RAM and a huge disk. – Ed S. Oct 12 '10 at 03:14
  • 4
    An 893kb executable is a far cry from "4GB+ of RAM and a huge disk", and as others have already pointed out, this includes the statically linked go runtime, which can easily be excluded. – Nick Johnson Oct 12 '10 at 08:05
  • 25
    Yes, it is a far cry, and I know the answer, but it is a valid question and the "who cares about memory consumption' attitude tends to come from working on systems where it doesn't matter. You seem to think that ignorance is ok and it's better just to not ask questions. And I will say again; sometimes ~1MB is a lot, you obviously don't work in that world. EDIT - You work at Google! lol. I still don't get the 'who cares' attitude though. – Ed S. Oct 13 '10 at 07:51
  • 14
    Evidently in the Java department ;) – Matt Joiner Oct 13 '10 at 13:03
  • 3
    Should the title mention the Go language? This comes up in a google search looking for general techniques to reduce compiled file size. But this is not generally applicable. – StayOnTarget Oct 20 '17 at 11:57

11 Answers11

111

If you are using a Unix-based system (e.g. Linux or Mac OSX) you could try removing the debugging information included in the executable by building it with the -w flag:

go build -ldflags "-w" prog.go

The file sizes are reduced dramatically.

For more details visit the GDB's page: http://golang.org/doc/gdb

J Aamish
  • 522
  • 7
  • 12
Amged Rustom
  • 1,739
  • 3
  • 19
  • 25
  • 5
    The same is achieved with `strip` command: `go build prog.go; strip prog` then we got so called _striped_ executable: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), **stripped** – Alexander I.Grafov Jul 01 '14 at 15:02
  • 1
    This works on Windows, and provides a slightly smaller binary file compared to the one produced by building normally and stripping. – Isxek Oct 20 '14 at 02:55
  • 10
    @Axel: Stripping go binaries is expressly not supported, and can cause serious bugs in some cases. See [here](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=717172). – Jonathan Hall Nov 19 '14 at 19:35
  • 10
    Currently the documentation lists `-w`, instead of `-s`. Not sure what the difference is, but you might want to update your answer :-) – Dynom Mar 21 '16 at 22:02
  • 2
    @Dynom: -w seems to reduce the size some but the reduction is not as good as with -s. -s seems to imply -w: https://golang.org/cmd/link/ – arkod Jun 29 '16 at 13:17
  • A "Hello World" was 2.4 MB large on Windows and Go 1.6 here, got 1.7 MB with Go 1.7, and 1.0 MB with this trick. Impressive, and definitely works on Windows. Not just slightly smaller, depending on the particular scenario of course. – Jonas Sep 25 '16 at 19:28
  • using `-s` did not make a noticeable difference for a hello world program. `-w` took it from 1.5 MB to 1.0 MB – ruffrey Dec 09 '16 at 14:56
61

The 2016 answer:

1. Use Go 1.7

2. Compile with go build -ldflags "-s -w"

➜ ls -lh hello
-rwxr-xr-x 1 oneofone oneofone 976K May 26 20:49 hello*

3. Then use upx, goupx is no longer needed since 1.6.

➜ ls -lh hello
-rwxr-xr-x 1 oneofone oneofone 367K May 26 20:49 hello*
OneOfOne
  • 95,033
  • 20
  • 184
  • 185
  • 4
    I'd like to add here that the usual caveats about UPX probably apply here. See this for more information: http://stackoverflow.com/questions/353634/are-there-any-downsides-to-using-upx-to-compress-a-windows-executable (much of it applies to non-Windows operating systems too). – Jonas Sep 25 '16 at 21:45
  • 2
    Just to add information on the linker flags: `-s` omits the symbol table and debug information; `-w` omits the DWARF symbol table. – rodrigocfd Feb 10 '22 at 02:02
33

Note: This answer is outdated

Please note that this answer is outdated. Please refer to the other higher-voted answers. I would like to delete this post, accepted answers can't be deleted though.


Is it a problem that the file is larger? I don't know Go but I would assume that it statically links some runtime lib which is not the case for the C program. But probably that is nothing to worry about as soon as your program gets larger.

As described here, statically linking the Go runtime is the default. That page also tells you how to set up for dynamic linking.

Dirk Vollmar
  • 172,527
  • 53
  • 255
  • 316
  • 2
    There is an [open issue](https://github.com/golang/go/issues/6853), with the milestone set for Go1.5 – Joe Jan 09 '15 at 10:24
25

Go binaries are large because they are statically linked (except for library bindings using cgo). Try statically linking a C program and you'll see it grow to a comparable size.

If this is really a problem for you (which I have a hard time believing), you can compile with gccgo and dynamically link.

Evan Shaw
  • 23,839
  • 7
  • 70
  • 61
  • 21
    Also it's kind of a nice move for a language that isn't universally adopted yet. Nobody is interested in cluttering their systems with go system libs. – Matt Joiner Oct 05 '10 at 13:53
  • 4
    The Go creators explicitly prefer dynamic linking: http://harmful.cat-v.org/software/dynamic-linking/. Google statically links all its C and C++: http://www.reddit.com/r/golang/comments/tqudb/are_go_binaries_really_entirely_statically_linked/c4p1awi – Graham King May 20 '12 at 16:38
  • 21
    I think that the linked article states the opposite - Go creators explicitly prefer static linking. – Petar Donchev Dec 03 '15 at 11:55
21

create a file named main.go, let's try with simple hello world program.

package main

import "fmt"

func main(){
    fmt.Println("Hello World!")
}

I use go version 1.9.1

$ go version
 go version go1.9.1 linux/amd64

Compile with standard go build command.

$ go build main.go
$ ls -lh
-rwxr-xr-x-x 1 nil nil 1.8M Oct 27 07:47 main

Let's compile once again with go build but with ldflags as suggested above,

$ go build -ldflags "-s -w" main.go
$ ls -lh
-rwxr-xr-x-x 1 nil nil 1.2M Oct 27 08:15 main

File size is reduced by 30%.

Now, lets use gccgo,

$ go version
 go version go1.8.1 gccgo (GCC) 7.2.0 linux/amd64

Building go with gccgo,

$ go build main.go
$ ls -lh
-rwxr-xr-x 1 nil nil 34K Oct 27 12:18 main

Binary size is reduced by almost 100%. Let's once again try building our main.go with gccgo but with build flags,

$ go build -gccgoflags "-s -w" main.go
-rwxr-xr-x 1 nil nil 23K Oct 27 13:02 main

Warning: As gccgo binaries were dynamically linked. If you have a binary which is very big in size, your binary when compiled with gccgo will not be decreased by 100%, but it will be reduced in size by considerable amount.

Compared to gc, gccgo is slower to compile code but supports more powerful optimizations, so a CPU-bound program built by gccgo will usually run faster. All the optimizations implemented in GCC over the years are available, including inlining, loop optimizations, vectorization, instruction scheduling, and more. While it does not always produce better code, in some cases programs compiled with gccgo can run 30% faster.

The GCC 7 releases are expected to include a complete implementation of the Go 1.8 user libraries. As with earlier releases, the Go 1.8 runtime is not fully merged, but that should not be visible to Go programs.

Pros:

  1. Reduced size
  2. Optimized.

Cons

  1. Slow
  2. Cannot use the latest version of go.

You can see over here and here.

nilsocket
  • 1,441
  • 9
  • 17
  • Thank you. I have a question, as I read, `gogcc` doesn't support ARM processor, is that right? And, can you suggest a tool that reduces the Golang build file size? – M. Rostami Apr 06 '20 at 10:05
  • 1
    How you compile with gccgo? – Vitaly Zdanevich Apr 07 '20 at 01:26
  • @M.Rostami `gogcc` is using `gcc` obsly under the hood, and we know `gcc` supports ARM. Also, `gogcc` suggest building it yourself, when we do we can use the arm branch of the `gcc`. https://go.dev/doc/install/gccgo https://gcc.gnu.org/git.html - Haven't tried it, could possibly work – Ido Mar 08 '23 at 08:46
16

You should get goupx, it will "fix" Golang ELF executables to work with upx. I've already got around 78% file size shrink in some cases ~16MB >> ~3MB.

Compression ratio usually tends to 25%, so it's worth a try:

$ go get github.com/pwaller/goupx
$ go build -o filename
$ goupx filename

>>

2014/12/25 10:10:54 File fixed!

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
  16271132 ->   3647116   22.41%  linux/ElfAMD   filename                            

Packed 1 file.

EXTRA: -s flag (strip) can shrink bin file even more goupx -s filename

marcio
  • 10,002
  • 11
  • 54
  • 83
9

More compact hello-world example:

package main

func main() {
  print("Hello world!")
}

We are skiped large fmt package and noticeably reduced binary:

  $ go build hello.go
  $ ls -lh hello
  ... 259K ... hello2
  $ strip hello
  $ ls -lh hello
  ... 162K ... hello2

Not so compact as C, but though measured in K not M :) Ok, it is not general way just shows some ways to optimize a size: use strip and try to use minimum packages. Anyway Go is not language for making tiny sized binaries.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Alexander I.Grafov
  • 1,370
  • 2
  • 16
  • 17
  • 11
    This code makes use of the builtin print() function and this practice is not recommended. And the question was not about how to reduce the code size of the sample program provided, but in a more general way. – wldsvc Jul 01 '13 at 08:28
  • @wldsvc I agree with you note. Though not understand why print() is bad way for display output? For simple scripts or for debug output it is good enough. – Alexander I.Grafov Sep 16 '14 at 15:55
  • 3
    See http://doc.golang.org/ref/spec#Bootstrapping _These functions are documented for completeness but are not guaranteed to stay in the language_. To me it means "don't use them" in any script except for one time debugging purposes. – wldsvc Sep 17 '14 at 15:34
  • 2
    @wldsvc print() still exists in 2020 but with the same disclaimer – Phani Rithvij Dec 10 '20 at 00:06
3

By default gcc links dynamically and go - statically.

But if you link you C code statically, you might get a binary with a bigger size.

In my case:

  • go x64 (1.10.3) - generated binary with size 1214208 byte
  • gcc x64 (6.2.0) - generated binary with size 1421312 byte

both binaries are statically linked and without debug_info.

go build -ldflags="-s -w" -o test-go test.go
gcc -static -s -o test-c test.c
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Vitaly
  • 611
  • 1
  • 7
  • 21
2

2018 answer for the next Go 1.11, as tweeted by Brad Fitzpatrick (mid-June 2018):

As of a few minutes ago, DWARF sections in #golang ELF binaries are now compressed, so at tip binaries are now smaller than Go 1.10, even with all the extra debug stuff at tip.

https://pbs.twimg.com/media/DfwkBaqUEAAb8-Q.jpg:large

Cf. Golang issue 11799:

Compressing our debug info might offer a significant, cheap file size win.

See more in commit 594eae5

cmd/link: compress DWARF sections in ELF binaries

The trickiest part of this is that the binary layout code (blk, elfshbits, and various other things) assumes a constant offset between symbols' and sections' file locations and their virtual addresses.

Compression, of course, breaks this constant offset.
But we need to assign virtual addresses to everything before compression in order to resolve relocations before compression.

As a result, compression needs to re-compute the "address" of the DWARF sections and symbols based on their compressed size.
Luckily, these are at the end of the file, so this doesn't perturb any other sections or symbols. (And there is, of course, a surprising amount of code that assumes the DWARF segment comes last, so what's one more place?)

name        old exe-bytes   new exe-bytes   delta
HelloSize      1.60MB ± 0%     1.05MB ± 0%  -34.39%  (p=0.000 n=30+30)
CmdGoSize      16.5MB ± 0%     11.3MB ± 0%  -31.76%  (p=0.000 n=30+30)
[Geo mean]     5.14MB          3.44MB       -33.08%

Rob Pike mentions:

That only helps on machines that use ELF.
Binaries are still too big, and growing.

Brad replied:

At least it's something. Was about to be much worse.
Stopped the bleeding for one release.

Reasons: Debug info, but also register map for the GC to let any instruction be a savepoint.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
1

The binary contains by default the garbage collector, the schedulding system that manage the go routines, and all the libraries you import.

The result is a minimal size of about 1 Mb.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
julienfr112
  • 2,077
  • 2
  • 25
  • 37
1

From Go 1.8 you can also use the new plugin system to split up your binary into something that resembles shared libraries. For this release it only works on Linux, but other platforms will probably be supported in the future.

https://tip.golang.org/pkg/plugin/

Joppe
  • 1,465
  • 1
  • 12
  • 17