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!
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.
Consider the following code...
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.
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.
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.
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!
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
Anyway, you might run into some problems with its prerequisites for the compiler.
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.
# 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
Inside the container, let's do the benchmarking again!
docker compose build
docker run -it go-beast-mode_app bash
Inside my container, here is what I got.
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.
Top comments (0)