Optimizing Go code with GCCGO for improved performance
Tags: go, tutorial, programming, devjournal
Published: 2022-12-29 16:24:22

> One day, my teacher asked me why Go compiled program has terrible perfomance and he kept mentioning about how a good compiler should behave.

Tldr; Check out [my demo!](https://github.com/parmcoder/go-beast-mode)

It is using `gccgo -O3 -o cool main.go` instead of `go build main.go`.

Basically, you will find that some code could run faster by changing the compiler instead of rewriting the code.

So, we want to turn our code into its peak performance despite getting bugs.

![Super gopher](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uv5icp4n861xz5vr0sne.gif)

Consider the following code...
``` go
package main

import (
	"errors"
	"fmt"
)

const limA = 2000

type EulerSolution struct {
	first   int
	second  int
	third   int
	fourth  int
	culprit int
}

func intPow(n, m int) int {
	if m == 0 {
		return 1
	}
	result := n
	for i := 2; i <= m; i++ {
		result *= n
	}
	return result
}

func computeEuler() (EulerSolution, error) {
	for e := 1; e < limA; e++ {
		for a := 1; a < e; a++ {
			var a5 = intPow(a, 5)
			for b := 1; b <= a; b++ {
				var b5 = intPow(b, 5)
				for c := 1; c < b; c++ {
					var c5 = intPow(c, 5)
					for d := 1; d < c; d++ {
						var d5 = intPow(d, 5)
						var e5 = intPow(e, 5)
						var got int = a5 + b5 + c5 + d5

						if got == e5 {
							return EulerSolution{
								first:   a,
								second:  b,
								third:   c,
								fourth:  d,
								culprit: e,
							}, nil
						}

					}
				}
			}
		}
	}

	return EulerSolution{}, errors.New("Euler solution")
}

```

We know that this code is poorly written. Let's write the another function that produce similar result.

``` go
func computeBeastEuler() (EulerSolution, error) {
	for e := 1; e < limA; e++ {
		var e5 = intPow(e, 5)
		for a := 1; a < e; a++ {
			var a5 = intPow(a, 5)
			for b := 1; b <= a; b++ {
				var b5 = intPow(b, 5)
				for c := 1; c < b; c++ {
					var c5 = intPow(c, 5)
					for d := 1; d < c; d++ {
						var d5 = intPow(d, 5)
						var got int = a5 + b5 + c5 + d5
						if got == e5 {
							return EulerSolution{
								first:   a,
								second:  b,
								third:   c,
								fourth:  d,
								culprit: e,
							}, nil
						}

					}
				}
			}
		}
	}

	return EulerSolution{}, errors.New("Euler solution")
}
```

Much better, now we want to prove that the new function, `computeBeastEuler`, is faster.

``` zsh
go test -bench main_test.go -v

=== RUN   Test_computeEuler
=== RUN   Test_computeEuler/empty
--- PASS: Test_computeEuler (4.19s)
    --- PASS: Test_computeEuler/empty (4.19s)
=== RUN   Test_computeBeastEuler
=== RUN   Test_computeBeastEuler/empty
--- PASS: Test_computeBeastEuler (2.51s)
    --- PASS: Test_computeBeastEuler/empty (2.51s)
PASS
ok      go-beast-demo   6.705s
```

It is better, and we expect our `go build` to do the optimization for us.

``` zsh
go build main.go
time ./main 
{133 110 84 27 144}

real    0m4.371s
...

```
Well, it is not what we expected. The problem is that Go compiler is not focusing on optimization by default. It only focuses on compilation speed.

Now, let me talk about another compiler that will help us get the faster program using the same code.

Let's install `gccgo` to our machine. [Make sure that you are not using Darwin OS!](https://github.com/golang/go/issues/463)

``` zsh
git clone --branch devel/gccgo git://gcc.gnu.org/git/gcc.git gccgo
mkdir objdir
cd objdir
../gccgo/configure --prefix=/opt/gccgo --enable-languages=c,c++,go --with-ld=/opt/gold/bin/ld
make
make install
```
References:
[gccgo](https://go.dev/doc/install/gccgo)

Anyway, you might run into some problems with its prerequisites for the compiler.
``` zsh
cd gccgo
./contrib/download_prerequisites
```
The script will download all you need for the `gccgo`.

Instead, we can just use a dockerfile to help us set up the environment for our new compiler.

``` dockerfile
# Download base image ubuntu 20.04
FROM ubuntu:20.04

# Disable Prompt During Packages Installation
ARG DEBIAN_FRONTEND=noninteractive

# Update Ubuntu Software repository
RUN apt update

RUN apt-get update -y

RUN apt-get install -y gccgo

WORKDIR /go/app

COPY . .

RUN gccgo -O3 -o cool main.go

CMD ["./cool"]
```

References:
[gcc optimize](https://www.rapidtables.com/code/linux/gcc/gcc-o.html)

Inside the container, let's do the benchmarking again!

``` zsh
docker compose build
docker run -it go-beast-mode_app bash
```
Inside my container, here is what I got.
``` zsh
time ./cool
{133 110 84 27 144}

real    0m0.906s
user    0m0.583s
sys     0m0.028s
```
It is even 2x faster than our `computeBeastEuler` in time.

In fact, Go can compile blazingly fast, but the trade-off is the lost of performance. If we want to optimize our Go program, then this is an example of what you could try. It will be compiled slower, but the performance is greatly increased along with the slight increase in memory usage.