Reputation: 127
====================== 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
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
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