Alexniver
Alexniver

Reputation: 127

Why I test golang goroutine slow than java Thread

====================== Edit 2016 05 27 16:55 Solved =======================

This problem is solved! thanks to @Paul Hankin, You are right! Java is good at do "for() {i = i + 1}", so when i change code to "for() {i = i + i}", java lose.

(PS: Use Java ExecutorService really make the java result nice, but still not as good as goroutine, no ExecutorService example here)

Java Code :

import java.util.ArrayList;
import java.util.List;


public class Test {
    public static void main(String args[]) throws InterruptedException {
        List<Thread> threads = new ArrayList<Thread>();


        for(int i = 0; i < 10; i ++) {
            threads.add(new Thread(new RunableAdd()));
        }

        long timeStart = System.currentTimeMillis();

        for(Thread t: threads) {
            t.start();
        }


        for(Thread t: threads) {
            t.join();
        }


        System.out.println("Time Use : " + (System.currentTimeMillis() - timeStart) + "ms");
    }


}

class RunableAdd implements Runnable {
    @Override
    public void run() {
        long i = 1;
        while(true) {
            i += i;
            if (i > 1000 * 1000* 1000) {
                return;
            }
        }
    }
}

Golang code:

package main

import (
    "fmt"
    "time"
)

func main() {
    chanSignal := make(chan bool)

    startTime := time.Now()

    goroutineNum := 10

    for i := 0; i < goroutineNum; i++ {
        go func() {
            j := 1
            for {
                j = j + j
                if j > 1000*1000*1000 {
                    chanSignal <- true
                    return
                }
            }
        }()
    }

    for i := 0; i < goroutineNum; i++ {
        <-chanSignal
    }

    fmt.Println(time.Since(startTime))
}

Result :

Thread/goroutine num : 10

java : Time Use : 4ms
golang : 19.105µs

Thread/goroutine num : 100

java : Time Use : 27ms
golang : 180.272µs

Thread/goroutine num : 1000

java: Time Use : 512ms
golang : 1.565521ms

turns out that golang is good, and java is good at "i = i + 1",

What's instering is that , when goroutine num pluse 10, the time cost is less than 10 times.

===============================End====================================

I'm test golang goroutine with java thread today, I think golang goroutine should faster than java Thread, but my test result show that java thread wins.

Environment: java 1.8 golang 1.6

cpu are 4 core

$ cat /proc/cpuinfo | grep 'core id'
core id     : 0
core id     : 0
core id     : 1
core id     : 1

Java code:

import java.util.ArrayList;
import java.util.List;


public class Test {
    public static void main(String args[]) throws InterruptedException {
        List<Thread> threads = new ArrayList<Thread>();


        for(int i = 0; i < 100; i ++) {
            threads.add(new Thread(new RunableAdd()));
        }

        long timeStart = System.currentTimeMillis();

        for(Thread t: threads) {
            t.start();
        }


        for(Thread t: threads) {
            t.join();
        }


        System.out.println("Time Use : " + (System.currentTimeMillis() - timeStart) + "ms");
    }


}

class RunableAdd implements Runnable {
    @Override
    public void run() {
        int i = 0;
        while(true) {
            i ++;
            if (i == 10 * 1000* 1000) {
                return;
            }
        }
    }
}

golang code:

package main

import (
    "fmt"
    "time"
)

func main() {
    chanSignal := make(chan bool)

    startTime := time.Now()

    goroutineNum := 100

    for i := 0; i < goroutineNum; i++ {
        go func() {
            j := 0
            for {
                if j == 10*1000*1000 {
                    chanSignal <- true
                    return
                } else {
                    j = j + 1
                }
            }
        }()
    }

    for i := 0; i < goroutineNum; i++ {
        <-chanSignal
    }

    fmt.Println(time.Since(startTime))
}

and result is :

when i set thread num 10:

Java : Time Use : 18ms
golang : 50.952259ms

Thread num 100:

Java : Time Use : 88ms
golang : Time Use : 458.239685ms

Thread num 1000:

Java : Time Use : 1452ms
golang : 4.701811465s

Did Java Thread really fast than golang goroutine? Or my program did something wrong?

Upvotes: 2

Views: 2614

Answers (2)

user9054726
user9054726

Reputation: 11

The i = i+i test is silly, since it performs nearly only 24 adds and 24 < comparisons, and I believe we all admit that we don't often encounter real world problem which can be completed with only 48 machine instructions.

And rather than Thread, Executors.newFixedThreadPool is more close Java equivalent of goroutine, try it with warm up, you might get different results.

Upvotes: 1

Paul Hankin
Paul Hankin

Reputation: 58389

Benchmarks are hard to get right, and it's easy to think you're measuring one thing (cost of goroutines/threads) when you're measuring something else. I think that's what's happening here.

For example, the inner loop in the threads/goroutines aren't the same in the two programs. Rewriting the goroutine to be more like the java code:

for j := 0; j != 10*1000*1000; j++ {}
chanSignal <- true

results in a 2x speedup for the go.

That still leaves a significant performance gap when I run it on my machine -- the go code takes 663ms with 1000 goroutines, and the java code 55ms with 1000 threads. Perhaps what's happening is that the JVM is JITting away the useless loop in the run method of the thread after it's been executed a few times.

Here's some evidence that the cost of goroutines aren't relevant. Just executing the code serially gives a runtime of 2.55 seconds on my machine:

package main

import (
    "fmt"
    "time"
)

func main() {
    goroutineNum := 1000
    startTime := time.Now()
    for i := 0; i < goroutineNum; i++ {
        for j := 0; j < 1000*1000*10; j++ {
        }
    }
    fmt.Println(time.Since(startTime))
}

The goroutine-based version of this code, on 4 processors, runs in 663ms, which is only slightly more than one quarter of the 2.55 seconds the serial code takes (on my machine). So this is really quite efficient parallelism, and the cost of the goroutines is negligible.

So mostly I think you're benchmarking how efficiently java and go execute empty loops. It looks like this is a perfect use-case for JIT, and you're seeing that in the excellent run-time performance of the Java code.

Upvotes: 4

Related Questions