为什么我测试golang goroutine的速度比java线程慢



================编辑2016 05 27 16:55已解决===================

这个问题解决了!感谢@Paul Hankin,你说得对!Java擅长做"for(){i=i+1}",所以当我把代码改为"for((){id=i+i}"时,Java就输了。

(附言:使用Java ExecutorService确实让Java结果很好,但仍然不如goroutine,这里没有ExecutorServices的例子)

Java代码:

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代码:

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))
}

结果:

线程/goroutine数量:10

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

线程/goroutine数量:100

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

线程/goroutine数量:1000

java: Time Use : 512ms
golang : 1.565521ms

事实证明,golang很好,java擅长"i=i+1",

说明是,当goroutine num加10时,时间成本不到10倍。

====================================结束========================================

我今天用java线程测试golang goroutine,我认为golang goroutine应该比java线程更快,但我的测试结果显示java线程获胜。

环境:java 1.8golang 1.6

cpu是4核

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

Java代码:

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;
}
}
}
}

戈兰代码:

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))
}

结果是:

当我设置线程编号10:时

Java : Time Use : 18ms
golang : 50.952259ms

线程编号100:

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

线程编号1000:

Java : Time Use : 1452ms
golang : 4.701811465s

Java线程真的比golang goroutine快吗?或者我的程序出错了?

基准很难正确,当你在衡量其他东西时,很容易认为你在衡量一件事(goroutines/threads的成本)。我想这就是这里正在发生的事情。

例如,线程/goroutines中的内部循环在这两个程序中并不相同。重写goroutine使其更像java代码:

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

结果是速度提高了2倍。

当我在机器上运行它时,这仍然留下了显著的性能差距——go代码在1000个goroutine中需要663毫秒,而java代码在1000线程中需要55毫秒。可能发生的情况是,JVM在线程的run方法中执行了几次之后,正在JITting掉无用的循环。

这里有一些证据表明goroutines的成本并不相关。在我的机器上,仅仅串行执行代码就可以获得2.55秒的运行时间:

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))
}

该代码的基于goroutine的版本在4个处理器上运行时间为663ms,仅略高于串行代码(在我的机器上)所需2.55秒的四分之一。因此,这确实是非常有效的并行,goroutines的成本可以忽略不计。

因此,我认为您主要是在测试java和go执行空循环的效率。看起来这是JIT的一个完美用例,您可以从Java代码出色的运行时性能中看到这一点。

i=i+i测试很愚蠢,因为它几乎只执行24次加法和24<比较,我相信我们都承认,在现实世界中,我们并不经常遇到只用48条机器指令就能完成的问题。

Executors.newFixedThreadPool与Thread相比,更接近于goroutine的Java等价物,请尝试预热,您可能会得到不同的结果。

最新更新