For loops Performance

Hello, If you do not mind I wanted to know why Go “for” loops are slow, I tried to do some simple “for” loops benchmarks, comparing the performance of Go default compiler, TinyGo, and Graal Native Image, and the result was so mush disappointing, even though after I turned the GC off, TinyGo and Graal was so close on the other hand the default Go compiler was far away, does the Go compiler do any optimizing for the loops at all?

BTW: TinyGo was better even in calling C code (CGO).

Share specific benchmarks to get specific feedback. Based on what you’re describing, it seems likely to me that go isn’t the tool for the job you’re trying to do. Take a look at Rust if low-level performance is of utmost importance but you want modern tooling/ecosystem. I’m also pretty partial to zig but it’s not ready for primetime yet.

1 Like

I’m not comparing a low-level language to a low-level language, I’m comparing a high-level to a high-level and trying different compilers, don’t take it personally, thank you.

How was I taking it personally? Go is a tool; one I use for certain tasks where it is well-suited and don’t use others. I was questioning, based on what you were saying, whether it was the correct tool for you to be using and suggested some alternatives. I also suggested how you could improve your question to potentially get more useful responses.

I’ll reiterate: post specific benchmarks if you want a chance to get specific feedback. As it stands, your post is vague, includes no code, and thus is likely to produce vague responses. That having been said, there’s a chance you’re not even benchmarking what you think you’re benchmarking. Take a look at these links:

https://www.reddit.com/r/golang/comments/2p46ix/golang_simple_loop_slower_than_java/

2 Likes

Sorry about that, here is a simple “for” loop benchmark in PureGo, TinyGo and Kotlin/Graal Native Image (all of them without a GC):

Go and TinyGo Code :

Command used are :

$ GOGC=off go build src/main.go
$ GOGC=off tinygo build -opt=2 src/main.go

package main
import (
    "fmt"
    "time"    
    )
func main ( ) {
    var timeBefore = time.Now()
    doSomething()
    var timeAfter = time.Now()
    fmt.Println ( timeAfter.Sub(timeBefore) )

}

func doSomething () int {

    var x int = 0
    for i := 1 ; i <= 1000000 ; i++ {
        
        x += i 

    }
    return x
    
}

Kotlin Code :

$ native-image --gc=epsilon -march=native -O3 -jar main.jar
// Compile a jar to a native executable without a GC

import kotlin.time.Duration
import kotlin.time.measureTime

fun main () {

    var executionTime = measureTime { doSomething() }
    println ( executionTime )

 
}

fun doSomething () :Int {

    var x:Int = 0
    for ( i in 1..1000000 ) {
        x += i
    }
    return x


}
Result : 
----------------------------- First Time | Second Time | Third Time----------------------
kotlin / Graal                1.816us      1.816us     1.537us
Go                            455.654µs    672.655µs   457.749µs
TinyGo                        5.448µs      5.308µs     3.562µs

Even when calling a C loop, TinyGo is faster, and as you see here, the loop does not invoke any function (like println).

Hello Dean, forget what I said, I tried more pure complex “for” loops that can not be ignored (no shortcut for the compilers) to make sure that the loops actually iterate, Graal Native Image was so mush slower than PureGo and TinyGo, Tiny Go was a litte more faster :

TinyGo :              432.148669ms 443.742183ms 440.125731ms
Go :                  485.346782ms 487.894507ms 477.667357ms
GraalNM:              961.963131ms 974.069681ms 957.945913ms

TinyGo does some optimizations, but unfortunately It does not support all feature of the latest Go version, Are there some flags that can be passed to the Go compiler for optimization?

Edit: After doing the same test in C and calling it from Go, TinyGo was a lot better, I think TinyGo can call C more efficiently :

Go :      445.494291ms 446.961961ms 442.149793ms
TinyGo :  85.634284ms  61.546723ms  88.512513ms

Synthetic benchmarks are notoriously hard to get correct, which is why people tend to prefer real-world scenarios. I’m not super familiar with calling C from TinyGo but my understanding is that it does, indeed, have less overhead:

https://aykevl.nl/2021/11/cgo-tinygo/

And from the TinyGo FAQ:

The standard Go compilers do a few special things for CGo calls. This is necessary because only Go code can use the (small) Go stack while C code will need a much bigger stack. A new compiler can avoid this limitation if it ensures stacks are big enough for C, greatly reducing the C ↔ Go calling overhead.

This seems consistent with what you’re finding. It sounds like maybe the answer is “use TinyGo”. What features of Go is it missing that you need? In language design, nearly everything is a series of tradeoffs. Again - I would question whether Go is making the correct tradeoffs (ease of use, simple concurrency with goroutines, compile speed, garbage collection, capable stdlib, tooling, ecosystem, etc.) for the project you are trying to build.

1 Like

Thank you, I decided to use it. As for What TinyGo is missing, some features maybe do no work as expected.

Game engine. I hope Go & C are good enough for that.

Rust is probably a better tool for that job. That said, check out:

1 Like

Thanks, Rust is not good for mental health IMO.

1 Like

Calling C functions from Go is 281 times slower than calling Go from Go, and calling C functions from TinyGo is 107 times faster than calling Go from Go, a lot of improvements for the Go compiler can be done with time I hope.