?

Log in

No account? Create an account

February 11th, 2016

Go vs Swift vs C++ microbenchmark

Looking at the Swift vs Go vs Python battle, couldn't help but notice its old age. It was written almost 12 months ago and the weight of time clearly shows. In a fast paced race towards ecosystem simplicity that we observe nowadays, a year is a lot.

So on my Macbook Air (1.7 GHz Intel Core i5) I decided to repeat the test, and add C++ to the mix. It is too easy to test against python (which is about 10x slower than the "compiled" languages), so C++ should be a fair competition. Go 1.5.3, Swift 2.1.1, C++ clang-700.1.81.

I don't propose taking my test seriously. The micro-benchmarking doesn't tell us much about about the language ecosystem, tooling, maintainability, surface area of a language, etc, etc. It even doesn't tell us too much about performance. But you know the drill. If you see a micro-benchmark, puke and close the window. Don't read any further.

Nevertheless! There's some source.

GoSwiftC++
package main
import "fmt"

func main() {
    var sum int64 = 0

    for e := 0; e < 200; e++ {
        sum = 0

        var x[]int64
        for i := 0 ; i < 1000000; i++ {
            x = append(x, int64(i));
        }

        var y[]int64
        for i := 0 ; i < 1000000-1; i++ {
            y = append(y, x[i]+x[i+1]);
        }

        for i := 0 ; i < 1000000; i += 100 {
            sum += y[i];
        }
    
    }
    
    fmt.Println(sum);
}
var sum : Int64 = 0

for e in 0..<200 {
    sum = 0 
    
    var x : [Int64] = []
    for (var i = 0; i < 1000000; i++) {
        x.append(Int64(i));
    }
        
    var y: [Int64] = []
    for (var i = 0; i < 1000000-1; i++) {
        y.append(x[i] + x[i+1]);
    }
        
    for (var i = 0; i < 1000000; i+=100) {
        sum += y[i]
    }   
}
        
print(sum)  
#include <vector>
#include <iostream>

int main() {
    int64_t sum = 0;

    for(int e = 0; e < 200; e++) {
        sum = 0;

        std::vector<int64_t> x;
        for(int i = 0; i < 1000000; i++) {
            x.push_back(i);
        }

        std::vector<int64_t> y;
        for(int i = 0; i < 1000000-1; i++) {
            y.push_back(x[i] + x[i+1]);
        }

        for (int i = 0; i < 1000000; i += 100) {
            sum += y[i];
        }
    }

    std::cout << sum << std::endl;
}


Why I chose int64 instead of int? On my 64-bit platform the C++'s int is 32 bit, whereas Go's and Swift's default ints are 64-bit. I wanted to level the playing field a little.

Anyway. Let's the battle begin:

go build -o test-go go/test.go
swiftc -O -o test-swift swift/test.swift
c++ -O3 -o test-c cplusplus/test.cc
GOMAXPROCS=2 time ./test-go
9999010000
        4.32 real         5.99 user         0.38 sys
GOMAXPROCS=1 time ./test-go
9999010000
        3.93 real         3.89 user         0.05 sys
time ./test-swift
9999010000
        3.96 real         2.82 user         1.12 sys
time ./test-c
9999010000
        2.37 real         1.74 user         0.62 sys


What we see here? With Go, GOMAXPROCS adds measurable overhead. As expected. But not that huge. C++ is predictably faster. No surprise here. In fact, this appears to be mostly a competition of memory allocators and GCs (where applicable), and Swift and Go are rather on par with each other.

But let's try a smaller array so memory allocation becomes a bit less of an issue. The following test was made with e<=2000 (instead of 200) and loop iterations to 100k instead of 1m:

go build -o test-go go/test.go
swiftc -O -o test-swift swift/test.swift
c++ -O3 -o test-c cplusplus/test.cc
GOMAXPROCS=2 time ./test-go
99901000
        4.40 real         5.65 user         1.10 sys
GOMAXPROCS=1 time ./test-go
99901000
        5.06 real         5.07 user         0.83 sys
time ./test-swift
99901000
        2.32 real         2.29 user         0.01 sys
time ./test-c
99901000
        1.39 real         1.37 user         0.01 sys


Now, we're talking. Go is lagging behind, perhaps because of its reliance on its own code generator. This gets us into "3x slower than C" domain. Swift is using LLVM, the same code generator my C++'s compiler uses under the hood. But it adds some checks and GC, so Swift also exhibits a serious slowdown, though well within 2x of C++.

What we can take from it?

I suspect that if Swift had first class concurrency support while managing to retain its speed, many people would consider it instead of Go. But we can't have both. Either we have a speedy and high maintenance language, or concurrent but a bit slow language, and then Swift.

...and we also have Erlang, but its benchmark didn't fit on my charts :)