Search code examples
gotinygo

Is it possible to make a Go binary smaller by compiling it with TinyGo?


The TinyGo Homepage says that it is designed for micro controllers (or MCU). I wonder if it is possible to use TinyGo to compile go into small binary for ARM/Linux system?

Currently with UPX --best --lzma, my go code compiles to arm binary about 1MB, I hope to make it even smaller, because the system has limited storage.


Solution

  • tl;dr: basically yes, but you might have to port your program.

    I wonder if it is possible to use TinyGo to compile go into small binary for ARM/Linux system?

    Yes (reference: https://tinygo.org/docs/guides/linux/)

    TinyGo also lets you compile programs for Linux systems, both 32-bit and 64-bit, on both x86 and ARM architectures.

    For cross compiling, you can use GOOS and GOARCH as usual.

    For example:

    GOARCH=arm GOOS=linux tinygo build -o example ./...
    

    As for how smaller the executable will be compared with one with the official go compiler/runtime, it will likely depend on the specifics of your program. It is true though that TinyGo raison d'etre is to provide smaller binaries.


    So, we can try building some executables and empirically compare sizes. BTW, I realized that this comparison is available in TinyGo overview page. We can reproduce it here anyway with a few additional details:

    First, Go 1.17 is not supported yet (GitHub issue). You have to have Go 1.16 or lower installed in your system to be able to build, or use TinyGo dev branch.

    Then, as I mentioned in the comments, keep in mind that TinyGo doesn't support compiling all standard library packages, so if your code makes use of one of them you'll have to port it. This is the list of supported and unsupported packages: https://tinygo.org/docs/reference/lang-support/stdlib/

    To run the comparison, I download Go 1.16.5, which TinyGo supports as of today.

    # reference: https://golang.org/doc/manage-install
    $ go install golang.org/dl/go1.16.5@latest
    $ go1.16.5 download
    

    The program:

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

    I'm on a mac, so I have to build with the appropriate env vars. I include building with -ldflags="-s -w" as suggested in the comments.

    $ go env | grep -E "(GOOS|GOARCH)"
    GOARCH="amd64"
    GOOS="darwin" 
    
    $ go1.16.5 build -o example_go ./...
    $ go1.16.5 build -ldflags="-s -w" -o example_go_ldflags ./...
    $ GOROOT="/Users/blackgreen/sdk/go1.16.5" GOARCH=amd64 GOOS=darwin tinygo build -o example_tinygo ./...
    
    $ du -hs example_*
    1.9M    example_go
    1.5M    example_go_ldflags
    64K     example_tinygo
    

    For a simple hello world program, there is a conspicuous gain. Unsurprisingly, it's significantly better than ldflags too.